AZ-008 — Email integration and Mocking Bird capture
Status
Done
Context
Read README, especially the selected Resend HTTPS adapter, Mocking Bird limits, deployment classification and safe logging. No external runtime integrations exist. Email will serve password recovery in AZ-010; this increment proves delivery boundaries using a constrained local test-email operation and controlled HTTP tests, without waiting for recovery functionality. AZ-005 supplies authenticated users, CSRF and local/test versus hosted configuration.
Goal
Deliver one concrete email integration with bounded failures and an opt-in local interception capability that records sanitized outbound requests without contacting the real provider.
Requirements
- Define a narrow backend application email capability for sender-configured transactional mail: recipient, subject, text and optional safe HTML, with optional internal correlation context. Application code knows delivery success/failure categories, not Resend JSON, API keys or fetch. No abstract integration registry/plugin framework or all-provider base class.
- Add provider-specific adapter code within backend infrastructure responsibilities, documented
under a small new
backend/src/integrations/area if appropriate; do not introduce a top-level package.server.tswires the real adapter or controlled local transport. Resend RESTPOST https://api.resend.com/emailsmaps fields/auth and checks success/error bodies, using Node 24 fetch. Verify the live official API documentation before implementation; provider assumptions live in this adapter and focused tests. - Introduce validated server-only mail mode/config:
MAIL_MODE=disabled|mock|real,MAIL_FROM,RESEND_API_KEY, configured request timeout (initial 10 seconds), andMOCKING_BIRD_ENABLED=falseby default. Real mode requires sender/key; no example key in.env.example, browser config or images. Disabled is explicit unavailability, never success. Mock mode requires local/test deployment classification and explicit Mocking Bird enablement. Staging/production reject mock/developer enablement; disabled is allowed until AZ-010 makes recovery/mail a required hosted feature. No startup live email/connectivity check. - Provider destination is fixed/allowlisted in real deployment; do not accept URL/headers/sender from browser requests. Tests may inject a controlled destination/transport without creating a runtime arbitrary outbound proxy. Never forward user cookies/auth headers to the provider.
- Use one explicit injected outbound HTTP transport for this adapter, with bounded timeout and response body size, cancellation, and safe classified failures (rejected, throttled, timeout, unavailable, malformed response). Do not patch global fetch or intercept unrelated DB/browser calls. No blind retry of a possibly delivered email; automatic retries require provider-supported idempotency and explicit future requirements. Sanitize third-party errors before logging.
- Start Mocking Bird at this transport seam: in-memory capped ring (initial 100 requests), fixed success/provider-error/timeout scenarios, bounded optional latency (0–5000ms), and sanitized capture of method, allowlisted destination/path, content type, redacted header names, safe operation summary, outcome, duration and correlation ID. Never store Authorization, Cookie, provider keys, passwords, arbitrary body, recipient address or token-bearing URLs in general history. Payload summaries identify mail kind/size, not content.
- In mock mode all registered mail requests are intercepted before network; unmatched or unsafe requests fail closed. No automatic real fallthrough. Mode is startup configuration, not a UI production switch. Scenarios are data fixtures/enums, not executable user code.
- A separate bounded local synthetic-mail preview may retain recipient and rendered text/reset link for development and tests. Restrict to synthetic fixtures created by guarded test tooling or explicitly configured local test recipients. Do not capture real-recipient mail bodies, expose preview token URLs in logs/history, or upload them as CI artifacts. Clear preview/history at startup/reset and on test teardown; no disk/DB history or replay engine.
- Add a small authenticated, CSRF-protected, rate-limited local/test-only
POST /api/developer/email-testreturning 202 on mock/real acceptance or a safe error. Accept no arbitrary recipient/body/URL/header input: send a fixed harmless message to the current user's explicitly allowlisted synthetic fixture address. For a real-mode manual test, require explicit local allowlist/config and owner-authorized send; automated tests never send real mail. Disabled developer mode means route absent/404, not only menu hidden. Production cannot register this route even if a frontend flag is enabled. - Document developer API operations in OpenAPI with
x-deployment-environments: [local, test]and explicit enablement requirement. Extend route-surface tests to compare enabled/disabled app variants against that metadata, preserving exact matching for normal public operations. Await Fastify ready before observing async plugin routes; do not blanket-ignore developer routes. - Test real adapter serialization/auth/status/body/timeout with a controlled HTTP server or transport, separately from Mocking Bird fixtures. Test mock no-network guarantee using a transport spy that would fail if called; caps, clearing/redaction and cancellation are tested. Add structured safe events using AZ-004; request context is internal metadata, not raw mail.
Non-goals
No provider signup/subscription/domain configuration/live send, payments/POS, SMTP, multiple vendors, generic integration framework, developer UI (AZ-009), recovery tokens (AZ-010), request scripting, arbitrary proxy URLs, persisted replay or hosted mock mode.
Implementation guidance
Keep application email contract independent of HTTP; only integration adapter/transport uses vendor
JSON and headers. Separate safe capture projection from raw request so redaction is enforced before
history storage. Limit preview separately and do not turn an adapter unit-test injection into a
configurable production backdoor. The test-email endpoint is a concrete proof and must be guarded
with backend deployment config, authentication, CSRF and synthetic-recipient validation together. Do
not change the main db:seed or make recovery's mail provider a database concern.
Acceptance criteria
- Controlled HTTP tests verify real Resend request/response/failure mapping and bounded timeout.
- Mock scenarios/capture work without any real network; unmatched requests fail closed.
- History is bounded/redacted before storage; synthetic previews are separate and local-only.
- Auth/CSRF/local enablement/recipient constraints protect the proof endpoint; route is absent in disabled and staging/production variants, regardless of UI.
- Mail disabled/mock/real configuration fails clearly when inconsistent, without logging keys.
- OpenAPI variant surface and normal route tests remain exact; application knows no vendor API.
Verification
pnpm quality
pnpm --filter @foodtruck-order/backend test:unit
pnpm --filter @foodtruck-order/backend test:coverage
pnpm build
pnpm validate:docker
Backend aggregate coverage uses PostgreSQL. Add transport/adapter/capture/config/injection tests; integration tests exercise the local proof operation through real user/session persistence and mocked external mail only. Update canonical CI local-test configuration explicitly; do not enable Mocking Bird in hosted migration jobs or defaults. Manually inspect a synthetic request through the protected local API and confirm secrets are absent from structured logs. No real send is needed for implementation acceptance; deployed delivery evidence remains a later release check.
Documentation
Update architecture (integration/transport boundary), techstack (native fetch and Resend rationale), development (mode/config/synthetic recipient/test mail), Docker/CI (explicit local test enablement), testing (client versus mock tests), OpenAPI, current-state/handover. Record an integration ADR with HTTPS-versus-SMTP rationale and external provider prerequisites.
Dependencies
AZ-005 and its predecessors. Can proceed beside AZ-006/AZ-007; there is no UI dependency.
Future considerations
An actual POS/payment adapter may reuse proven timeout/redaction conventions when needed, without assuming email's retry/idempotency semantics or adding unused generic interfaces now.