email

package
v0.8.1 Latest Latest
Warning

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

Go to latest
Published: Oct 22, 2026 License: Apache-2.0 Imports: 29 Imported by: 0

Documentation

Overview

Package email is the platform's one email-sending capability: a narrow Sender interface plus a registry of providers (SMTP, SES, Resend, Postmark, SendGrid, Mailgun), shared by internal/alerting and internal/api so neither imports the other.

Index

Constants

View Source
const (
	RecordSPF   = "spf"
	RecordDKIM  = "dkim"
	RecordDMARC = "dmarc"

	CheckPass = "pass"
	CheckWarn = "warn"
)

Domain check record types and statuses.

View Source
const (
	DeliverySent    = "sent"
	DeliveryFailed  = "failed"
	DeliveryBounced = "bounced"
)

Delivery statuses.

View Source
const (
	TLSAuto     = ""         // STARTTLS when the server offers it, implicit TLS on port 465
	TLSStartTLS = "starttls" // require STARTTLS
	TLSImplicit = "tls"      // TLS from the first byte
	TLSNone     = "none"     // plaintext
)

TLS modes for SMTP-based backends.

View Source
const (
	MailgunRegionUS = "us"
	MailgunRegionEU = "eu"
)

Mailgun regions an operator can pick.

Variables

View Source
var ErrNotConfigured = errors.New("email: not configured")

ErrNotConfigured is returned by NewSender when a Config names no backend: no settings row and no env-var fallback.

View Source
var ErrRateLimited = errors.New("email: send rate limit reached")

ErrRateLimited is returned when the configured sends-per-minute or per-day cap is reached.

View Source
var SESRegions = []string{
	"us-east-1", "us-east-2", "us-west-1", "us-west-2",
	"af-south-1",
	"ap-south-1", "ap-south-2", "ap-northeast-1", "ap-northeast-2", "ap-northeast-3",
	"ap-southeast-1", "ap-southeast-2", "ap-southeast-3", "ap-southeast-5",
	"ca-central-1", "ca-west-1",
	"eu-central-1", "eu-central-2", "eu-west-1", "eu-west-2", "eu-west-3",
	"eu-north-1", "eu-south-1", "eu-south-2",
	"il-central-1", "me-south-1", "me-central-1", "mx-central-1", "sa-east-1",
	"us-gov-west-1",
}

SESRegions is the one registry of AWS regions that offer SES; the API validates against it and the UI mirrors it.

Functions

func DomainOf

func DomainOf(from string) string

DomainOf returns the domain part of an address ("Name <a@b.com>" or "a@b.com").

func FormatFrom

func FormatFrom(name, addr string) string

FormatFrom renders "Name <addr>" (or the bare address when name is empty).

func IsTransient

func IsTransient(err error) bool

IsTransient reports whether a retry could plausibly succeed.

func Register

func Register(b Backend, fn Builder)

Register adds a provider to the registry; each provider file calls it from init, so adding one is one file.

func SESSMTPHost

func SESSMTPHost(region string) (string, error)

SESSMTPHost derives the SES SMTP endpoint for region.

func SendMessage

func SendMessage(ctx context.Context, s Sender, m Message) error

SendMessage delivers m through s. A Sender without HTML support gets the plain-text part through Send, so mail never silently disappears.

func SendWithID

func SendWithID(ctx context.Context, s Sender, to, subject, body string) (string, error)

SendWithID sends through s and returns the provider message id when the sender exposes one.

func ValidBackend

func ValidBackend(b Backend) bool

ValidBackend reports whether b is a registered provider.

func ValidSESRegion

func ValidSESRegion(region string) bool

ValidSESRegion reports whether region is a listed SES region.

func ValidateAddress added in v0.2.0

func ValidateAddress(s string) error

ValidateAddress reports an error unless s is a single bare email address ("a@b.com", not "Name <a@b.com>" or a list).

func WithKind

func WithKind(ctx context.Context, kind string) context.Context

WithKind tags sends made with ctx (for example "password_reset") in the delivery log.

func WithoutRetry

func WithoutRetry(ctx context.Context) context.Context

WithoutRetry disables retries for sends made with ctx, for interactive test sends.

Types

type APIConfig

