Help us shape the future of Go! Take the 2026 Go Developer Survey

mail

package
v0.30.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 19, 2026 License: MIT Imports: 31 Imported by: 1

Documentation

Overview

Package mail sends email. A Mailable describes one email (envelope and content); a Mailer renders it and hands the Message to a Transport: SMTP, Resend, log (development) or Array (tests).

type Welcome struct{ User models.User }

func (m Welcome) Envelope() mail.Envelope {
	return mail.Envelope{To: []string{m.User.Email}, Subject: "Welcome!"}
}
func (m Welcome) Content() mail.Content { return mail.View("mail/welcome", m) }

err := app.Mailer().Send(ctx, Welcome{User: u})

The app builds its Mailer from MAIL_* variables; Preview renders mailables in the browser during development.

See https://github.com/AlexGalhardo/vukem/blob/main/docs/subsystems.md#mail.

Example
package main

import (
	"context"
	"fmt"

	"github.com/AlexGalhardo/vukem/mail"
)

type Welcome struct{ Email string }

func (m Welcome) Envelope() mail.Envelope {
	return mail.Envelope{To: []string{m.Email}, Subject: "Welcome!"}
}

func (m Welcome) Content() mail.Content { return mail.Text("Thanks for signing up.") }

func main() {
	// Array keeps messages in memory (tests); apps configure SMTP or
	// Resend through MAIL_* variables.
	sent := mail.NewArray()
	m := mail.New(sent, "Shop <hello@shop.test>", nil)

	if err := m.Send(context.Background(), Welcome{Email: "ada@example.com"}); err != nil {
		fmt.Println(err)
		return
	}

	msgs := sent.SentTo("ada@example.com")
	for i := range msgs {
		fmt.Println(msgs[i].From, "|", msgs[i].Subject, "|", msgs[i].Text)
	}
}
Output:
"Shop" <hello@shop.test> | Welcome! | Thanks for signing up.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrNoQueue = errors.New("mail: SendLater needs a queue (use app.Mailer or Mailer.SetEnqueue)")

ErrNoQueue is returned by SendLater (SendLaterTx) on a mailer without an Enqueue (EnqueueTx).

View Source
var ErrNoRecipients = errors.New("mail: no recipients")

ErrNoRecipients is returned for messages without To, Cc or Bcc.

Functions

func Build

func Build(msg *Message) ([]byte, error)

Build renders msg as RFC 5322 bytes: multipart/mixed (attachments) over multipart/related (inline parts) over multipart/alternative (text, HTML). Bcc is never written as a header.

func Preview

func Preview(m *Mailer, mailables map[string]Mailable) http.Handler

Preview serves rendered mailables for development: the index lists names, "<name>" shows the HTML part and "<name>?text=1" the text part. Mount it only outside production:

if !app.Config.IsProduction() {
	r.Handle("/_mail/", http.StripPrefix("/_mail/", mail.Preview(mailer, map[string]mail.Mailable{...})))
}

Types

type Array

type Array struct {
	// contains filtered or unexported fields
}

Array keeps sent messages in memory (tests; vukemtest uses it).

func NewArray

func NewArray() *Array

NewArray returns an empty Array transport.

func (*Array) Reset

func (a *Array) Reset()

Reset forgets sent messages.

func (*Array) Send

func (a *Array) Send(_ context.Context, msg *Message) error

Send implements Transport.

func (*Array) Sent

func (a *Array) Sent() []Message

Sent returns a copy of the sent messages.

func (*Array) SentTo

func (a *Array) SentTo(addr string) []Message

SentTo returns the messages with addr among To, Cc or Bcc.

type Attachable added in v0.21.0

type Attachable interface {
	Attachments() []Attachment
}

Attachable is implemented by mailables that attach files.

type Attachment

type Attachment struct {
	Filename    string
	ContentType string
	Data        []byte
	Inline      bool
	ContentID   string
}

Attachment is a file sent with the message; Inline parts are referenced from HTML as cid:<ContentID>.

type Content

type Content struct {
	// contains filtered or unexported fields
}

Content is the body: a view (rendered by the Mailer) or literal parts.

func HTML

func HTML(htmlBody, text string) Content

HTML is literal HTML (and optional text) content.

func Text

func Text(text string) Content

Text is plain-text content.

func View

func View(name string, data any) Content

View renders the html/template page name with data; the plain-text part comes from the page "<name>_text" when it exists, else from the HTML.

type ContextEnveloper added in v0.25.0

type ContextEnveloper interface {
	EnvelopeContext(ctx context.Context) Envelope
}

