Documentation
¶
Overview ¶
Package mail defines email messages and the Sender contract implemented by provider modules such as mail/resend and mail/smtp (ADR-0025).
Stability: stable (ADR-0015, ADR-0054).
Index ¶
Constants ¶
const RedactedAddress = "[email]"
RedactedAddress replaces email addresses in text redacted by RedactAddresses.
Variables ¶
var ErrRejected = errors.New("mail: rejected")
ErrRejected marks a send that will never succeed as it is, such as an invalid message, an unverified sender domain or a refused recipient. Providers wrap it; the jobs mail worker stops retrying such sends. Temporary failures (network errors, rate limits, provider outages) must not wrap it.
var ErrSuppressed = fmt.Errorf("%w: every recipient is on the suppression list", ErrRejected)
ErrSuppressed marks a message none of whose recipients may receive email because they are on the suppression list: addresses that bounced permanently or complained (ADR-0062). It wraps ErrRejected, so the jobs mail worker cancels the send instead of retrying it.
Functions ¶
func NormalizeAddress ¶
NormalizeAddress returns email as suppression lists store it: without surrounding spaces and in lower case. Mailbox names are case-sensitive in theory but not at any provider in practice, and a list that missed a differently capitalized address would keep sending to it.
func RedactAddresses ¶
RedactAddresses returns text with everything shaped like an email address replaced by RedactedAddress. Providers and senders apply it to server replies before they become errors: SMTP servers and APIs often echo the recipient ("550 5.1.1 <jane@example.com>: Recipient address rejected"), and errors are shown in job runs and logged, where addresses must not appear.
Types ¶
type Brand ¶
type Brand struct {
// Name is the product's name, shown as the wordmark when LogoURL is empty
// and always in the footer.
Name string
// URL is where the name links and the address in the footer; empty for
// neither.
URL string
// LogoURL is the absolute URL of a logo shown in place of the wordmark,
// up to 32px high. Empty shows the name.
LogoURL string
// SupportEmail ends the footer with where to write; empty omits it.
SupportEmail string
// why the recipient gets the email. Empty omits it.
Footer string
}
Brand is what every email an app sends has in common: the name at the top and in the footer, where it links, an optional logo, and how to reach support. Apps build one in internal/app and pass it to the modules' NewBrandedEmails; their own emails go through Brand.Message (ADR-0078).
func (Brand) Message ¶
Message returns a message to to with subject and the category tag, with e rendered by b. From is left for the sender's defaults.
type Button ¶
A Button is a call to action: the label on the button and the address it opens, which is repeated as a link for clients that don't show buttons.
type Defaults ¶
type Defaults struct {
FromName config.Value[string]
FromEmail config.Value[string]
// ReplyTo is one email address; an empty value means no reply-to.
ReplyTo config.Value[string]
}
Defaults are live sender values applied to messages that leave them empty. Each is read on every send, so runtime settings apply immediately. A nil field is ignored.
type Email ¶
type Email struct {
// Preheader is the summary inbox lists show after the subject. It is not
// shown in the opened email.
Preheader string
// Title is the heading.
Title string
// Paragraphs are the message, before the code or button.
Paragraphs []string
// Code is a one-time code, shown large in its own block. CodeLabel is
// its caption ("Verification code") and CodeNote the line under the
// block ("It expires in 15 minutes.").
Code string
CodeLabel string
CodeNote string
// Button is the main action; the zero value shows none.
Button Button
// Closing paragraphs come last, set smaller: "If you didn't do this…".
Closing []string
}
An Email is the content of one transactional message, rendered by Brand.Render as HTML and plain text. Every field is optional; the parts appear in the order they are declared.
type Message ¶
type Message struct {
From Address
To []Address
ReplyTo []Address
Subject string
// Text and HTML are alternative bodies; at least one is required.
Text string
HTML string
// IdempotencyKey lets providers drop duplicate sends, for example when a
// background job is retried. Jobs use their job ID.
IdempotencyKey string
// Tags are provider metadata such as a message category.
Tags map[string]string
}
A Message is an email to send. Add fields by setting them; the zero value of every optional field means "not set".
type Sender ¶
A Sender delivers email. Implementations must be safe for concurrent use, respect ctx cancellation and return an error when delivery fails.
func WithDefaults ¶
WithDefaults returns a Sender that fills an empty From and ReplyTo from d, then sends through next. Messages that set them keep their own.
func WithSuppressionList ¶
func WithSuppressionList(next Sender, list SuppressionList) Sender
WithSuppressionList returns a Sender that removes suppressed recipients from each message before sending it through next. A message left without recipients isn't sent and returns ErrSuppressed; a list that can't be read returns its error, which is temporary, so the send is retried rather than delivered to an address that may be suppressed. Reply-to addresses aren't checked: they receive nothing.
Wrap the sender the mail worker delivers through, so the check happens when the email is sent rather than when it is queued.
type SenderFunc ¶
SenderFunc adapts a function to the Sender interface.
type SuppressionList ¶
type SuppressionList interface {
// Suppressed returns the addresses among emails, each normalized with
// [NormalizeAddress], that are on the list. An error means the list
// couldn't be read.
Suppressed(ctx context.Context, emails []string) ([]string, error)
}
A SuppressionList holds addresses that must not receive email, such as those that bounced permanently or marked a message as spam. Implementations must be safe for concurrent use.