Notifications
Purpose
Two distinct delivery pipelines living side by side:
- User inbox: every per-user signal (booking reminder, pass expiring, refund settled, broadcast received) becomes a Notification row. Read/unread state is tracked per user.
- Broadcasts: a company member writes a Message (one row), the system fans it out to a set of recipients (Message recipient — one row per recipient), and each fan-out creates a
BROADCAST_MESSAGENotification in the recipient’s inbox.
Push delivery uses Device token — one row per device per user.
Boundaries
- In: the inbox, the broadcast pipeline, push delivery tokens.
- Out:
- What triggers a notification (a booking confirmation, a pass expiry, a refund settlement) — those events live in their own contexts. Notifications is downstream.
- Sending the push / SMS / email — the transport adapter is implementation detail of this module; the contract is “create a Notification row, transport happens after”.
Entities
| ID | Entity | Role |
|---|---|---|
| ENT-026 | Notification | Per-user inbox row — read/unread, typed, with payload |
| ENT-024 | Message | Broadcast initiated by a company member, with a target type (ALL / ACTIVITY / SESSION / MANUAL) |
| ENT-025 | Message recipient | One row per recipient of a broadcast — fans the Message out into Notifications |
| ENT-023 | Device token | A device’s push token, scoped to a user |
Backend implementation
- Module:
libs/features/notifications/ - Surface routing: notifications visible to client (own inbox) and business (broadcast composer + delivery stats); device tokens client-only.
- Drizzle schema:
libs/shared/data-access-db/src/lib/schema/notifications.schema.ts(pgSchema('notifications')).
Cross-context relationships
- This context owns the
notifications.*Postgres schema. - This context references:
- Identity via
device_token.user_id,notification.user_id,message.sent_by,message_recipient.user_id— FKs tousers.users.id(cross-schema FK declared, ON DELETE CASCADE). - Companies via
message.company_id(cross-schema FK, ON DELETE CASCADE). - Polymorphically via
message.target_id— points at anactivity_idorsession_iddepending ontarget_type(no DB FK, see ADR).
- Identity via
Open questions
- Two delivery pipelines (per-event + broadcast) share the same Notification row shape — keeping the
typeenum healthy is important. - OPEN: push delivery success/failure status per device — is it persisted on Device token or in a separate adapter log?