Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ Yes, please! Contributions of all kinds are very welcome! Feel free to check our
| [Line](https://line.me) | [service/line](service/line) | [line/line-bot-sdk-go](https://github.com/line/line-bot-sdk-go) | :heavy_check_mark: |
| [Line Notify](https://notify-bot.line.me) | [service/line](service/line) | [utahta/go-linenotify](https://github.com/utahta/go-linenotify) | :heavy_check_mark: |
| [Mailgun](https://www.mailgun.com) | [service/mailgun](service/mailgun) | [mailgun/mailgun-go](https://github.com/mailgun/mailgun-go) | :heavy_check_mark: |
| [Mailtrap](https://mailtrap.io) | [service/mailtrap](service/mailtrap) | - | :heavy_check_mark: |
| [Matrix](https://www.matrix.org) | [service/matrix](service/matrix) | [mautrix/go](https://github.com/mautrix/go) | :heavy_check_mark: |
| [Microsoft Teams](https://www.microsoft.com/microsoft-teams) | [service/msteams](service/msteams) | [atc0005/go-teams-notify](https://github.com/atc0005/go-teams-notify) | :heavy_check_mark: |
| [PagerDuty](https://www.pagerduty.com) | [service/pagerduty](service/pagerduty) | [PagerDuty/go-pagerduty](https://github.com/PagerDuty/go-pagerduty) | :heavy_check_mark: |
Expand Down
109 changes: 109 additions & 0 deletions service/mailtrap/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Mailtrap

[![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/nikoksr/notify/service/mailtrap)

[Mailtrap](https://mailtrap.io) is an email delivery platform for sending, testing, and controlling emails. This service sends transactional emails through the Mailtrap [Email Sending API](https://api-docs.mailtrap.io).

## Prerequisites

To use the Mailtrap notification service, you will need:

- A Mailtrap API token — create one under [API Tokens](https://mailtrap.io/api-tokens).
- A verified sending domain and a sender email address that belongs to it.

## Usage

```go
package main

import (
"context"
"log"

"github.com/nikoksr/notify"
"github.com/nikoksr/notify/service/mailtrap"
)

func main() {
mailtrapSvc := mailtrap.New("your-api-token", "sender@your-domain.com")

mailtrapSvc.AddReceivers("recipient@example.com")

notifier := notify.New()
notifier.UseServices(mailtrapSvc)

if err := notifier.Send(context.Background(), "subject", "message"); err != nil {
log.Fatalf("notifier.Send() failed: %s", err.Error())
}

log.Println("notification sent")
}
```

## Options

### Sender name

By default only the sender email address is sent. Use `WithSenderName` to also set a display name:

```go
mailtrapSvc := mailtrap.New(
"your-api-token",
"sender@your-domain.com",
mailtrap.WithSenderName("Acme Notifications"),
)
```

### Plain-text messages

The message body is sent as HTML by default. Switch to plain text with `BodyFormat`:

```go
mailtrapSvc := mailtrap.New("your-api-token", "sender@your-domain.com")
mailtrapSvc.BodyFormat(mailtrap.PlainText)
```

### Bulk sending

To route messages through the Mailtrap bulk sending host (intended for high-volume sending), use `WithBulkHost`:

```go
mailtrapSvc := mailtrap.New(
"your-api-token",
"sender@your-domain.com",
mailtrap.WithBulkHost(),
)
```

### Sandbox (testing)

To capture emails in a Mailtrap test inbox instead of delivering them, use `WithSandbox` with your inbox ID. The sandbox API requires the inbox ID as part of the request path:

```go
mailtrapSvc := mailtrap.New(
"your-api-token",
"sender@your-domain.com",
mailtrap.WithSandbox("your-inbox-id"),
)
```

### Custom HTTP client

To configure timeouts, proxies, or a custom transport, provide your own HTTP client with `WithHTTPClient`:

```go
import (
"net/http"
"time"

"github.com/nikoksr/notify/service/mailtrap"
)

httpClient := &http.Client{Timeout: 10 * time.Second}

mailtrapSvc := mailtrap.New(
"your-api-token",
"sender@your-domain.com",
mailtrap.WithHTTPClient(httpClient),
)
```
145 changes: 145 additions & 0 deletions service/mailtrap/mailtrap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// Package mailtrap provides a notify.Notifier implementation that sends transactional emails through the Mailtrap Email
// Sending API.
package mailtrap

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
)

const (
defaultSendHost = "https://send.api.mailtrap.io"
sendPath = "/api/send"
)

// BodyType is used to specify the format of the message body.
type BodyType int

const (
// HTML specifies that the message body is HTML. This is the default.
HTML BodyType = iota
// PlainText specifies that the message body is plain text.
PlainText
)

// httpDoer is the subset of *http.Client used by Mailtrap. It allows callers
// to inject a custom client (see WithHTTPClient) and simplifies testing.
type httpDoer interface {
Do(req *http.Request) (*http.Response, error)
}

// Mailtrap holds the data required to communicate with the Mailtrap Email Sending API.
type Mailtrap struct {
apiKey string
host string
path string
client httpDoer
senderAddress string
senderName string
usePlainText bool
receivers []recipient
}

type recipient struct {
Email string `json:"email"`
}

type sender struct {
Email string `json:"email"`
Name string `json:"name,omitempty"`
}

type payload struct {
From sender `json:"from"`
To []recipient `json:"to"`
Subject string `json:"subject"`
Text string `json:"text,omitempty"`
HTML string `json:"html,omitempty"`
}

// New returns a new instance of a Mailtrap notification service.
// You will need a Mailtrap API token, which can be created at https://mailtrap.io/api-tokens.
// See https://api-docs.mailtrap.io for the full API documentation.
func New(apiKey, senderAddress string, opts ...Option) *Mailtrap {
m := &Mailtrap{
apiKey: apiKey,
host: defaultSendHost,
path: sendPath,
client: http.DefaultClient,
senderAddress: senderAddress,
receivers: []recipient{},
}

for _, opt := range opts {
opt(m)
}

return m
}

// AddReceivers takes email addresses and adds them to the internal address list. The Send method will send
// a given message to all those addresses.
func (m *Mailtrap) AddReceivers(addresses ...string) {
for _, address := range addresses {
m.receivers = append(m.receivers, recipient{Email: address})
}
}

// BodyFormat can be used to specify the format of the message body.
// Default BodyType is HTML.
func (m *Mailtrap) BodyFormat(format BodyType) {
switch format {
case PlainText:
m.usePlainText = true
case HTML:
m.usePlainText = false
default:
m.usePlainText = false
}
}

// Send takes a message subject and a message body and sends them to all previously set receivers.
// Message body supports html as markup language by default; use BodyFormat(PlainText) to send plain text instead.
func (m Mailtrap) Send(ctx context.Context, subject, message string) error {
body := payload{
From: sender{Email: m.senderAddress, Name: m.senderName},
To: m.receivers,
Subject: subject,
}

if m.usePlainText {
body.Text = message
} else {
body.HTML = message
}

data, err := json.Marshal(body)
if err != nil {
return fmt.Errorf("marshal message: %w", err)
}

req, err := http.NewRequestWithContext(ctx, http.MethodPost, m.host+m.path, bytes.NewReader(data))
if err != nil {
return fmt.Errorf("create request: %w", err)
}

req.Header.Set("Content-Type", "application/json")
req.Header.Set("Api-Token", m.apiKey)

resp, err := m.client.Do(req)
if err != nil {
return fmt.Errorf("send message: %w", err)
}
defer func() { _ = resp.Body.Close() }()

if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
respBody, _ := io.ReadAll(resp.Body)
return fmt.Errorf("the Mailtrap endpoint returned status %d: %s", resp.StatusCode, string(respBody))
}

return nil
}
Loading
Loading