Notifications
Breeze delivers alert notifications through the channels configured in Alerts > Delivery. The page has three sections: Channels, Routing, and Escalation policies. Channels and routing can belong to one organization or to its partner. Every alert still creates in-app notifications; external delivery follows the monitor’s Notify choice and the visible routing rows, ending at Everything else.
How notifications work internally
Notification delivery follows a two-stage pipeline:
- Event Bus – When an alert is triggered, acknowledged, or resolved, the internal event bus emits an event (
alert.triggered,alert.acknowledged,alert.resolved). - Notification Dispatcher – A background worker picks up the event, sends in-app notifications immediately, then queues individual send jobs for each configured notification channel.
The dispatcher runs with a concurrency of 5 workers and tracks delivery status (pending, sent, failed) for each notification. Escalation policies can schedule delayed follow-up notifications that are automatically cancelled when an alert is acknowledged or resolved.
Notification Channels
Section titled “Notification Channels”Channels define where notifications are delivered. Each channel belongs to an organization, has a type, a JSON configuration object, and an enabled/disabled flag.
A channel can also be created partner-wide so a single Slack, PagerDuty, email, or other destination serves every organization the partner manages, instead of configuring the same destination per org – see Partner-wide Channels and Routing below.
Supported channel types
Section titled “Supported channel types”| Type | Description | Config key(s) |
|---|---|---|
email |
Sends alert emails via the configured EmailService | recipients or to |
slack |
Posts to a Slack channel via incoming webhook | webhookUrl |
teams |
Posts to a Microsoft Teams channel via incoming webhook | webhookUrl |
webhook |
Sends an HTTP request to any HTTPS endpoint | url, method, headers, authType, … |
pagerduty |
Creates an incident via PagerDuty Events API v2 | routingKey or integrationKey |
sms |
Sends text messages via Twilio Programmable Messaging | phoneNumbers, from, messagingServiceSid |
pushover |
Sends push notifications to mobile devices via the Pushover Messages API (emergency-priority capable) | token, user |
Sender Details
Section titled “Sender Details”The email sender uses the platform EmailService to deliver formatted alert notification emails.
Configuration fields:
| Field | Type | Required | Description |
|---|---|---|---|
recipients or to |
string or string[] | Yes | One or more email addresses |
Payload includes: alert name, severity, summary, device name, occurrence time, dashboard URL, and organization name.
The sender validates email addresses against the pattern ^[^\s@]+@[^\s@]+\.[^\s@]+$. If the EmailService is not configured (no SMTP settings), the sender returns a failure with the error “Email service not configured”.
Webhook
Section titled “Webhook”The webhook sender delivers alert data as structured JSON to an HTTPS endpoint. It includes SSRF protection that blocks requests to private/loopback IP ranges, localhost, and .local hostnames on hosted Breeze. DNS resolution is also checked to prevent rebinding attacks.
Self-hosted deployments (IS_HOSTED=false) may target private RFC 1918/ULA addresses on their own network — an on-prem SIEM or ticketing system — and plain http:// is permitted only when the target resolves to such a private address. Public destinations still require HTTPS, and cloud-metadata, loopback, and link-local addresses are blocked in every configuration.
Configuration fields:
| Field | Type | Required | Description |
|---|---|---|---|
url |
string | Yes | HTTPS webhook URL |
method |
string | No | HTTP method: POST, PUT, or PATCH (default POST) |
headers |
object | No | Custom HTTP headers |
authType |
string | No | none, bearer, basic, or api_key |
authToken |
string | If bearer | Bearer token value |
authUsername |
string | If basic | Basic auth username |
authPassword |
string | If basic | Basic auth password |
apiKeyHeader |
string | If api_key | Header name for the API key |
apiKeyValue |
string | If api_key | API key value |
timeout |
number | No | Request timeout in ms (1000–60000, default 30000) |
retryCount |
number | No | Number of retries on failure (default 0) |
payloadTemplate |
string | No | Custom JSON template with {{variable}} placeholders |
Default payload structure:
{ "event": "alert.triggered", "timestamp": "2026-02-18T12:00:00.000Z", "alert": { "id": "uuid", "name": "CPU High", "severity": "high", "summary": "CPU usage exceeded 95%", "triggeredAt": "2026-02-18T11:59:00.000Z", "ruleId": "uuid", "ruleName": "High CPU Rule" }, "device": { "id": "uuid", "name": "web-server-01" }, "organization": { "id": "uuid", "name": "Acme Corp" }, "context": {}}Payload templates support {{variable}} syntax with dot-notation paths. Available variables: alertId, alertName, severity, summary, deviceId, deviceName, orgId, orgName, triggeredAt, ruleId, ruleName, timestamp, plus any keys from the alert context object.
Retry behavior: Failed requests are retried with exponential backoff (2^attempt seconds). Client errors (4xx) are not retried.
Slack and Teams
Section titled “Slack and Teams”Slack and Teams channels use incoming webhook URLs and deliver notifications through the webhook sender with a text-based payload template.
Configuration:
| Field | Type | Required | Description |
|---|---|---|---|
webhookUrl |
string | Yes | Incoming webhook URL from Slack or Teams |
Message format: [SEVERITY] Alert Name: Summary message
Both channel types reuse the webhook sender internally with a hardcoded payload template: {"text":"[{{severity}}] {{alertName}}: {{summary}}{{dashboardUrl}}"}. The dashboard URL is appended when the DASHBOARD_URL environment variable is set.
PagerDuty
Section titled “PagerDuty”The PagerDuty sender creates incidents via the PagerDuty Events API v2 at https://events.pagerduty.com/v2/enqueue.
Configuration fields:
| Field | Type | Required | Description |
|---|---|---|---|
routingKey or integrationKey |
string | Yes | PagerDuty service integration key |
severity |
string | No | Override severity: critical, error, warning, info |
source |
string | No | Source field (defaults to device name or breeze-rmm) |
component |
string | No | Component field (defaults to device name) |
group |
string | No | Group field (defaults to organization name) |
class |
string | No | Class field (defaults to rule name) |
dedupKey |
string | No | Deduplication key (defaults to alert ID) |
customDetails |
object | No | Additional key-value pairs merged into custom_details |
timeout |
number | No | Request timeout in ms (1000–60000, default 15000) |
Severity mapping (when no override is configured):
| Alert Severity | PagerDuty Severity |
|---|---|
critical |
critical |
high |
error |
medium |
warning |
low / info |
info |
The sender sends a trigger event action. The dedup key defaults to the alert ID, enabling PagerDuty to group repeated alerts into a single incident.
The SMS sender delivers text messages via Twilio Programmable Messaging.
Configuration fields:
| Field | Type | Required | Description |
|---|---|---|---|
phoneNumbers |
string[] | Yes | Array of recipient phone numbers in E.164 format |
from |
string | No | Sender phone number in E.164 format |
messagingServiceSid |
string | No | Twilio Messaging Service SID |
Message format: [SEVERITY] Alert Name on Device (Org): Summary message
Messages are truncated to 1400 characters. Phone numbers must be in E.164 format (e.g., +15551234567). The SMS service must be configured via the Twilio integration; if not, the sender returns “SMS service not configured”.
The send result includes sentCount and failedCount for multi-recipient deliveries, along with per-recipient error details.
Pushover
Section titled “Pushover”The Pushover sender delivers alert notifications to a user’s phone via the Pushover Messages API, including emergency-priority alerts that repeat until acknowledged.
Configuration fields:
| Field | Type | Required | Description |
|---|---|---|---|
token |
string | Yes* | Pushover application API token (30 characters max). Leave blank to inherit the partner default. |
user |
string | Yes* | Pushover user or group key (30 characters max). Leave blank to inherit the partner default. |
device |
string | No | Target a specific device by name (25 characters max). |
priority |
number | No | -2 (lowest) to 2 (emergency). Defaults to a severity-based mapping when blank. |
sound |
string | No | A Pushover built-in or custom sound name. |
* Either the channel config or the partner default must supply both token and user. Saving a channel with both blank and no partner default returns a validation error.
Partner-level inheritance: MSPs can set default Pushover credentials once at the partner level (pushoverAppToken and pushoverDefaultUser under the partner’s notification settings). Any organization channel that leaves token and/or user blank inherits the partner default, so customers don’t each need their own Pushover application. Like the channel values, the partner defaults are stored encrypted and returned masked: the settings page shows a saved value as saved, leaving the field blank keeps it, and Remove clears it.
Security: The token and user values are encrypted at rest, the same as PagerDuty credentials.
In-App
Section titled “In-App”In-app notifications are created directly in the user_notifications database table and appear in each user’s notification center within the Breeze dashboard.
Delivery scope: When an alert triggers, in-app notifications are sent to:
- All active users directly assigned to the alert’s organization
- All active partner users with access to the organization (either
allorg access orselectedaccess that includes the org)
User IDs are deduplicated before notification records are created.
No configuration required. In-app notifications are always sent as the baseline delivery mechanism for every alert. They do not need a notification channel record.
Notification fields:
| Field | Type | Description |
|---|---|---|
id |
UUID | Notification ID |
userId |
UUID | Target user |
orgId |
UUID | Organization context |
type |
enum | alert, device, script, automation, system, user, security |
priority |
enum | low, normal, high, urgent (mapped from alert severity) |
title |
varchar(255) | Notification title (alert name) |
message |
text | Notification body |
link |
varchar(500) | Deep link (e.g., /alerts/{alertId}) |
metadata |
JSONB | Additional context (alert ID, severity, device info) |
read |
boolean | Read status (default false) |
readAt |
timestamp | When marked as read |
createdAt |
timestamp | Creation timestamp |
Priority mapping from alert severity:
| Alert Severity | Notification Priority |
|---|---|
critical |
urgent |
high |
high |
medium |
normal |
low / info |
low |
In-App Notification Management
Section titled “In-App Notification Management”The /notifications API endpoints allow authenticated users to manage their in-app notifications.
Listing notifications
Section titled “Listing notifications”GET /notifications?limit=50&offset=0&unreadOnly=true&type=alertQuery parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
number | 50 | Max notifications to return |
offset |
number | 0 | Pagination offset |
unreadOnly |
boolean | false | Filter to unread notifications only |
type |
string | – | Filter by type: alert, device, script, automation, system, user, security |
Response:
{ "notifications": [...], "total": 142, "unreadCount": 7, "limit": 50, "offset": 0}Notifications are returned in reverse chronological order (newest first). The unreadCount is always returned regardless of filters, showing the user’s total unread count.
Getting the unread count
Section titled “Getting the unread count”GET /notifications/unread-countReturns { "count": 7 } – the number of unread notifications for the current user.
Marking notifications as read or unread
Section titled “Marking notifications as read or unread”PATCH /notifications/readContent-Type: application/json
{ "ids": ["uuid1", "uuid2"], "read": true}Request body:
| Field | Type | Description |
|---|---|---|
ids |
string[] | Specific notification IDs to update |
all |
boolean | Set to true to update all notifications |
read |
boolean | Target read state (default true) |
Use "all": true to mark all notifications as read in a single operation. Use "read": false to mark notifications as unread.
Deleting notifications
Section titled “Deleting notifications”Delete a single notification:
DELETE /notifications/:idReturns 404 if the notification does not exist or belongs to another user.
Delete all notifications:
DELETE /notificationsRemoves all notifications for the current user.
Mobile Push Notifications
Section titled “Mobile Push Notifications”Breeze also supports push notifications to registered mobile devices via Firebase Cloud Messaging (FCM) for Android and Apple Push Notification Service (APNS) for iOS.
Key features:
- Severity filtering – Mobile devices can specify which alert severities they want to receive via the
alertSeveritiesarray - Quiet hours – Each mobile device can configure quiet hours (start/end time with timezone) during which push notifications are suppressed
- Per-device delivery – Push notifications are sent individually to each registered mobile device for a user
Push notifications subscribe to the same alert.triggered event bus and run in parallel with the notification dispatcher. Delivery status is tracked in the push_notifications table with states: pending, sent, stubbed, failed.
Notification Routing
Section titled “Notification Routing”Open Alerts > Delivery, then Routing. A monitor’s own Notify setting wins; otherwise the first matching row from the top decides delivery.
- In-app notifications are created independently of external delivery.
- A monitor set to Inbox only sends no external notifications and does not escalate. A monitor set to Use these channels instead uses its own channel list.
- During the conversion period, an unretired legacy rule’s explicit channel override still applies. Conversion carries that choice onto the monitor; upgrading Delivery does not silently remove it.
- Other alerts use the first matching enabled routing row. Rows match severity, monitor kind (
monitorKinds), and site. Lower priority numbers run first; organization rows win ties with partner rows. Non-default rows are considered before defaults. - If no ordinary row matches, the organization’s Everything else row applies when present; otherwise the partner’s Everything else row applies. Empty default channels mean Inbox only initially. With no default row, initial delivery is also inbox only; any independently resolved escalation still runs.
A site-specific row matches only a known matching site. A monitor-kind row cannot match an alert with no monitor kind. The old condition-type and device-tag routing inputs never affected delivery and are no longer accepted.
Everything else is always shown last and cannot be reordered. The partner row is permanent; Use partner default removes an optional org override after confirming the inherited channels/escalation (or inbox only). Its channels and escalation policy can be edited. In an organization view, Customize for this organization creates the optional org default from the partner’s current choices. That row stops following the partner default for that organization and can be removed later to inherit again.
Preview delivery
Section titled “Preview delivery”A monitor’s Notify > Inherit card shows the channel names, the matching row and its owner, and the escalation policy. Its link opens Alerts > Delivery. Changing severity or monitor kind refreshes the preview using the current editor values. Explicit channel and inbox-only choices remain editable on the monitor.
On Delivery, Test this rule set previews a severity, optional monitor kind, and optional site. Select an organization first for partner-wide monitors. Without a site, site-specific rows are excluded. This preview does not send notifications.
The API is GET /alerts/delivery/resolve?orgId=<uuid>&severity=critical&kind=cpu&siteId=<uuid>&monitorId=<uuid>. Only orgId and severity are required. It returns eligible channelIds, skippedChannelIds, escalationPolicyId, source, optional routingRuleId/routingRuleName, a readable display, and structured names in description. A supplied monitor ID uses that saved monitor’s Notify settings. The request is read-only and requires alerts:read; an inaccessible org/site/monitor is rejected.
The decision returns eligible channelIds and skippedChannelIds with disabled or unavailable reasons. Unavailable channels may be missing, outside the organization’s ownership, or invisible to the current session; the preview does not distinguish those cases. The preview shows both, and escalation resolves independently. Channel test results, send failures, throttles, suppression, and acknowledgement can affect actual delivery after a decision is made; a preview is not a delivery receipt.
Escalation policies
Section titled “Escalation policies”Open Alerts > Delivery > Escalation policies to create, edit, or delete a policy. Give it a name and timed steps, then choose it on a routing row or in a monitor’s Notify card. Partner-wide policies have an All orgs badge and require partner-wide management access to change.
Steps notify channels, users in-app, or both, with at least one target per step. Use 1..10 steps, integer delays of 1..10080 minutes, repeat intervals of 1..1440 minutes and 1..10 additional sends, with at most 50 total occurrences. Optional renotify: { everyMinutes, maxTimes } repeats a step at that interval; maxTimes is the number of additional sends after its first notification.
Escalation resolves independently of the initial channel list: an explicit monitor escalation wins unless Notify is Inbox only; otherwise the winning routing/default row’s escalation applies, then none. An empty default channel list can therefore mean inbox now and escalation later. Acknowledging or resolving the alert cancels pending escalation jobs.
{ "name": "On-call", "steps": [ { "delayMinutes": 15, "channelIds": ["channel-uuid-1"] }, { "delayMinutes": 60, "channelIds": ["channel-uuid-1"], "userIds": ["user-uuid-1"], "renotify": { "everyMinutes": 15, "maxTimes": 2 } } ]}User targets are validated on save and checked again against the alert’s organization when delivered: active unrestricted org members or partner users whose all/selected org access includes that org. Org callers may newly select only org members, while existing partner-picked targets survive edits. Partner/system callers can also select partner users; partner-wide policies list all active partner users and re-filter per alert. Product owner confirmation pending (HO-20260919-alerting-consolidation Q1).
From automations
Section titled “From automations”An automation’s send_notification action targets its configured channel explicitly. It does not change the alert delivery defaults.
AI and MCP
Section titled “AI and MCP”Use manage_delivery to resolve delivery, list/create/update/delete ordinary routing rows, edit the default with set_default, remove an optional org default with delete_routing to inherit again (the partner default is permanent), and list/create/update/delete escalation policies using the same bounded schemas and caller-aware user validation as the web/API. Reads require alerts:read; mutations require alerts:write and the applicable ownership and MFA/agent guards. Use manage_notification_channels for channel CRUD and test sends.
Upgrade note: explicit defaults
Section titled “Upgrade note: explicit defaults”Partner-wide Channels and Routing
Section titled “Partner-wide Channels and Routing”Choose All organizations when creating a partner-owned channel, routing row, or escalation policy. Ownership is fixed at creation. The org view shows its rows together with inherited partner rows; inherited rows are labelled and read-only. Manage partner rows from the partner view. Inherited reads never include channel configuration or escalation step targets: channels show identity, type and enabled state, routing shows site-filtered conditions and destination IDs, and escalation policies show identity and step count.
Partner-wide channels do not automatically subscribe new alerts merely because they exist. Add them to a routing/default row or a monitor’s explicit channel choice. Test sends on a shared channel require partner-wide management access. Delivery history, throttling, and send records remain scoped to the firing device’s organization.
API Reference
Section titled “API Reference”In-App Notification Endpoints
Section titled “In-App Notification Endpoints”| Method | Path | Description |
|---|---|---|
| GET | /notifications |
List notifications for the current user |
| GET | /notifications/unread-count |
Get unread notification count |
| PATCH | /notifications/read |
Mark notifications as read/unread (by IDs or all) |
| DELETE | /notifications/:id |
Delete a single notification |
| DELETE | /notifications |
Delete all notifications for the current user |
All endpoints require authentication via authMiddleware.
Notification Channel Endpoints
Section titled “Notification Channel Endpoints”| Method | Path | Description |
|---|---|---|
| GET | /alerts/channels |
List notification channels visible to the caller (?orgId=&type=&enabled=) – includes partner-wide channels for partner-scoped tokens |
| POST | /alerts/channels |
Create a notification channel. Set ownerScope: "partner" to create it partner-wide |
| PUT | /alerts/channels/:id |
Update a notification channel |
| DELETE | /alerts/channels/:id |
Delete a notification channel |
| POST | /alerts/channels/:id/test |
Send a test notification through the channel |
Channel endpoints require organization, partner, or system scope. Create, update, delete, and test all require MFA verification. Creating, updating, deleting, or test-sending a partner-wide channel additionally requires partner-wide management access.
Per-channel throttle
Section titled “Per-channel throttle”Each channel can cap how many notifications it dispatches per device (or per rule, depending on the alert source) inside a sliding window. Use this when a noisy device might otherwise pager-bomb on-call.
| Field | Type | Default | Description |
|---|---|---|---|
throttleMaxPerWindow |
integer or null | null | Maximum notifications per window. null or 0 means unlimited. |
throttleWindowSeconds |
integer | 3600 |
Length of the sliding window in seconds. |
The throttle is applied at dispatch time. When the cap is reached, the dispatcher skips the send and records a “throttled” outcome on the delivery — throttled deliveries are not retried. If the throttle backend (Redis) is unavailable, the dispatcher fails open and delivers the notification rather than dropping it.
Add these fields to any channel POST/PUT body:
curl -X POST /alerts/channels \ -H "Content-Type: application/json" \ -d '{ "name": "On-Call SMS", "type": "sms", "config": { "phoneNumbers": ["+15551234567"], "from": "+15550001111" }, "throttleMaxPerWindow": 5, "throttleWindowSeconds": 3600, "enabled": true }'Creating a channel
Section titled “Creating a channel”curl -X POST /alerts/channels \ -H "Content-Type: application/json" \ -d '{ "name": "Ops Team Email", "type": "email", "config": { "recipients": ["ops@example.com", "oncall@example.com"] }, "enabled": true }'curl -X POST /alerts/channels \ -H "Content-Type: application/json" \ -d '{ "name": "Incident Webhook", "type": "webhook", "config": { "url": "https://hooks.example.com/breeze", "method": "POST", "authType": "bearer", "authToken": "secret-token", "timeout": 10000, "retryCount": 2 }, "enabled": true }'curl -X POST /alerts/channels \ -H "Content-Type: application/json" \ -d '{ "name": "Slack #alerts", "type": "slack", "config": { "webhookUrl": "https://hooks.slack.com/services/T.../B.../xxx" }, "enabled": true }'curl -X POST /alerts/channels \ -H "Content-Type: application/json" \ -d '{ "name": "PagerDuty On-Call", "type": "pagerduty", "config": { "routingKey": "your-pagerduty-integration-key", "source": "breeze-production" }, "enabled": true }'curl -X POST /alerts/channels \ -H "Content-Type: application/json" \ -d '{ "name": "On-Call SMS", "type": "sms", "config": { "phoneNumbers": ["+15551234567", "+15559876543"], "from": "+15550001111" }, "enabled": true }'curl -X POST /alerts/channels \ -H "Content-Type: application/json" \ -d '{ "name": "On-Call Pushover", "type": "pushover", "config": { "token": "your-pushover-app-token", "user": "your-pushover-user-key", "priority": 1 }, "enabled": true }'curl -X POST /alerts/channels \ -H "Content-Type: application/json" \ -d '{ "name": "Teams IT Channel", "type": "teams", "config": { "webhookUrl": "https://outlook.office.com/webhook/..." }, "enabled": true }'Testing a channel
Section titled “Testing a channel”Send a test notification to verify the channel is configured correctly:
POST /alerts/channels/:id/testThe test endpoint sends a real notification with a test payload through the selected channel type and returns the result:
The outcome is also recorded on the channel itself, so a failure stays visible after you navigate away. The channel keeps lastTestedAt, lastTestStatus, and — when the test failed — lastTestError, which holds the reason the send was rejected (a bad token, a refused recipient, an unreachable endpoint). The channel list surfaces that reason rather than a bare failure icon, so a broken channel can be diagnosed without re-running the test blind.
{ "channelId": "uuid", "channelName": "Ops Team Email", "channelType": "email", "testMessage": { "title": "Test Alert from Breeze RMM", "message": "This is a test notification sent to channel \"Ops Team Email\" at 2026-02-18T...", "severity": "info", "source": "manual_test" }, "testResult": { "success": true, "message": "Test email sent successfully", "details": { "recipients": ["ops@example.com"] } }, "testedAt": "2026-02-18T12:00:00.000Z", "testedBy": "user-uuid"}Troubleshooting
Section titled “Troubleshooting”No notifications being sent for alerts. Open the monitor’s Notify preview or Alerts > Delivery > Test this rule set with the alert’s organization, severity, kind, and site. Check which row won, whether it has channels, and whether those channels are enabled and pass a channel test. A new channel receives no alerts until it is explicitly selected. An empty Everything else row or no default row produces only in-app notifications unless an independent escalation is configured. Existing legacy overrides remain effective during conversion. Confirm the notification dispatcher is running and inspect delivery history for failed or throttled sends.
Email notifications not arriving.
Ensure the EmailService is configured with valid SMTP settings. Check that the channel config has a recipients or to field with valid email addresses. The sender validates addresses against a basic pattern; typos in email addresses will cause validation failures.
Webhook returning errors.
On hosted Breeze, check that the URL uses HTTPS – HTTP URLs are rejected – and that the endpoint is publicly reachable; private IP ranges, localhost, and .local hostnames are blocked. (Self-hosted deployments may target private addresses; see the Webhook section above.) If the webhook requires authentication, confirm the authType, authToken, or credential fields are correctly configured. Review the statusCode in the error response for HTTP-level failures. 4xx errors are not retried.
Slack or Teams messages not appearing.
Confirm the webhookUrl is correctly copied from the Slack/Teams incoming webhook configuration. The URL must be a valid HTTPS URL. Test the channel using POST /alerts/channels/:id/test to get a detailed error response.
PagerDuty incidents not created.
Verify the routingKey or integrationKey is valid and belongs to an active PagerDuty service integration. Check that the Events API v2 integration is enabled on the PagerDuty service. The default timeout is 15 seconds.
SMS not delivered.
Confirm the Twilio integration is configured (Twilio credentials are set). All phone numbers must be in E.164 format (e.g., +15551234567). Check the send result for per-recipient error details. The SMS body is truncated at 1400 characters.
Pushover notifications not delivered.
Confirm token and user are correct, or — if relying on inheritance — that the partner’s pushoverAppToken and pushoverDefaultUser defaults are set. Test the channel with POST /alerts/channels/:id/test; the response now returns a specific validation message (for example, a too-long key or a missing token) rather than a generic failure. Ensure the Pushover app is installed on the device and has notifications enabled.
Escalation notifications still firing after alert was acknowledged.
Escalation cancellation listens for alert.acknowledged and alert.resolved events on the event bus. If the event bus is not running or the events are not emitted, delayed escalation jobs will continue to fire. Verify that alert acknowledge/resolve operations emit the correct events.
In-app notifications not appearing for partner users.
Partner users only receive in-app notifications if they have orgAccess: 'all' or orgAccess: 'selected' with the specific organization included in their orgIds array. Verify the partner user’s access configuration.
Notification queue backed up. Check notification queue health in the monitoring dashboard. The worker runs with concurrency 5. A large number of failed jobs may indicate a downstream service outage. Check Redis connectivity and the background queue worker health.