Skip to content

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_MESSAGE Notification 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

IDEntityRole
ENT-026NotificationPer-user inbox row — read/unread, typed, with payload
ENT-024MessageBroadcast initiated by a company member, with a target type (ALL / ACTIVITY / SESSION / MANUAL)
ENT-025Message recipientOne row per recipient of a broadcast — fans the Message out into Notifications
ENT-023Device tokenA 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 to users.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 an activity_id or session_id depending on target_type (no DB FK, see ADR).

Open questions

  • Two delivery pipelines (per-event + broadcast) share the same Notification row shape — keeping the type enum healthy is important.
  • OPEN: push delivery success/failure status per device — is it persisted on Device token or in a separate adapter log?