ContextEnveloper is implemented by mailables whose envelope depends on the send's context, such as a subject in the request locale:

func (m Welcome) EnvelopeContext(ctx context.Context) mail.Envelope {
	return mail.Envelope{To: []string{m.User.Email}, Subject: i18n.T(ctx, "mail.welcome")}
}

The Mailer calls it instead of Envelope, which stays the fallback for code without a context. SendLater renders before queueing, so the subject uses the context of the SendLater call.

type Enqueue added in v0.24.0

type Enqueue func(ctx context.Context, msg *Message, opts ...queue.Option) error

Enqueue stores a rendered message in a queue job that sends it later. The app's mailer (app.Mailer) enqueues on the app queue with the payload encrypted, so mail bodies (reset links, tokens) never sit in the jobs tables in clear text.

type EnqueueTx added in v0.25.0

type EnqueueTx func(ctx context.Context, ex database.Executor, msg *Message, opts ...queue.Option) error

EnqueueTx is Enqueue tied to the transaction of ex (Queue.DispatchTx): the mail is queued only if the transaction commits.

type Envelope

type Envelope struct {
	From    string
	To      []string
	Cc      []string
	Bcc     []string
	ReplyTo []string
	Subject string
	// Tags are provider metadata (Resend tags; X-Tag headers over SMTP;
	// Mailtrap category and custom variables). Names must not be empty and
	// neither may contain a line break or colon; Resend also limits them
	// to ASCII letters, digits, "_" and "-".
	Tags map[string]string
}

Envelope holds addresses and headers. Addresses accept "Name <a@b.c>".

type Log

type Log struct {
	L *slog.Logger
}

Log writes messages to a logger instead of sending them (development). Bodies are logged at debug level only.

func (Log) Send

func (l Log) Send(ctx context.Context, msg *Message) error

Send implements Transport.

type Mailable

type Mailable interface {
	Envelope() Envelope
	Content() Content
}

Mailable is one email.

type Mailer

type Mailer struct {
	// contains filtered or unexported fields
}

Mailer renders mailables and sends them.

func New

func New(t Transport, from string, views web.Renderer) *Mailer

