feat: phase (a) part 1 - outbox, providers, SMTP and SES, messaging bridge #6

Merged
KofTwentyTwo merged 2 commits from feature/phase-a-outbox into develop 2026-10-14 12:09:09 +00:00
KofTwentyTwo commented 2026-10-14 01:18:39 +00:00 (Migrated from github.com)

Phase (a) part 1 of docs/DESIGN.md (sections 2, 3.1–3.4, 4, 9, 13, 14).

  • Tables (email_ prefix, snake_case columns, Postgres DDL in docs/ddl/email-postgres.sql): provider, message, message_attempt, template, template_version, brand, suppression. Nav: Messages, Templates, Brands, Suppressions, Providers; attempts and versions are child lists. Message and attempt rows are system-written.
  • Providers: env-var-name credentials (regex + allowlist), one enabled default, SMTP host/port, from-domain check; the record view shows "Set"/"Not set" per named variable, never values.
  • EmailService: address parsing, provider resolution, idempotency keys (race-safe), suppression rules (ALL, category, expiry, partial), same-transaction enqueue or after-commit across backends, linked record, sender, send-after. Ad-hoc Markdown/HTML for now; templates arrive in part 2 behind the EmailRenderer seam.
  • Dispatcher: claim by unique (message, attempt) insert, 120 s lease, retry backoff with capped retry-after, permanent → dead letter, max attempts, per-minute/per-day limits from the database, provider health with probes and recovery, optional core alerts. Logs carry message id and status only.
  • Transports: SMTP (jakarta.mail, auth, STARTTLS/TLS, 10/15/15 s timeouts, 5xx permanent vs 4xx/connection transient) and SES (AWS SDK v2 sesv2 raw MIME, default chain or named env vars).
  • Bridge: QBIT_EMAIL messaging provider (qbitEmail) so SendMessageAction callers queue through the outbox.
  • Actions: Resend, Cancel, Send Now (messages), Send Test Email (providers, admin) with the standard Review/Result; email_send_templated process; hidden scheduled dispatch, maintenance and retention.
  • Permissions: email.view, email.send, email.manage, email.admin.

Deviation: RC.5's alert-type name pattern forbids underscores, so the dead-letter alert is email.message.dead-letter.

Tests: 33 (GreenMail SMTP end to end with auth on/off, SES with an injected client, two-replica claim race, retries/lease/dead letter, idempotency race, suppression, rate limits, bridge through SendMessageAction, record actions, retention, permissions, producer standards). ./mvnw verify green: Checkstyle clean, JaCoCo 93.4% instructions / 100% classes.

🤖 Generated with Claude Code

Phase (a) part 1 of `docs/DESIGN.md` (sections 2, 3.1–3.4, 4, 9, 13, 14). - **Tables** (`email_` prefix, snake_case columns, Postgres DDL in `docs/ddl/email-postgres.sql`): provider, message, message_attempt, template, template_version, brand, suppression. Nav: Messages, Templates, Brands, Suppressions, Providers; attempts and versions are child lists. Message and attempt rows are system-written. - **Providers:** env-var-name credentials (regex + allowlist), one enabled default, SMTP host/port, from-domain check; the record view shows "Set"/"Not set" per named variable, never values. - **EmailService:** address parsing, provider resolution, idempotency keys (race-safe), suppression rules (ALL, category, expiry, partial), same-transaction enqueue or after-commit across backends, linked record, sender, send-after. Ad-hoc Markdown/HTML for now; templates arrive in part 2 behind the `EmailRenderer` seam. - **Dispatcher:** claim by unique (message, attempt) insert, 120 s lease, retry backoff with capped retry-after, permanent → dead letter, max attempts, per-minute/per-day limits from the database, provider health with probes and recovery, optional core alerts. Logs carry message id and status only. - **Transports:** SMTP (jakarta.mail, auth, STARTTLS/TLS, 10/15/15 s timeouts, 5xx permanent vs 4xx/connection transient) and SES (AWS SDK v2 `sesv2` raw MIME, default chain or named env vars). - **Bridge:** `QBIT_EMAIL` messaging provider (`qbitEmail`) so `SendMessageAction` callers queue through the outbox. - **Actions:** Resend, Cancel, Send Now (messages), Send Test Email (providers, admin) with the standard Review/Result; `email_send_templated` process; hidden scheduled dispatch, maintenance and retention. - **Permissions:** `email.view`, `email.send`, `email.manage`, `email.admin`. Deviation: RC.5's alert-type name pattern forbids underscores, so the dead-letter alert is `email.message.dead-letter`. Tests: 33 (GreenMail SMTP end to end with auth on/off, SES with an injected client, two-replica claim race, retries/lease/dead letter, idempotency race, suppression, rate limits, bridge through `SendMessageAction`, record actions, retention, permissions, producer standards). `./mvnw verify` green: Checkstyle clean, JaCoCo 93.4% instructions / 100% classes. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.