Delivery & escalation
A push notification arrives instantly — but only if the phone is at hand. Delivery (the Delivery tab in the channel settings) solves the other half of the problem: what to do when nobody read the message.
Each channel can enable additional delivery methods. They work as an escalation: a message goes out over such a method only if the owner has not read it within a given time.
POST /message ──▶ push to every device (instantly) │ └─ every minute: the delivery worker ├─ message already read? → do nothing └─ method timeout elapsed? → email / SMS / call / Slack / Telegram / webhookDelivery methods
Section titled “Delivery methods”| Method | What you configure | What arrives |
|---|---|---|
| recipient address, timeout | an email with a link to the message and action buttons | |
| SMS | phone number as +79001234567, timeout | a short text with the channel name and the title |
| Call | phone number, timeout | a voice call with the notification |
| Slack | incoming webhook URL, timeout, min. priority | a message in the Slack channel |
| Telegram | bot token, chat ID, timeout, min. priority | a message from the bot (with inline buttons) |
| Webhook | URL, optional secret, timeout, min. priority | a POST with a JSON body |
Timeout: when exactly it fires
Section titled “Timeout: when exactly it fires”Every method has its own delivery timeout (readTimeoutSec):
0— deliver on the next worker run (that is, within about a minute);300— if the message has not been read for 5 minutes;- the maximum is 23 hours (
82800).
The countdown starts from the moment the message was created, not from when
the push was delivered. A message counts as read when you open it in the admin UI
or the mobile app, when one of the endpoints
POST /message/read / POST /application/{id}/message/read
is called, or when you press ✓ Read in the email or in Telegram.
The “before it was enabled” cut-off
Section titled “The “before it was enabled” cut-off”When a method is enabled, the server records a timestamp. Messages created before that moment are not escalated over the new method — otherwise enabling email in the evening would produce a mail about everything that piled up during the day.
Minimum priority
Section titled “Minimum priority”Slack, Telegram and webhook have a min. priority field: messages with a priority below the given value are not sent over that adapter. Handy for keeping only important events in the work chat while the noise stays in the app.
Email, SMS and calls have no priority filter — they are controlled only by the toggle and the timeout.
Grouping and digests
Section titled “Grouping and digests”Within a single run, messages are grouped by “owner + destination”:
- email — a single message arrives as a regular email, several arrive as one digest email with a list;
- SMS — one text per number: a title plus a short list of channels and subjects;
- call — one call per number, regardless of how many messages there are.
Slack, Telegram and webhook are not grouped: one message — one send.
Delivery guarantees
Section titled “Delivery guarantees”| Method | Behaviour on failure |
|---|---|
| email, Slack, Telegram, webhook | Idempotent: the delivery mark is written after sending, on failure the next run retries |
| SMS, call | The mark is written before sending (a duplicate is unacceptable); on error it is rolled back and the attempt is repeated |
Each message goes out over each method exactly once: the delivery fact is recorded in a log and never fires twice.
Action buttons in email and Telegram
Section titled “Action buttons in email and Telegram”Signed links are added to the email and to the Telegram bot message:
- ✓ Read — marks the message as read and stops escalation over the remaining methods;
- answer options — if the message contains a closed question (Ask), the buttons match the answer options.
The links are HMAC-signed and valid for a limited time (for questions — until the answer deadline), so they need no authentication in the UI.
Webhook format
Section titled “Webhook format”A generic webhook receives a POST with JSON:
{ "title": "CPU high", "message": "load average 12.4", "priority": 8, "channel": "Prod", "appId": 12345, "ts": 1782561600000}If a secret is set, the body is signed with HMAC-SHA256 and the signature is passed in a header:
X-Notifly-Signature: sha256=<hex>Verifying it on the receiving side (Python):
import hmac, hashlib
def valid(raw_body: bytes, header: str, secret: str) -> bool: expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, header)Configuration
Section titled “Configuration”From the admin UI
Section titled “From the admin UI”Channel settings → the Delivery tab. Turn on the method you need with the toggle, fill in the address and the timeout. The Test button next to a method saves the settings and immediately sends a test notification there.
Telegram hint: create a bot via @BotFather (/newbot) — it returns a bot token;
find the Chat ID (for direct messages ask @userinfobot, for a group it looks
like -100…); make sure to press Start on your bot or add it to the group,
otherwise Telegram will refuse the delivery.
Via the REST API
Section titled “Via the REST API”Delivery settings are part of the channel object — the deliveryConfig field in
POST /application and PUT /application/{id}:
curl -X PUT "$NOTIFLY_URL/application/12345" \ -H "Content-Type: application/json" \ -H "X-Notifly-Key: <client-token>" \ -d '{ "name": "Prod", "deliveryConfig": { "email": { "enabled": true, "to": "oncall@example.com", "readTimeoutSec": 300 }, "telegram": { "enabled": true, "botToken": "123:ABC", "chatId": "-1001234567890", "readTimeoutSec": 900, "minPriority": 7 }, "webhook": { "enabled": true, "url": "https://example.com/hook", "secret": "s3cret", "readTimeoutSec": 0, "minPriority": 5 } } }'Adapter fields:
| Field | Used by | Meaning |
|---|---|---|
enabled | all | whether the method is on |
to | email, sms, call | recipient address or number (+79001234567) |
url | slack, webhook | incoming webhook URL / arbitrary URL |
botToken, chatId | telegram | bot parameters |
secret | webhook | HMAC-SHA256 signing key (optional) |
readTimeoutSec | all | escalation timeout, 0…82800 |
minPriority | slack, telegram, webhook | do not send messages below this priority |
Testing delivery
Section titled “Testing delivery”curl -X POST "$NOTIFLY_URL/application/12345/delivery/test" \ -H "Content-Type: application/json" \ -H "X-Notifly-Key: <client-token>" \ -d '{"adapter": "telegram"}'Without a body, all enabled methods of the channel are checked. The response carries a result per method:
{ "results": { "telegram": { "ok": false, "error": "telegram: 403 bot was blocked by the user" } } }If no method is enabled (or the requested adapter is off), the response is 400
no enabled adapter to test. The check bypasses escalation: no need to wait for a
timeout, the notification goes out immediately.
Quotas
Section titled “Quotas”Escalation does not consume the daily quota: the event was already charged
when the message was created (message). No matter how many delivery methods are
enabled, the event counter does not grow — see
Quotas & pricing.
Security
Section titled “Security”- Slack and webhook URLs are checked by the SSRF guard: private, loopback and link-local addresses are blocked at send time.
- Secrets (
botToken,secret) live in the channel settings and are available to the channel owner only: someone with delegated access does not get the “Delivery” tab. - Managing delivery requires the channel owner’s client token; the channel app token is not enough.
See also
Section titled “See also”- Sending messages — priorities, titles and extras.
- Questions in channels (Ask) — answering right from the email or Telegram.
- WebSocket protocol — instant receiving instead of escalation.
- Public status pages — notifications for your users rather than for your team.