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.
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.
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)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Phase (a) part 1 of
docs/DESIGN.md(sections 2, 3.1–3.4, 4, 9, 13, 14).email_prefix, snake_case columns, Postgres DDL indocs/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.EmailRendererseam.sesv2raw MIME, default chain or named env vars).QBIT_EMAILmessaging provider (qbitEmail) soSendMessageActioncallers queue through the outbox.email_send_templatedprocess; hidden scheduled dispatch, maintenance and retention.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 verifygreen: Checkstyle clean, JaCoCo 93.4% instructions / 100% classes.🤖 Generated with Claude Code