Files
clean-architecture-backend-…/docs/notification/adr/NOTIF-ADR-001-durable-acceptance.md
T
DongHyeonkaandClaude Opus 5 701ba67456 feat(notification): implement the notification delivery platform
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>
2026-08-14 13:57:27 +09:00

1.2 KiB

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.