Files
dance-lessons-coach/documentation/EMAIL.md
Gabriel Radureau b3027d2669
All checks were successful
CI/CD Pipeline / Build Docker Cache (push) Successful in 11s
CI/CD Pipeline / CI Pipeline (push) Successful in 5m23s
CI/CD Pipeline / Trigger Docker Push (push) Successful in 5s
feat(bdd): pkg/bdd/mailpit/ HTTP client + integration tests (ADR-0030 Phase A.2) (#60)
Co-authored-by: Gabriel Radureau <arcodange@gmail.com>
Co-committed-by: Gabriel Radureau <arcodange@gmail.com>
2026-05-05 10:51:33 +02:00

108 lines
3.5 KiB
Markdown

# Email infrastructure
Outgoing email transport. Per [ADR-0029](../adr/0029-email-infrastructure-mailpit.md): Mailpit for local dev + BDD tests, production sender deferred.
## Local setup (one-time)
Mailpit is part of `docker-compose.yml`:
```bash
docker compose up -d # starts postgres + mailpit
docker compose ps # confirm both running
```
Mailpit listens on:
- **SMTP submission** — `localhost:1025` (the app sends here)
- **HTTP UI / API** — http://localhost:8025 (you inspect captured messages here)
No real emails leave the docker network. No internet required.
## Application configuration
The application's outgoing transport is configured under `auth.email.*` in `config.yaml` (or via `DLC_AUTH_EMAIL_*` env vars). Defaults already match local Mailpit:
```yaml
auth:
email:
from: noreply@dance-lessons-coach.local
smtp_host: localhost
smtp_port: 1025
smtp_use_tls: false
timeout: 10s
# smtp_username + smtp_password left empty for local Mailpit
```
For production, override these to point at the chosen provider (SES, Postmark, etc.).
## Inspecting messages
### Web UI
http://localhost:8025 — list of all captured messages, search, raw view, HTML preview.
### HTTP API (for automation)
```bash
# Latest 10 messages (no filter — /api/v1/messages is for pagination)
curl -s 'http://localhost:8025/api/v1/messages?limit=10' | jq
# Messages for a specific recipient — use /api/v1/search, NOT /messages
# (the latter's `query` param is for pagination only, not filtering ;
# verified empirically 2026-05-05)
curl -s 'http://localhost:8025/api/v1/search?query=to:test-user@bdd.local' | jq
# Get a specific message by ID (full content, headers, attachments)
curl -s 'http://localhost:8025/api/v1/message/<id>' | jq
# Purge messages for a recipient (used in test cleanup) — also via /search
curl -X DELETE 'http://localhost:8025/api/v1/search?query=to:test-user@bdd.local'
```
Full API: https://mailpit.axllent.org/docs/api-v1/
## Sending email from Go code
```go
import "dance-lessons-coach/pkg/email"
sender := email.NewSMTPSender(email.SMTPConfig{
Host: cfg.GetEmailConfig().SMTPHost,
Port: cfg.GetEmailConfig().SMTPPort,
// username/password optional — empty means no AUTH (Mailpit local)
})
err := sender.Send(ctx, email.Message{
To: "alice@example.com",
From: cfg.GetEmailConfig().From,
Subject: "Your magic link",
BodyText: "Click: https://example.com/magic-link/consume?token=...",
Headers: map[string]string{
// optional — useful for BDD test correlation
"X-Trace-Id": "req-abc-123",
},
})
```
Or, when both text and HTML are needed (`multipart/alternative`):
```go
err := sender.Send(ctx, email.Message{
To: "alice@example.com", From: "...", Subject: "...",
BodyText: "Click: https://...",
BodyHTML: `<p>Click <a href="https://...">your magic link</a></p>`,
})
```
## Production sender (TBD)
Not chosen yet. When ready, implement another `email.Sender` in
`pkg/email/<provider>_sender.go` and wire it via the config. The
`Sender` interface is the swap point — call sites don't change.
## Cross-references
- [ADR-0028 — Passwordless auth migration](../adr/0028-passwordless-auth-migration.md) (consumes this infrastructure)
- [ADR-0029 — Email infrastructure decision](../adr/0029-email-infrastructure-mailpit.md)
- [ADR-0030 — BDD email parallel strategy](../adr/0030-bdd-email-parallel-strategy.md)
- [Mailpit docs](https://mailpit.axllent.org/docs/)