Notifications

THE FULL VERSION OF THIS PAGE SHIPS IN THE REPOSITORY AS DOCS/NOTIFICATIONS.MD. THE PROVIDER LIST BELOW IS GENERATED FROM THE REGISTRY THAT RENDERS THE EDITOR, AND CI FAILS IF THEY DISAGREE.

One sentence first, because everything else on this page exists to make it true: when Vigil decides to alert you, that decision is durable, and the message is delivered at least once or recorded as failed where you can see it.

25 native provider types, unlimited channels

Two different numbers, and they are worth keeping apart. 25 native provider types ship, in both editions, and that count is generated from the code so no page here can drift from it. Native means Vigil implements that service’s own documented API and pins the version. How many channels you configure is not limited at all: forty Slack channels, one per client, each pointing somewhere different, is a supported setup, and so are two pointing at the same place. Nothing counts them and no edition changes that.

There is also one bridge, and it is not a twenty-sixth integration. The Apprise provider forwards to an Apprise API server you run; what that server reaches is between you and Apprise. Vigil hosts no Apprise server, operates no shared relay, and has not tested the services yours forwards to. So the sentence is always “25 native providers, plus additional services through your own Apprise server”, and the two halves are never added together.

“Unlimited” means this application imposes no count. It is not a throughput promise: Slack and the rest still enforce their own rate limits, your server is still your server, and what a large fan-out actually costs is measured and published rather than glossed over.

A channel is a provider, its settings, its encrypted credentials and the event classes it subscribes to. Every provider rides the same transactional outbox: durable enqueue in the transaction that decided to alert, leases, retries with jittered backoff, idempotency keys derived from the cause, and a per-channel rate limit. No provider has a queue of its own.

Provider API Vigil is written against Credential you supply
Chat
Slack Incoming Webhooks the webhook URL
Discord Webhooks (API v10) the webhook URL
Microsoft Teams Workflows (Power Automate) trigger the workflow URL
Telegram Bot API sendMessage bot token and chat id
Google Chat Chat API v1 incoming webhook the webhook URL
Mattermost REST API v4, POST /api/v4/posts bot token and channel id
Rocket.Chat REST API v1, chat.postMessage personal access token and user id
Matrix Client-Server API v3 access token and room id
Zulip REST API v1, POST /api/v1/messages bot email and API key
LINE Messaging API v2 push channel access token and destination id
On-call and tickets
PagerDuty Events API v2, trigger and resolve Events API v2 integration key
Jira Service Management Jira Cloud REST API v3 account email and API token
Push
Pushover Messages API, /1/messages.json application token and user or group key
Gotify Server API, POST /message application token
ntfy Publish API, ntfy.sh or your server token or user + password, optional
Pushbullet API v2, POST /v2/pushes access token
Bark Bark API v2, POST /push device key and your Bark server
Web Push RFC 8291 aes128gcm, RFC 8292 VAPID VAPID key pair and a browser subscription
Home Assistant REST API, notify domain only long-lived access token
SMS and messaging
Twilio SMS Programmable Messaging 2010-04-01 your Twilio account and a sender
Twilio WhatsApp Programmable Messaging 2010-04-01 a WhatsApp sender, and a template for out-of-hours
Email
SMTP Own client: STARTTLS/TLS, AUTH PLAIN your mail server, password optional
Resend API v1, POST /emails with Idempotency-Key API key
Webhooks and buses
Webhook Signed JSON POST, X-Vigil-Signature HMAC signing secret
Amazon SNS Query API 2010-03-31, Signature Version 4 access key allowed to sns:Publish
Bridge, not an integration
Apprise (your own server) apprise-api, POST /notify or /notify/{key} your own Apprise server

Every credential is yours. There is no Vigil-funded relay, account, API budget or hosted service behind any provider, and that includes the bridge - there is no managed Apprise. Self-hosted rows (Gotify, ntfy, Matrix, Mattermost, Rocket.Chat, Zulip, Bark, Home Assistant, Apprise, the signed webhook) may point at servers on your own network; cloud metadata and link-local space are refused wherever a URL points or resolves, and redirects are never followed.

