99 lines
4.9 KiB
Markdown
99 lines
4.9 KiB
Markdown
# Notification support matrix
|
|
|
|
What each channel can actually prove, and what the platform refuses to claim.
|
|
|
|
The grade column is not an opinion. `docs/notification/evidence-manifest.json` declares which claims
|
|
each grade requires and which executable artifact proves each claim, and
|
|
`./gradlew verifyNotificationEvidence` refuses a grade whose claims are not all backed by a file that
|
|
exists. Raising a grade means adding the artifact first.
|
|
|
|
Five channels read `Stable` until 2026-08-15. Nothing in the pipeline had ever sent a request to a
|
|
provider — the sandbox job's whole body was two `echo` statements behind `continue-on-error` — and
|
|
no test killed a worker mid-dispatch. The protocols are implemented and their contracts are proven
|
|
against real PostgreSQL; the wire and the crash are not. That is what the grade now says.
|
|
|
|
## Channels
|
|
|
|
| Channel | Reference implementation | Grade | Strongest evidence the platform records by default |
|
|
|---|---|---|---|
|
|
| Email | SMTP, Amazon SES API | Contract implemented / runtime unqualified | Provider acceptance; recipient mail-server delivery, bounce and complaint when the provider publishes events |
|
|
| SMS | Twilio Programmable Messaging | Contract implemented / runtime unqualified | `accepted`/`queued`, `sent`, and carrier-DLR `delivered`/`undelivered` |
|
|
| Mobile push (Android and cross-platform) | FCM, FID-first with legacy registration token compatibility | Contract implemented / runtime unqualified | FCM acceptance and explicit failures |
|
|
| Mobile push (Apple) | APNs HTTP/2 provider API | Contract implemented / runtime unqualified | APNs acceptance |
|
|
| Web Push | RFC 8030, RFC 8291, RFC 8292 | Contract implemented / runtime unqualified | Push-service acceptance; user-agent acknowledgement only where the service offers receipts |
|
|
| In-app inbox | Own database | Optional stable | `PERSISTED`, `SEEN`, `READ` |
|
|
| Webhook | HTTP client platform | Extension | Whatever the receiving HTTP contract states |
|
|
|
|
## What each grade requires
|
|
|
|
| Grade | Requires |
|
|
|---|---|
|
|
| Stable | durable, multi-worker-safe, callback-supported, recoverable, provider-wire-qualified |
|
|
| Contract implemented / runtime unqualified | durable, multi-worker-safe, callback-supported |
|
|
| Optional stable | durable |
|
|
| Extension | nothing; the receiving contract owns its own guarantees |
|
|
| Experimental | nothing; the grade is the warning |
|
|
|
|
`recoverable` was met on 2026-08-15 by `WorkerCrashRecoveryContractTest`, which walks the four
|
|
phases a dispatch passes through — claimed, attempt written, request started, body committed — and
|
|
asserts for each that the delivery becomes claimable again or becomes a question for the provider,
|
|
never both and never neither.
|
|
|
|
The remaining unmet claim, and what would meet it:
|
|
|
|
- **provider-wire-qualified** — a real provider sandbox call producing an immutable, uploaded
|
|
artifact with a correlation id. No request has ever left the process in CI.
|
|
|
|
## Evidence levels
|
|
|
|
`NONE` → `PLATFORM_QUEUED` → `PROVIDER_ACCEPTED` → `NETWORK_OR_CARRIER_ACCEPTED` →
|
|
`DEVICE_DELIVERED` → `USER_AGENT_DISPLAYED` → `USER_READ`
|
|
|
|
| Provider signal | Highest evidence it may produce |
|
|
|---|---|
|
|
| Internal queue commit | `PLATFORM_QUEUED` |
|
|
| SES `MessageId` | `PROVIDER_ACCEPTED` |
|
|
| SES `Delivery` | `NETWORK_OR_CARRIER_ACCEPTED` |
|
|
| Twilio `accepted` / `queued` | `PROVIDER_ACCEPTED` |
|
|
| Twilio `sent` | `NETWORK_OR_CARRIER_ACCEPTED` |
|
|
| Twilio `delivered` | `DEVICE_DELIVERED` |
|
|
| FCM send success | `PROVIDER_ACCEPTED` |
|
|
| APNs 2xx | `PROVIDER_ACCEPTED` |
|
|
| Web Push `201` | `PROVIDER_ACCEPTED` |
|
|
| Web Push receipt capability | `DEVICE_DELIVERED` |
|
|
| In-app row commit | `PROVIDER_ACCEPTED` |
|
|
| In-app `seen` endpoint | `USER_AGENT_DISPLAYED` |
|
|
| In-app `read` endpoint, authenticated app receipt | `USER_READ` |
|
|
|
|
Promotions the platform will not make, in code or in configuration:
|
|
|
|
- FCM send success is not `DEVICE_DELIVERED`.
|
|
- An APNs 2xx is not `DELIVERED`.
|
|
- An SES `MessageId` is not `DELIVERED`.
|
|
- An SMTP `250` is not inbox delivery.
|
|
|
|
## Submission outcomes
|
|
|
|
`NOT_SUBMITTED`, `CONFIRMED_ACCEPTED`, `CONFIRMED_REJECTED`, `AMBIGUOUS`.
|
|
|
|
`AMBIGUOUS` is a first-class stored state, not an error path. It means the request body was committed
|
|
to the provider and the outcome could not be read. While an ambiguous attempt exists on a recipient
|
|
delivery, automatic retry and automatic cross-channel fallback are both blocked.
|
|
|
|
## Not supported
|
|
|
|
The platform will not claim any of the following, because no channel above can support them:
|
|
|
|
- guaranteed delivery
|
|
- guaranteed read
|
|
- exactly-once human notification
|
|
- unconditional multi-provider failover after an unread response
|
|
- provider SDK types in the public API
|
|
- audience selection, campaign segmentation or jurisdiction rulings
|
|
|
|
## Target model
|
|
|
|
`FCM_FID` is the primary mobile push target. `FCM_REGISTRATION_TOKEN_LEGACY` and
|
|
`APNS_DEVICE_TOKEN` are separate types with separate lifecycles; they are never flattened into one
|
|
string field.
|