Foundation for the passwordless auth migration (ADR-0028 Phase A) and the BDD email-parallel strategy (ADR-0030). This PR ships only the infrastructure — no auth code yet ; that lands in subsequent PRs. Changes: - docker-compose.yml: add mailpit service (axllent/mailpit:latest), SMTP on :1025, HTTP UI/API on :8025, MP_MAX_MESSAGES=5000 - pkg/email/sender.go: provider-agnostic Sender interface + Message struct - pkg/email/smtp_sender.go: SMTPSender implementation (net/smtp), with Mailpit-friendly defaults (localhost:1025, no TLS, no AUTH), context- aware Send with timeout, supports plain text and multipart/alternative - pkg/config: AuthConfig.Email field + EmailConfig struct + GetEmailConfig getter + 7 new env vars (DLC_AUTH_EMAIL_*) + defaults - documentation/EMAIL.md: setup, inspection (UI + API), code examples, cross-refs to ADR-0028/0029/0030 Tests (pkg/email/smtp_sender_test.go): - validateMessage rejects missing fields, accepts minimal - buildRFC5322 plain-text path produces single-part text/plain with expected headers - buildRFC5322 multipart path produces multipart/alternative with both parts and a closing boundary - buildRFC5322 custom headers are canonicalised (lowercase keys → Title-Case) - NewSMTPSender defaults are Mailpit-friendly - Send respects context cancellation (no 10s wait when ctx cancelled) Race detector clean. Build clean. Vet clean. Out of scope for this PR (Phase A.2+): - BDD email-steps helper package (pkg/bdd/mailpit/, pkg/bdd/steps/email_steps.go) - magic_link_tokens table + repository - magic-link/request and magic-link/consume HTTP handlers - BDD scenarios for the magic-link flow
46 lines
1.6 KiB
Go
46 lines
1.6 KiB
Go
// Package email provides the abstraction over outgoing email transport.
|
|
//
|
|
// ADR-0029 picked Mailpit for local dev and BDD ; production sender is
|
|
// deferred. The Sender interface is the swap point : a future production
|
|
// adapter (AWS SES, Postmark, SendGrid) implements the same contract
|
|
// without touching call sites.
|
|
package email
|
|
|
|
import "context"
|
|
|
|
// Sender sends email messages. Implementations must be safe for
|
|
// concurrent use — multiple goroutines may call Send simultaneously.
|
|
type Sender interface {
|
|
Send(ctx context.Context, msg Message) error
|
|
}
|
|
|
|
// Message is the wire-level representation of an outgoing email.
|
|
// Headers is for trace correlation (e.g. X-Test-Scenario-ID for BDD)
|
|
// and arbitrary application-specific tags. Implementations include
|
|
// these as RFC 5322 header fields.
|
|
type Message struct {
|
|
// To is the recipient address (single recipient ; we don't currently
|
|
// support multi-recipient broadcasts — keeps the contract simple
|
|
// and matches the magic-link use case which is always 1:1).
|
|
To string
|
|
|
|
// From is the sender address. Required.
|
|
From string
|
|
|
|
// Subject is the RFC 5322 Subject. Required for non-empty body.
|
|
Subject string
|
|
|
|
// BodyText is the plain-text body. At least one of BodyText or
|
|
// BodyHTML must be non-empty.
|
|
BodyText string
|
|
|
|
// BodyHTML is the optional HTML body. When both are set, the
|
|
// SMTP-level message is multipart/alternative.
|
|
BodyHTML string
|
|
|
|
// Headers are extra RFC 5322 header fields. Keys are case-insensitive ;
|
|
// implementations canonicalise via textproto.CanonicalMIMEHeaderKey.
|
|
// Useful for BDD test correlation (X-BDD-Scenario, etc.).
|
|
Headers map[string]string
|
|
}
|