Notifications¶
SerOps splits notification configuration into two pages, matching the model used by Grafana Alerting and Prometheus Alertmanager:
| Page | Answers | Backed by |
|---|---|---|
| Alerts & routing → Delivery Channels | Where can SerOps send? | IncidentNotificationChannel |
| Alerts & routing → Routing Rules | Which incidents go where? | NotificationRoutingRule |
If you have configured Grafana contact points and notification policies, this is the same separation under different names. Channels are destinations; rules decide which destinations fire.
Upgrading from an earlier version
Earlier releases had a single Settings → Notifications page with one email toggle, one Slack webhook, and one Teams webhook. That page is gone. Your existing configuration was migrated into Delivery Channels automatically, and a catch-all routing rule was created for your organisation targeting every channel — so delivery behaviour is unchanged until you narrow it.
The old /dash/settings/notifications and
/dash/settings/incident-channels URLs redirect to
/dash/settings/delivery.
Delivery Channels¶
Location: Alerts & routing → Delivery Channels
One table, one row per destination. Unlike the old page, you can create as many channels of each kind as you need — separate Slack channels per team, a PagerDuty key per service, several email recipient lists.
Channel kinds¶
| Kind | Auth | Notes |
|---|---|---|
| Slack | Incoming Webhook URL | Block Kit message with fallback text for mobile push |
| Microsoft Teams | Incoming Webhook connector URL | MessageCard, coloured by severity |
| PagerDuty | Events API v2 integration key | Emits a change event — attaches to existing incidents without paging |
| OpsGenie | API key + region (us / eu) |
Creates a note-style alert. Non-paging |
| None — uses your SMTP config | Recipients configured per channel |
Webhook URLs and API keys are encrypted at rest and write-only on the
API: reads return has_webhook_url / has_api_key booleans, never the
plaintext.
Creating an email channel¶
Email has no secret to store, but it does need recipients. Set them in
the channel's extra_config:
to accepts a single address, a list, or a comma-separated string:
{
"to": ["sre@example.com", "platform@example.com"],
"from": "SerOps Alerts <alerts@example.com>",
"reply_to": "oncall@example.com"
}
from and reply_to are optional and override the system-wide
DEFAULT_FROM_EMAIL.
SMTP must be configured first
Email channels use the server's SMTP settings, which are
environment-only — there is no UI editor, following the same model
as Grafana. See
Email / SMTP configuration.
Without it, email channels fail with a delivery error recorded in
last_error.
Testing a channel¶
Each row has a Test send button. SerOps posts a synthetic message
so you can verify the URL or secret before a real incident depends on
it. The row's last_sent_at and last_error fields record the
outcome.
Failure handling¶
Notification failures never break the analysis pipeline. If a Slack
webhook returns 500, the batch analysis still succeeds and the
channel's last_error records the response for you to investigate.
Channels that fail with a permanent error (4xx — invalid URL, revoked key) are automatically deactivated, since the same request will fail identically every time. Fix the configuration and re-enable the channel.
Routing Rules¶
Location: Alerts & routing → Routing Rules
A rule pairs a matcher — which incidents it applies to — with a set of target channels.
Matcher¶
All four axes are ANDed together. Leaving one empty means "no filter on this axis", so an entirely empty matcher matches every incident.
| Axis | Values | Empty means |
|---|---|---|
| Minimum severity | LOW, MEDIUM, HIGH, CRITICAL |
Any severity |
| Environments | List of environment names | All environments |
| Services | List of service names | All services |
| Event types | batch_analyzed, alert_triggered, critical_issue |
All events |
Severity is compared against the batch's highest-severity issue.
| Event type | Fires when |
|---|---|
batch_analyzed |
Any batch analysis completes, scheduled or alert-triggered |
alert_triggered |
Only analyses started by an inbound alert — see Real-time Alerts |
critical_issue |
The batch produced at least one CRITICAL issue |
How rules are evaluated¶
SerOps walks every active rule, collects the targets from each one that matches, and takes the union — de-duplicated, so a channel matched by three rules is still contacted once.
There is no first-match-wins, and no rule stops evaluation of the
others. priority exists on each rule and orders evaluation, but since
the result is a union it is currently informational.
No rules means no notifications
If an organisation has zero active routing rules, nothing is sent — regardless of how many delivery channels exist. This is the same behaviour as Alertmanager with an empty route tree.
Upgrades create a catch-all rule automatically, so this only affects organisations created after the upgrade or ones where every rule has been deleted.
Example rules¶
A typical setup separates paging from awareness:
| Rule | Matcher | Targets |
|---|---|---|
| Page on-call | severity CRITICAL, env production |
PagerDuty |
| SRE awareness | severity HIGH |
SRE Slack |
| Payments team | services checkout, billing |
Payments Slack, payments-oncall email |
| Alert context | event alert_triggered |
PagerDuty |
The third and fourth rules can both match the same incident; the Payments Slack channel is contacted once, and PagerDuty once.
Inbound alert authentication¶
The Delivery Channels page also manages the secret used for inbound alerts — the direction opposite to everything above. Alertmanager, Grafana, and Datadog authenticate to SerOps with a per-organisation bearer token.
Click Rotate ingest secret to generate one. The plaintext is shown exactly once; SerOps stores only a SHA-256 hash and a short display prefix, so it cannot be recovered afterwards.
Use it as a bearer token:
POST https://your-serops-host/api/webhooks/alerts/<source>/<org_id>/
Authorization: Bearer serops_alert_...
<source> is alertmanager, grafana, or datadog.
Rotation takes effect immediately
The previous secret stops working the moment a new one is generated. Update your alert senders before rotating, or expect a gap during which inbound alerts are rejected with 401.
If no per-organisation secret is set, SerOps falls back to the global
ALERT_WEBHOOK_SECRETS Django setting, so existing single-tenant
deployments keep working unchanged. See
Real-time Alerts for the full sender
configuration.
Escalation¶
Escalation is configured separately, under escalation rules, and is driven purely by whether a matching rule exists — there is no organisation-wide on/off switch.
| Level | Default delay | Notifies |
|---|---|---|
| Level 1 | 15 minutes | Project admins |
| Level 2 | 45 minutes | All organisation admins |
Acknowledging or resolving the issue stops the chain. See Set Up Escalation Rules.
Troubleshooting¶
| Symptom | Likely cause |
|---|---|
| Nothing is sent at all | No active routing rule matches. Check Alerts & routing → Routing Rules |
| One channel silent, others fine | Channel auto-deactivated after a permanent failure — check its last_error |
| No email received | SMTP not configured, or the channel's extra_config.to is empty |
| Slack returns 404 | Webhook URL revoked or expired — regenerate it in Slack |
| Teams returns an error | Regenerate the Incoming Webhook connector |
| Inbound alerts rejected with 401 | Ingest secret rotated without updating the sender |
API¶
Both surfaces are full CRUD ViewSets. Note the mount point is
/api/notification/ — singular.
# List delivery channels
curl -H "Authorization: Token $TOKEN" \
https://your-serops-host/api/notification/incident-channels/
# Create an email channel (admin)
curl -X POST -H "Authorization: Token $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
https://your-serops-host/api/notification/incident-channels/ \
-d '{
"name": "On-call email",
"kind": "email",
"extra_config": {"to": "oncall@example.com"},
"is_active": true
}'
# Create a routing rule (admin)
curl -X POST -H "Authorization: Token $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
https://your-serops-host/api/notification/routing-rules/ \
-d '{
"name": "Page on-call for prod criticals",
"min_severity": "CRITICAL",
"environments": ["production"],
"services": [],
"event_types": [],
"target_ids": [3],
"is_active": true
}'
# Rotate the inbound alert secret (admin) — plaintext returned once
curl -X POST -H "Authorization: Token $ADMIN_TOKEN" \
https://your-serops-host/api/organisation/alert-secret/rotate/
Testing a webhook before saving it uses the settings endpoint:
curl -X POST -H "Authorization: Token $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
https://your-serops-host/api/settings/notifications/test/ \
-d '{
"webhook_type": "slack",
"webhook_url": "https://hooks.slack.com/services/..."
}'
Removed endpoints
GET and PUT on /api/settings/notifications/ no longer exist —
they configured the deleted per-organisation settings model. Only
the /test/ sub-path survives.
See also¶
- Post-RCA Notifications — what the messages look like per channel kind
- Real-time Alerts — inbound alert ingestion
- Set Up Escalation Rules
- Notification Preferences — per-user settings