Workers & notifications¶
The worker process¶
scripts/worker.ts starts one pg-boss instance and registers all queues and schedulers. One instance is sufficient. Locally: pnpm worker. In a container: node --import tsx scripts/worker.ts, run directly with node so that SIGTERM reaches the graceful shutdown handler.
The worker needs DATABASE_URL and the same SETTINGS_ENCRYPTION_KEY as the web app, plus APP_URL, because it builds links to the status page when sending. It does not need SESSION_SECRET.
| Queue / job | Purpose | Trigger |
|---|---|---|
email-send |
Sends e-mail (dispatch via lib/email/) |
Event |
sms-send |
Sends SMS via Twilio | Event |
webhook-dispatch |
Delivers webhooks, signed with HMAC | Event |
push-send |
Sends web push notifications | Event |
maintenance-transitions |
Sends maintenance_started once for running windows without a start marker, and silently sets windows past their end to completed. The two sweeps run independently. |
Every minute |
uptime-check-sweep |
Runs HTTP checks for due, unpaused components with checks enabled | Every minute |
notification-event-reaper |
Marks e-mail events stuck in pending/processing for more than 30 minutes as failed |
Every 10 minutes |
failed-notification-alert |
Logs a warning "notification events failed in the last window" with the count and up to 50 event IDs, if e-mail events failed in the last 10 minutes. A basis for log-based alerting; nothing is sent. | Every 10 minutes |
uptime-check-retention |
Deletes uptime check results older than 30 days | Daily at 03:00 |
verify-token-cleanup |
Clears the verification token of confirmed subscribers whose confirmation is older than 30 days | Daily at 03:00 |
On start, the worker also runs ensureNotificationTemplateRows(). If an event type has no template row, it is created with all channels enabled.
Dispatch pipeline¶
Every notification follows the same path:
- A domain action calls
dispatchNotification(lib/notifications/notify.ts) with an event type and the affected entity IDs. - Mute gates are evaluated: if all affected components have notifications disabled, nothing is sent (
skipped_disabled). Per-channel flags on each component are checked separately. - Template gates are evaluated per channel. Each channel (
mail,sms,webhook,push) has its own enable flag innotification_templates. Fail-closed: no row means no sending. - Mail-only types (
MAIL_ONLY_EVENT_TYPESinlib/notifications/mail-only.ts, currentlydouble_optin) are sent by e-mail only. Webhook, SMS and push are skipped for them, regardless of the flags. - Delivery rows are created per channel:
- E-mail: one row in
notification_eventsand one job, if a provider is configured and there are recipients. - Webhook, SMS, push: one row per endpoint, recipient or subscription in
webhook_deliveries,sms_deliveriesorpush_deliveries, and one job per row. - The processor sends the message. Each delivery gets up to 3 attempts with a 2-second and then 4-second backoff. The result is stored in the channel's delivery table.
Channels are independent. A disabled e-mail channel does not block SMS or webhooks.
Channels¶
| Channel | Recipients | Transport | Notes |
|---|---|---|---|
| User accounts and confirmed subscribers (depending on subscription mode), per-component notification address | SMTP (nodemailer) or Resend | Templates are editable per event type in the admin UI. Subscriber mails use double opt-in. Sent in recipient chunks via BCC. | |
| Webhook | Configured endpoints, optional filter by event type | HTTP POST, 10-second timeout per attempt | Headers X-SLAtus-Signature: sha256=<hex>, X-SLAtus-Event and X-SLAtus-Delivery. Target types generic, slack, teams, discord format the body for the target system. Secrets can be rotated. |
| SMS | Admin-managed phone numbers | Twilio REST API | The message is built from a fixed builder and truncated to 300 characters. No self-registration. |
| Web push | Browser subscriptions created by visitors | web-push with VAPID keys |
Subscriptions that return HTTP 404 or 410 are removed automatically. |
For webhooks, the HMAC signature is calculated over the formatted body that is actually sent.
Event types¶
The event types are defined in NOTIFICATION_EVENT_TYPES (lib/db/schema.ts) and include: double_optin, outage_started, service_operational, service_outage_change, incident_created, incident_updated, incident_resolved, incident_reviewed, incident_closed, maintenance_scheduled, maintenance_started, maintenance_completed and sla_report_started.
double_optin is mail-only. On the server, fanOutEventTypesSchema (lib/validations/event-types.ts, event filters of SMS, webhook and push) and setNotificationTemplateChannelEnabledAction (channel flags) reject this type.
Retries and failures¶
- Retries happen only in the processor, so there is exactly one retry layer. pg-boss uses
retryLimit: 1solely as crash protection. - No processor throws. An unexpected exception sets the delivery to
failedwithlastError"unexpected error: …". For the e-mail processor,sent_chunksstays unchanged in that case. - E-mails are sent in recipient chunks.
notification_events.sent_chunksrecords how many chunks were already delivered. Every attempt, the pg-boss crash retry and the manual resend continue from there, so nobody gets a mail twice. - E-mail events that failed all attempts can be resent from
/admin/notification-events. Resetting sets the row back topendingand enqueues it again. For partially delivered events, the button reads Resend remaining. failed-notification-alertreports failed e-mail events in the log every 10 minutes (Issue #126).