New returns a Mailer sending from the default from address through t. views renders View contents (the app's view engine); it may be nil when only literal contents are used.

func (*Mailer) Render

func (m *Mailer) Render(ctx context.Context, mailable Mailable) (*Message, error)

Render builds the Message without sending it (previews, tests).

func (*Mailer) Send

func (m *Mailer) Send(ctx context.Context, mailable Mailable) error

Send renders and delivers mailable.

func (*Mailer) SendLater added in v0.24.0

func (m *Mailer) SendLater(ctx context.Context, mailable Mailable, opts ...queue.Option) error

SendLater renders mailable now (so rendering errors reach the caller) and sends it from a queue job, retried with the queue's backoff. The message ID stays the same across retries, so providers that deduplicate by it send once. opts select the queue, delay or retries of this mail. With QUEUE_CONNECTION=sync the job runs during the call.

func (*Mailer) SendLaterTx added in v0.25.0

func (m *Mailer) SendLaterTx(ctx context.Context, ex database.Executor, mailable Mailable, opts ...queue.Option) error

SendLaterTx is SendLater inside the transaction of ex: the mail is queued only if the transaction commits, so rolled-back work (a failed payment) never emails anyone. Rendering errors are returned at once. On the database queue the job is inserted with the transaction; on other drivers it is queued right after the commit (with QUEUE_CONNECTION=sync the mail is sent then, and a send error is logged, not returned).

func (*Mailer) SetEnqueue added in v0.24.0

func (m *Mailer) SetEnqueue(fn Enqueue)

SetEnqueue sets how SendLater queues messages. Call it before the mailer is used; app.Mailer sets it.

func (*Mailer) SetEnqueueTx added in v0.25.0

func (m *Mailer) SetEnqueueTx(fn EnqueueTx)

SetEnqueueTx sets how SendLaterTx queues messages. Call it before the mailer is used; app.Mailer sets it.

func (*Mailer) Transport

func (m *Mailer) Transport() Transport

Transport returns the mailer's transport (tests read the Array).

type Mailtrap added in v0.26.0

type Mailtrap struct {
	// contains filtered or unexported fields
}

Mailtrap sends through the Mailtrap Email API over HTTPS, for hosts that block outbound SMTP. The API has no idempotency key, so a retried 5xx answered after delivery may send twice.

func NewMailtrap added in v0.26.0

func NewMailtrap(cfg MailtrapConfig) *Mailtrap

NewMailtrap returns the Mailtrap transport. Send fails without an API token.

func (*Mailtrap) Send added in v0.26.0

func (m *Mailtrap) Send(ctx context.Context, msg *Message) error

Send implements Transport, retrying 429, 5xx and failed connections with backoff.

type MailtrapConfig added in v0.26.0

type MailtrapConfig struct {
	APIToken config.Secret
	// InboxID > 0 sends to that Email Sandbox inbox
	// (sandbox.api.mailtrap.io/api/send/{id}) instead of delivering.
	InboxID int64
	// BaseURL defaults to https://send.api.mailtrap.io, or
	// https://sandbox.api.mailtrap.io with InboxID (tests use a fake).
	BaseURL string
	// HTTP is the client (10s timeout and no redirects by default).
	HTTP *http.Client
	// MaxRetries for 429 and 5xx responses and failed connections (default 2).
	MaxRetries int
	// MaxBackoff caps waits between retries, including Retry-After (default 5s).
	MaxBackoff time.Duration
}

MailtrapConfig configures the Mailtrap Email API transport (https://docs.mailtrap.io/developers/email-sending).

type MailtrapError added in v0.26.0

type MailtrapError struct {
	Status int
	Errors []string
}

MailtrapError is an error answered by the Mailtrap API. It never carries the API token or the message body; Errors may contain addresses quoted by the API.

func (*MailtrapError) Error added in v0.26.0

func (e *MailtrapError) Error() string

Error returns the status and the first errors Mailtrap listed, on one line.

func (*MailtrapError) Retryable added in v0.26.0

func (e *MailtrapError) Retryable() bool

Retryable reports whether the request may succeed later.

type Message

type Message struct {
	// ID identifies the message (Message-ID, Resend idempotency key).
	ID          string
	From        string
	To          []string
	Cc          []string
	Bcc         []string
	ReplyTo     []string
	Subject     string
	HTML        string
	Text        string
	Attachments []Attachment
	Tags        map[string]string
}

Message is a rendered email ready for a Transport.

type Resend

type Resend struct {
	// contains filtered or unexported fields
}

Resend sends through the Resend HTTP API. The message ID is the Idempotency-Key, so retries never send twice.

func NewResend

func NewResend(cfg ResendConfig) *Resend

NewResend returns the Resend transport.

func (*Resend) Send

func (r *Resend) Send(ctx context.Context, msg *Message) error

Send implements Transport, retrying 429, 5xx and network errors with backoff.

type ResendConfig

type ResendConfig struct {
	APIKey config.Secret
	// BaseURL defaults to https://api.resend.com (tests use a fake).
	BaseURL string
	// HTTP is the client (10s timeout by default).
	HTTP *http.Client
	// MaxRetries for 429 and 5xx responses and network errors (default 2).
	MaxRetries int
	// MaxBackoff caps waits between retries, including Retry-After (default 5s).
	MaxBackoff time.Duration
}

ResendConfig configures the Resend transport (https://resend.com).

type ResendError

type ResendError struct {
	Status  int
	Name    string
	Message string
}

ResendError is an error answered by the Resend API. It never contains the API key.

func (*ResendError) Error

func (e *ResendError) Error() string

Error returns the status, Resend error name and message.

func (*ResendError) Retryable

func (e *ResendError) Retryable() bool

Retryable reports whether the request may succeed later.

type SMTP

type SMTP struct {
	// contains filtered or unexported fields
}

SMTP sends through an SMTP server with STARTTLS (or implicit TLS on 465).

func NewSMTP

func NewSMTP(cfg SMTPConfig) *SMTP

NewSMTP returns the SMTP transport.

func (*SMTP) Send

func (s *SMTP) Send(ctx context.Context, msg *Message) error

Send implements Transport.

type SMTPConfig

type SMTPConfig struct {
	Host     string
	Port     int // 587 (STARTTLS, default) or 465 (implicit TLS)
	Username string
	Password config.Secret
	// InsecureSkipTLS sends without TLS (local catchers such as Mailpit
	// only); by default STARTTLS is required.
	InsecureSkipTLS bool
	// Timeout bounds dialing and the whole exchange (default 30s).
	Timeout time.Duration
	// TLSConfig overrides the TLS settings (tests).
	TLSConfig *tls.Config
}

SMTPConfig configures the SMTP transport.

type Transport

type Transport interface {
	Send(ctx context.Context, msg *Message) error
}

Transport delivers messages.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL