> ## Documentation Index
> Fetch the complete documentation index at: https://internal.softcrum.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Standard — Notifications (NEW)

> email · in_app · webhook · push · sms (adapter F1b behind provider ADR) · whatsapp (F2) · live_activity (F2). The channel row and the NotificationChannelPort exist from day 1 even when the adapter arrives later.

## Channel catalog (`core.notification_channels`, parametric)

email · in\_app · webhook · push · sms (adapter F1b behind provider ADR) · whatsapp (F2) · live\_activity (F2). The channel row and the NotificationChannelPort exist from day 1 even when the adapter arrives later.

## Cascading resolution (levels 0–3 + recipient)

`resolveChannels(event, tenant, module, recipient)` — PURE function in packages/core, unit-tested:
0\. Platform: adapter exists and is operative (Vercel Flags operational flag, mirrored in `messaging.platform_channel_status`).

1. Tenant: channel active for this tenant (entitlement + configured credentials/sender domain) — `messaging.tenant_channel_settings`.
2. Module: channel enabled for the emitting module in this tenant — `messaging.module_channel_settings`.
3. Event type: routing entry (channels, per-channel template, category) — `messaging.event_channel_routing`.
   Recipient gate: consent for the channel ∧ not in `messaging.suppressions` ∧ preference center ∧ (marketing only: quiet hours + frequency caps).
   Result: one job per resolved channel onto `notif.{channel}.{rail}` (standards/jobs.md).

## Categories & recipient controls

* Category per event routing: `transactional | marketing | product`. Transactional is NO-opt-out (only suppression by hard bounce).
* Member preference center: per channel AND per category. Quiet hours: tenant default, campaign override. Frequency caps: marketing only.

## Traceability (end-to-end)

* Chain: origin event → rule/campaign → notification job → `messaging.sends` → `messaging.send_status_history` (queued→sent→delivered→opened→clicked / bounced / complained / failed) → outgoing webhooks. All linked by `correlation_id` + `origin_event_id`.
* Provider status ingestion via provider webhooks (Resend today); each future channel ships its status adapter.
* Hard bounces and complaints write to `messaging.suppressions` automatically. EVERY send checks suppressions — no exceptions.

## Rails & fairness

Transactional rail always outranks marketing (jobs.md). Token buckets per provider (global) and per tenant. Blasts: batch endpoints only, throttled; a tenant's blast MUST NOT degrade another tenant's OTP latency (this is the Resend shared-limit mitigation, generalized to all channels).
