Notifications
Overview¶
The notification system is responsible for informing users and external systems about events occurring in the platform. It consists of three phases:
- Emission: Writing notifications to an outbox table
- Relay: Polling the outbox, routing notifications to matching rules, and scheduling workflows
- Publishing: Delivering notifications to destination systems via durable workflows
---
title: Notification system overview
---
flowchart LR
subgraph Emission
D["Domain logic"]
O[("NOTIFICATION_OUTBOX")]
end
subgraph Relay
R["Outbox relay"]
RT["Router"]
end
subgraph Publishing
W["dex engine"]
P["Publishers"]
E["External systems"]
end
D -->|"INSERT"| O
R -->|"DELETE RETURNING"| O
R --> RT
RT -->|"Schedule workflow"| W
W --> P
P --> E
This design follows the transactional outbox pattern, as described in ADR-011. It enables atomic emission of notifications, removing the need for external message brokers in the critical path.
Emission¶
Notifications are emitted by inserting records into the NOTIFICATION_OUTBOX table.
This can happen within the same database transaction that performs the business logic
triggering the notification, thus avoiding the dual write problem.
erDiagram
NOTIFICATION_OUTBOX {
UUID ID PK
TIMESTAMPTZ TIMESTAMP
TEXT SCOPE
TEXT GROUP
TEXT LEVEL
BYTEA PAYLOAD
}
The ID column uses UUIDv7, which combines global uniqueness with sortability.
The PAYLOAD column contains the serialized notification in Protobuf format.
Its schema is documented here.
Preliminary filtering¶
To reduce load on the outbox table, notifications are only inserted if at least one
enabled notification rule could potentially match them. This check is performed during
the INSERT operation using an EXISTS subquery against the NOTIFICATIONRULE table.
Notifications that have no matching rules are discarded immediately.
Relay¶
The outbox relay is a background process that continuously polls the NOTIFICATION_OUTBOX table,
routes notifications to matching rules, and schedules publishing workflows.
The polling interval is configurable via notification.outbox-relay.poll-interval-ms,
and defaults to 1 second.
---
title: Relay cycle
---
sequenceDiagram
participant D as Database
participant R as Relay
participant RT as Router
participant DE as dex engine
activate R
R ->> D: Begin TX
R ->> D: Acquire advisory lock
D -->> R: Lock acquired
R ->> D: DELETE FROM NOTIFICATION_OUTBOX<br/>RETURNING PAYLOAD
D -->> R: Notifications
R ->> RT: Route notifications
RT -->> R: Matched rules per notification
R ->> DE: Schedule workflows (atomic)
R ->> D: Commit TX
deactivate R
Concurrency control¶
Transaction-level advisory locks prevent concurrent relay cycles across multiple API server instances.
This ensures notifications are relayed in approximately the order they were emitted.
The lack of concurrency is offset by batch processing. The batch size is configurable
via notification.outbox-relay.batch-size
and defaults to 100.
Routing¶
The router evaluates each notification against all enabled notification rules (a.k.a. alerts). A rule matches if:
- Its scope matches the notification's scope
- Its level is equal to or less verbose than the notification's level
- The notification's group is in the rule's configured groups
- If the rule is limited to specific projects or tags, the notification's subject matches
Routing is performed in batches, using a single SQL query with UNNEST,
reducing database round trips.
Large notification handling¶
Notifications exceeding a configurable size threshold (default: 64KiB) are offloaded to
file storage, rather than being passed directly to the dex engine. This prevents large payloads
(e.g. PROJECT_VULN_ANALYSIS_COMPLETED) from bloating the workflow history.
The publishing workflow retrieves the notification from file storage
and deletes the file upon completion.
Publishing¶
For each notification with at least one matched rule, a Publish Notification workflow is scheduled. The workflow coordinates delivery for all matched rules.
---
title: Publish notification workflow
---
sequenceDiagram
participant W as Workflow
participant A as Activity
participant D as Database
participant DE as Destination
activate W
par for each matched rule
W ->> A: Invoke activity
activate A
A ->> D: Get rule config
D -->> A: Publisher, template, config
A ->> DE: Publish notification
DE -->> A: Response
A -->> W: Result
deactivate A
end
deactivate W
Publishing for each rule is performed by a separate activity concurrently, allowing independent retries per rule. If delivery to one destination fails, it does not affect delivery to others.
A workflow is considered successful if at least one rule's publishing succeeded. Conversely, if publishing for all rules failed, the entire workflow is marked as failed.
Publishers¶
Publishers are implementations that deliver notifications to specific destination types. Built-in publishers include email, Kafka, Slack, Microsoft Teams, Jira, Webhook, and more. Refer to the publishers documentation for details.
Publishers receive:
- The notification payload
- Rule-specific configuration (e.g. destination URL, credentials)
- A template renderer for formatting the notification
Retries¶
Activities can signal retryable failures (e.g. rate limiting, temporary unavailability). The dex engine transparently handles retries with backoff. Non-retryable failures are terminal for a specific rule but do not affect other rules.