Adds a real :25 SMTP listener to the Go protocols service, giving F1's
"Nucleo SMTP" milestone its first working sub-project: application,
infrastructure, and presentation layers on top of F0's domain-only
Clean Architecture.
- ProcessInboundEmailUseCase resolves recipients against Postgres and
persists accepted mail (blob dedup by content hash, transactional
UID allocation + outbox event, never sending 250 OK before durable
commit per PLAN.md section 6.1).
- Postgres repositories via pgx/v5 (replacing lib/pq entirely).
- Local-disk blob storage with content-hash dedup, rename-before-commit
ordering so a DB row can never reference a file that isn't actually
on disk.
- A local-file TLS cert provider for STARTTLS, with an ephemeral
self-signed fallback so a missing/misconfigured cert degrades
gracefully instead of crash-looping the service (PLAN.md section 8.4).
- A github.com/emersion/go-smtp Backend/Session adapting real SMTP
traffic to the use case: unknown recipients get 550 5.1.1 before
DATA, duplicate RCPT TO for the same address is deduplicated, TLS is
hardened to the project's required PFS-only cipher/curve policy.
- A dev-seed script and Makefile target for local testing.
Explicitly out of scope (separate follow-up sub-projects): alias
resolution, SPF/DKIM/DMARC/ARC verification, Rspamd/ClamAV, Submission
ports with SASL auth, DKIM signing, outbound delivery, and the real
Traefik acme.json cert watcher.
Verified live end-to-end via WSL Docker: the full compose stack (db,
redis, migrate, auth, storage, protocols, web) reaches healthy, and a
real swaks SMTP session is accepted and durably persisted with correct
sequential UIDs, while an unknown recipient is correctly rejected with
550 5.1.1 and zero rows persisted. Built via 10 sequential tasks under
subagent-driven development, each independently reviewed, plus a final
whole-branch review that caught and fixed a TLS-crash bug, a blob
commit-ordering bug, missing TLS hardening, and missing CI integration
coverage before merge.
Known follow-up, not blocking: email_messages' partition only covers
July 2026 and needs a partition-management job before 2026-08-01.