type APIConfig struct {
	APIKey  string
	From    string
	ReplyTo string
	// Domain and Region are Mailgun-only.
	Domain string
	Region string
}

APIConfig is the connection detail shared by the key-based HTTP providers (Postmark, SendGrid, Mailgun).

type Backend

type Backend string

Backend names which transport a Config uses to actually send.

const (
	BackendSMTP     Backend = "smtp"
	BackendSES      Backend = "ses"
	BackendSESSMTP  Backend = "ses_smtp"
	BackendResend   Backend = "resend"
	BackendPostmark Backend = "postmark"
	BackendSendGrid Backend = "sendgrid"
	BackendMailgun  Backend = "mailgun"
)

The built-in backends; each registers itself in its own file.

func Backends

func Backends() []Backend

Backends lists every registered provider in a stable order.

type Builder

type Builder func(cfg Config) (Sender, error)

Builder turns a Config into the Sender for one provider.

type Config

type Config struct {
	Backend Backend
	SMTP    *SMTPConfig
	SES     *SESConfig
	Resend  *ResendConfig
	API     *APIConfig
	Limits  Limits
}

Config names which backend to use plus that backend's own settings.

type ConfigLoader

type ConfigLoader func(ctx context.Context) (Config, error)

ConfigLoader resolves the current Config on every call, never cached: settings can change at runtime.

type DNSLookup

type DNSLookup interface {
	LookupTXT(ctx context.Context, name string) ([]string, error)
	LookupCNAME(ctx context.Context, name string) (string, error)
}

DNSLookup is the slice of net.Resolver the domain check needs, so tests can fake it.

type Delivery

type Delivery struct {
	At        time.Time
	Provider  string
	To        string
	Kind      string
	Status    string // DeliverySent or DeliveryFailed
	MessageID string
	Error     string
	ErrorKind FailureKind
	Attempts  int
}

Delivery is one send outcome handed to a Recorder.

type DomainCheck

type DomainCheck struct {
	Domain   string        `json:"domain"`
	Provider string        `json:"provider"`
	Records  []RecordCheck `json:"records"`
	// SPFInclude is the provider's expected SPF include, shown as a hint.
	SPFInclude string `json:"spf_include,omitempty"`
}

DomainCheck is the SPF, DKIM and DMARC verdict for a sending domain.

func CheckDomain

func CheckDomain(ctx context.Context, dns DNSLookup, domain string, provider Backend) DomainCheck

CheckDomain looks up SPF, DKIM and DMARC for domain and compares them with what provider needs.

type DynamicSender

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

DynamicSender adapts a ConfigLoader into a Sender, so a settings change takes effect on the very next send with no restart required.

func NewDynamicSender

func NewDynamicSender(load ConfigLoader) *DynamicSender

NewDynamicSender builds a DynamicSender. load must not be nil.

func (*DynamicSender) Configured

func (d *DynamicSender) Configured(ctx context.Context) bool

Configured reports whether the current settings name a backend that can send, so callers can tell "no mail server set up" from a failed send.

func (*DynamicSender) Send

func (d *DynamicSender) Send(ctx context.Context, to, subject, body string) error

Send implements Sender. It rejects a CR or LF in to or subject before any backend runs, since either could start an injected header.

func (*DynamicSender) SendDetailed

func (d *DynamicSender) SendDetailed(ctx context.Context, to, subject, body string) (Result, error)

SendDetailed is Send plus the provider and message id of a successful send.

func (*DynamicSender) SendMessage

func (d *DynamicSender) SendMessage(ctx context.Context, m Message) error

SendMessage implements RichSender through the current settings, with the same retries, caps and delivery log as Send.

func (*DynamicSender) SendMessageDetailed

func (d *DynamicSender) SendMessageDetailed(ctx context.Context, m Message) (Result, error)

SendMessageDetailed is SendMessage plus the provider and message id of a successful send.

func (*DynamicSender) WithRecorder

func (d *DynamicSender) WithRecorder(r Recorder) *DynamicSender

WithRecorder logs every send outcome and enforces the configured send caps.

func (*DynamicSender) WithRetry

func (d *DynamicSender) WithRetry(attempts int, base time.Duration) *DynamicSender

