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
| Layer | Code location | What it does |
|---|---|---|
| Inbox reads | workers_nivaa/src/lib/notifications/inbox/ | Owns list/read/read-all semantics and projection shaping for notification payloads. |
| Domain event shape | workers_nivaa/src/lib/notifications/events.ts | Defines DomainNotificationEvent, module keys, channels, and dispatch helpers. |
| Channel delivery | workers_nivaa/src/lib/notifications/channels/ | Delivers in-app notifications to user_notifications and Telegram notifications through the bot channel. |
| Frontend store | frontend_nivaa/src/lib/notifications/store.ts | Provides shared load/load-more/mark-read/mark-all state for workspace shells and full notification pages. |
Domain Modules Using It
| Domain module | Typical event keys | Common recipients |
|---|---|---|
| Session workflow | reschedule.requested, reschedule.countered, trial.invoice_paid, session approval/dispute keys | Parent, tutor, and coordinator ops inboxes |
| Posting workflow | Posting create/push/apply/accept keys | Coordinators, parents, and tutors depending on the step |
| Trial workflow | Date proposal, invoice, payment, feedback, and conversion keys | Parent, tutor, and coordinator |
| Admin broadcast / bootstrap | Operational attention items | Admin/coordinator inboxes |
Dispatch Lifecycle
- 1 1. Workflow command completes: A domain command performs its state transition and determines who must be notified.
- 2 2. Effects package event specs: The domain's effect layer or route helper builds
NotificationInput[]and wraps them in aDomainNotificationEvent. - 3 3. Dispatcher fans out by channel:
dispatchDomainNotificationEvent()sends each recipient throughinappand/ortelegramdelivery channels. - 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. 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.