Skip to main content

Notification Pipeline

PSAP dispatcher creates Incident
POST /api/v1/incidents { agency_ids: [...], priority, incident_type, ... }
OR
InboundEventController receives signed event from PSAPLink PSAP instance
POST /api/v1/core/events/incident { incident_id, priority, company_agency_ids, ... }
|
v
NotificationService::dispatch(incident, agencyIds[])
|
├── For each company agency:
| └── Load all active users in that agency
| └── For each user:
| └── Load active UserTransportPreferences (ordered by priority_order)
| ├── Create Notification (user + incident) [ack_status = pending]
| ├── Create NotificationDelivery per transport preference
| └── Dispatch DeliveryMessage to Messenger bus (one per delivery)
|
v (async — Messenger worker)
TransportHandler::send(delivery)
├── Decrypt channel credentials from transport_channels.config
├── Call external provider (Twilio, Slack, etc.)
├── Update delivery.send_status (sent / failed)
├── CommunicationLogger::log(type: notification_sent)
└── Messenger::dispatch(EscalationMessage, [DelayStamp(ack_timeout_seconds)])

Two-Layer Model

Every outbound alert creates two database records:

  • Notification — one row per (incident, user). Tracks overall ACK status for that user.
  • NotificationDelivery — one row per (notification, transport preference). Tracks delivery status for each channel.

This separation means a single user can be notified via SMS, email, and Slack simultaneously. One ACK from any channel closes all deliveries for that notification.

Delivery Statuses

StatusMeaning
pendingCreated, not yet attempted
queuedHanded to Messenger worker
sentSuccessfully handed to transport provider
deliveredProvider confirmed delivery (where supported)
failedTransport returned an error

Three Sole-Path Rules

These are non-negotiable architectural constraints:

  1. CommunicationLogger is the only path to incident_communications. Never write communication records directly.

  2. AckIngestionService is the only path to changing notification.ack_status. All ACK sources (web, SMS reply, webhook) must go through this service.

  3. ACK cross-stream rule. When any delivery stream is ACKed, AckIngestionService marks ALL deliveries for that notification as acknowledged and logs a single event naming the triggering stream and total count. Partial acknowledgement does not exist.