✨ feat(email): pkg/email + Mailpit docker-compose service (ADR-0029 Phase A.1)
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
This commit is contained in:
45
pkg/email/sender.go
Normal file
45
pkg/email/sender.go
Normal file
@@ -0,0 +1,45 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user