E1 — The notification row IS the outbox.notification.emailed_at stamps delivery; the
scheduler tick (advisory-locked) delivers unstamped rows through the channel interface.
At-least-once, crash-safe, no SMTP inside domain transactions — forced by construction:
notifications are born inside SQL (fan-out and sweep DEFINER functions).
E2 — Structured, localized messages.notification.params (jsonb, e.g. the human document
id) + app_user.locale; per-type templates render at display/delivery time in the recipient's
locale — the inbox renders from type+params (the message column becomes fallback for legacy
rows), so in-app and email translate consistently. Producers (V-functions, sweep) populate
params instead of composing English prose.
E3 — quarkus-mailer, config per deployment; lqms.notifications.email.enabled=false by
default — absent email config never impairs in-app delivery (REQ-NOT-007). Tests via
MockMailbox.
E4 — Everything is emailed when the channel is enabled; per-user preferences/digests are a
later user-facing slice.
NotificationChannel interface in the notification module; EmailChannel the first
implementation; further channels (webhook/chat) plug into the same delivery pass.
Failures leave emailed_at null → retried next tick; a poison message must not starve the pass
(per-row try/catch, log-and-continue).
Recipient email comes from app_user.email (rectifiable via IdP refresh, REQ-DPR-009).
Locale defaults to the deployment default (lqms.notifications.default-locale=en); a user-facing
locale setting joins the later preferences slice.