WithRetry sets how many attempts a transient provider failure gets and the first backoff delay.

type FailureKind

type FailureKind string

FailureKind is the typed reason a send failed, mapped to a Fix in the UI.

const (
	FailAuth              FailureKind = "auth_failed"
	FailSenderNotVerified FailureKind = "sender_not_verified"
	FailRegionMismatch    FailureKind = "region_mismatch"
	FailPortBlocked       FailureKind = "port_blocked"
	FailTLS               FailureKind = "tls_error"
	FailRateLimited       FailureKind = "rate_limited"
	FailNotConfigured     FailureKind = "not_configured"
	FailOther             FailureKind = "other"
)

The failure kinds Classify returns.

func Classify

func Classify(err error) FailureKind

Classify maps a send error to a FailureKind.

type HTTPError

type HTTPError struct {
	Provider string
	Status   int
	Body     string
}

HTTPError is a non-2xx answer from an HTTP provider API.

func (*HTTPError) Error

func (e *HTTPError) Error() string

type Limits

type Limits struct {
	PerMinute int
	PerDay    int
}

Limits caps how many sends the control plane will make; zero means unlimited.

type Message

type Message struct {
	To      string
	Subject string
	Text    string
	HTML    string
	Headers map[string]string
}

Message is an email with an HTML alternative and extra headers such as List-Unsubscribe.

type RecordCheck

type RecordCheck struct {
	Type    string `json:"type"`
	Name    string `json:"name"`
	Status  string `json:"status"`
	Code    string `json:"code"`
	Value   string `json:"value,omitempty"`
	Message string `json:"message"`
}

RecordCheck is one DNS record's verdict. Code is a stable key the UI translates; Message is the English text the CLI prints.

type Recorder

type Recorder interface {
	RecordDelivery(ctx context.Context, d Delivery) error
	CountSent(ctx context.Context, since time.Time) (int, error)
}

Recorder persists delivery outcomes and answers the sent-count questions the rate caps need.

type ResendConfig added in v0.2.0

type ResendConfig struct {
	APIKey  string
	From    string
	ReplyTo string
}

ResendConfig is a Resend sender's connection details.

type Result

type Result struct {
	Provider  string
	MessageID string
	Attempts  int
}

Result describes a successful send.

type RichSender

type RichSender interface {
	SendMessage(ctx context.Context, m Message) error
}

RichSender is implemented by senders that can deliver Message with its HTML part and headers intact.

type SESConfig

type SESConfig struct {
	Region          string
	AccessKeyID     string
	SecretAccessKey string
	From            string
	ReplyTo         string
}

SESConfig is an AWS SES v2 sender's connection details.

type SMTPConfig

type SMTPConfig struct {
	// Addr is host:port, e.g. "smtp.example.com:587".
	Addr     string
	Host     string // just the host part of Addr, for PlainAuth's identity check
	Username string
	Password string
	From     string
	// EnvelopeFrom is the bare MAIL FROM address; empty uses From.
	EnvelopeFrom string
	ReplyTo      string
	TLSMode      string
	SkipVerify   bool
}

SMTPConfig is an SMTP server's connection details.

type SendError

type SendError struct {
	Kind     FailureKind
	Attempts int
	Err      error
}

SendError wraps a final failure with its kind and how many attempts were made.

func (*SendError) Error

func (e *SendError) Error() string

func (*SendError) Unwrap

func (e *SendError) Unwrap() error

type Sender

type Sender interface {
	Send(ctx context.Context, to, subject, body string) error
}

Sender sends one plain-text email.

func NewSender

func NewSender(cfg Config) (Sender, error)

NewSender builds the Sender cfg.Backend names, or ErrNotConfigured for an empty or unrecognized Backend.

type SystemDNS

type SystemDNS struct{}

SystemDNS is the control plane's own resolver, so the check needs no external service.

func (SystemDNS) LookupCNAME

func (SystemDNS) LookupCNAME(ctx context.Context, name string) (string, error)

LookupCNAME resolves a CNAME through the system resolver.

func (SystemDNS) LookupTXT

func (SystemDNS) LookupTXT(ctx context.Context, name string) ([]string, error)

LookupTXT resolves TXT records through the system resolver.

Jump to

Keyboard shortcuts

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