Notifications & Comms
What this covers / who it's for. How the platform talks to client users: the in-app inbox, email, consent and preferences, what always gets through regardless of preferences, and the durable record of every send. Delivery mechanics live in Communications delivery; specifics in the notification module README.
Two channels, two timing guarantees
Every durable client notification goes to the in-app inbox and email, with deliberately different timing:
| Channel | When it's written | Why |
|---|---|---|
| Inbox | Instantly, inside the same transaction as the action that caused it | The inbox never shows a notification for something that didn't happen — if the action rolls back, so does the notification. |
| Queued, and dispatched only after the action commits | An email can never announce something that was rolled back; a queue backlog delays email, never the inbox. |
Product consequence: the inbox is the source of truth for "what happened to me"; email is the reach-out. If email is slow or down, the inbox is already correct.
Consent & preferences
Users control their comms per channel:
- Per-channel toggles — inbox and email can each be switched off.
- Re-engagement opt-in — marketing-style re-engagement notices are sent only to users who opted in.
What bypasses opt-out — by design. Two categories always get through:
| Category | Examples | Why it bypasses |
|---|---|---|
| Compliance notices | Travel-rule RFIs (deposit holds), freeze notices | Regulatory/action-required — a user cannot opt out of being told their deposit will be held. |
| Security messages | OTPs, verification codes, password-reset links, step-up codes | The account is unusable without them. These are ephemeral: email-only, never stored in the inbox, never logged — a one-time code has no audit value and shouldn't persist anywhere. |
Actionable notifications — CTAs as data
A notification can carry calls to action as structured data, not prose: a label plus a typed target — an allow-listed URL, a named in-app route, or an async-request id (so "your export is ready" can point the UI straight at the poll endpoint). The front end renders real buttons; the API stays presentation-free.
The durable comms record
Every non-ephemeral send is recorded in a per-tenant comms log — which notification, to whom, over which channel, when. This is the answer to "did we actually tell the customer?" — support and compliance can prove a send, not just assert one. Compliance-relevant comms are additionally tied into the audit trail via the triggering action's audit record.
Retention prune ships inert — deliberately. The pruning mechanism for the comms log exists and is scheduled, but its retention configuration ships empty, so nothing is pruned today; compliance-notice comms are additionally clamped to the 7-year retention floor. Turning pruning on is a legal sign-off decision, not a code change (per the wiki's ship-state honesty rule; see Data lifecycle guarantees).
Operator notifications
A separate, cross-tenant operator notification store exists (platform-wide notices, operator queues), but has no read surface yet — it becomes useful when the operator backoffice UI is rebuilt.