Rows worth reading twice. Teams is the Power Automate Workflows webhook - Microsoft disabled the old Office 365 connector URLs in May 2026, and Vigil never speaks the retired MessageCard format. Jira Service Management is not Opsgenie: Atlassian shuts Opsgenie down on 5 April 2027, so this is the Jira Cloud platform API, opening one issue per outage and closing it on recovery. LINE is the Messaging API, not the discontinued LINE Notify. Twilio WhatsApp needs an approved template for alerts outside a 24-hour service window, which is exactly when a 3am outage fires. PagerDuty is sent trigger and resolve and never acknowledge, because Vigil does not know whether a human picked the alert up. Amazon SNS is signed in-tree with Signature Version 4 and Web Push is encrypted in-tree per RFC 8291, both so the request goes through the same egress guard as everything else rather than through an SDK's own HTTP stack.

Routing

A channel subscribes to event classes, and every event belongs to exactly one, so one logical event cannot reach the same channel twice: monitor down/up, incident lifecycle, certificate and domain expiry (a carve-out of down/up by monitor kind - the certificate channel hears about certificates, the 3am pager does not), and in the commercial edition recovery results and remote-probe quorum. Recovery notifies outcomes only: success once verified, failure once the chain is exhausted. Quorum events fire on the transition, not once per round.

A channel also has a scope. Left alone it is a workspace default and hears about every monitor, plus the events that belong to no monitor at all, such as an incident somebody reported by hand. Point it at specific monitors and it hears about those and nothing else - which is how one Slack workspace gets everything and another gets only the client it belongs to. A channel matched by several rules is still sent one message.

Credentials and errors

Channel secrets are stored encrypted (AES-256-GCM) and are never sent back to a browser; the editor only shows which secret fields are set. Delivery errors are scrubbed of secret values and URL query strings before they are stored, and the delivery history shows provider, event, destination, attempts, final status and that redacted error. SMTP refuses to authenticate without TLS and certificate verification has no off switch.

Retries, honestly

Delivery is at-least-once, and since 1.18.0 it is durable: twenty attempts over a six-hour horizon, backed off exponentially to a half-hour ceiling and jittered a quarter either way. A provider that is down for an afternoon no longer costs you the alerts queued behind it — before 1.18.0 the whole retry window was 31 to 62 seconds, and an outage longer than a minute ended with every queued message marked failed. The schedule lives on the row, so a restart or a redeploy mid-outage changes nothing about when a message is next tried.

Where a chain ends, it says which bound it hit. Rejected means a provider refused it and will keep refusing — a revoked credential, an address that does not exist — and it is marked immediately instead of hiding behind a queue that never drains. Dead letter means it ran out of attempts. Expired means it ran out of time. A screen full of expired means a provider was unreachable; a screen full of rejected means something is misconfigured, and the two need opposite fixes.

Every attempt leaves its own record, written before anything is sent and never overwritten by a later one, so the settings page can show the whole timeline: attempt number, outcome, HTTP status, the redacted error and what the provider asked for. The unavoidable window is a crash after a provider accepted a message but before the row records it — that attempt is recorded as unknown rather than guessed at, and unknown attempts are counted separately, because they are the only case where a duplicate at the far end cannot be ruled out. Where the provider offers idempotency, the retry collapses on their side. The wording the product uses everywhere: at most one message per logical notification where the provider cooperates, at least one everywhere — and never a claim of exactly-once.

Finished deliveries can be replayed from the settings page, which queues new work and never rewinds the original: the record of what happened the first time is the point of having it. Terminal rows and their evidence are removed after thirty days; nothing queued is ever deleted, because a late message is not an old one.

The full engineering document, including the outbox schema and the exact guarantee wording, ships in the repository as docs/NOTIFICATIONS.md.