Workflow: Notification Dispatch

Technical reference for NIVAA's notification dispatch architecture. This covers both inbox read-state and write-side domain event fanout that workflow modules use to notify parents, tutors, and coordinators.

This is adjacent to workflows, not stored under `workflows/`

Notifications are centralized under workers_nivaa/src/lib/notifications/, not under src/lib/workflows/. The reason is intentional: inbox read-state and event delivery are shared infrastructure used by multiple workflow domains rather than being a single domain lifecycle on their own.

Architecture Split

LayerCode locationWhat it does
Inbox readsworkers_nivaa/src/lib/notifications/inbox/Owns list/read/read-all semantics and projection shaping for notification payloads.
Domain event shapeworkers_nivaa/src/lib/notifications/events.tsDefines DomainNotificationEvent, module keys, channels, and dispatch helpers.
Channel deliveryworkers_nivaa/src/lib/notifications/channels/Delivers in-app notifications to user_notifications and Telegram notifications through the bot channel.
Frontend storefrontend_nivaa/src/lib/notifications/store.tsProvides shared load/load-more/mark-read/mark-all state for workspace shells and full notification pages.

Domain Modules Using It

Domain moduleTypical event keysCommon recipients
Session workflowreschedule.requested, reschedule.countered, trial.invoice_paid, session approval/dispute keysParent, tutor, and coordinator ops inboxes
Posting workflowPosting create/push/apply/accept keysCoordinators, parents, and tutors depending on the step
Trial workflowDate proposal, invoice, payment, feedback, and conversion keysParent, tutor, and coordinator
Admin broadcast / bootstrapOperational attention itemsAdmin/coordinator inboxes

Dispatch Lifecycle

  1. 1 1. Workflow command completes: A domain command performs its state transition and determines who must be notified.
  2. 2 2. Effects package event specs: The domain's effect layer or route helper builds NotificationInput[] and wraps them in a DomainNotificationEvent.
  3. 3 3. Dispatcher fans out by channel: dispatchDomainNotificationEvent() sends each recipient through inapp and/or telegram delivery channels.
  4. 4 4. Inbox route reads remain centralized: /notifications, read-one, and read-all do not know the originating workflow; they operate on the unified inbox store.
  5. 5 5. Frontend renders one consistent inbox: Workspace shell preview and the full notifications page both use the same frontend notification store.

Technical Event Shape

DomainNotificationEvent
type DomainNotificationModule =
  | 'session-workflow'
  | 'tutor-onboarding'
  | 'family-onboarding'
  | 'notifications'
  | 'postings'
  | 'trial';

interface DomainNotificationEvent {
  module: DomainNotificationModule;
  key: string;
  recipients: NotificationInput[];
  channels: Array<'inapp' | 'telegram'>;
}

Operational Consequences

  • Route handlers no longer need to call raw createNotification() directly for the main workflow paths.
  • Workflow modules stay responsible for deciding *who* gets notified and *why*, while notification infrastructure stays responsible for *how* those payloads are delivered.
  • Inbox UI is no longer allowed to invent role-specific notification loading logic; it must consume the shared store.