Maps the 31-module plan onto the registry's 19 leaves as packages; the two edges the registry forbids (provider->httpclient, inbox->messaging) are replaced by application-owned ports. See docs/notification/module-mapping.md. Acceptance is not delivery: ProviderSubmissionResult refuses to carry a delivery outcome, and AMBIGUOUS is a first-class terminal state that blocks automatic retry and fallback until reconciliation resolves it. Providers: SES (SigV4 + SNS callback), Twilio (X-Twilio-Signature + reconciliation), FCM (FID-primary batch), APNs, Web Push (RFC 8030/8291/8292), SMTP and webhook. Contact points are AES-256-GCM encrypted with a separate HMAC lookup fingerprint; nothing raw reaches a log, metric tag or exception. Dispatch commits the attempt row, calls the provider with no transaction open, then records the outcome; the durable queue uses FOR UPDATE SKIP LOCKED. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
28 lines
1.2 KiB
Markdown
28 lines
1.2 KiB
Markdown
# NOTIF-ADR-001 — `submit()` means durable acceptance
|
|
|
|
## Status
|
|
|
|
Accepted.
|
|
|
|
## Context
|
|
|
|
The obvious API for a notification platform is `send()` returning success or failure. Every channel
|
|
this platform supports makes that return value a lie:
|
|
|
|
- SES accepts a request, returns a `MessageId`, and can still decline to send.
|
|
- Twilio separates `accepted`, `sent` and `delivered` into distinct, later events.
|
|
- APNs accepts a notification and may then deliver, store or discard it.
|
|
- Web Push separates push-service acceptance from user-agent acknowledgement at the protocol level.
|
|
|
|
## Decision
|
|
|
|
`submit()` and `schedule()` return once the logical request and its recipient jobs are committed to
|
|
the database. The receipt carries `notificationId`, `RequestStatus` and `acceptedAt`, and has no
|
|
`delivered`, `sent` or `read` component. No provider is contacted while the transaction is open.
|
|
|
|
## Consequences
|
|
|
|
Callers cannot mistake acceptance for delivery, because the type does not offer that reading.
|
|
Delivery state is a separate query against the projection built from the provider event ledger. The
|
|
cost is that "did it arrive?" is a second question — which is the honest number of questions.
|