Press / to search

All documentation
docs Operate Notification channels

Runs onWrit CloudDesktopSelf-hosted

notify ▸ where alerts land

Nine channels, and one grid.

Two different systems share the word “notification”, and confusing them costs an afternoon. Channels are what a monitor or an automation fires. Preferences are what the platform tells you. This page keeps them apart.

Every channel has a test send, so you find out it works before an incident does.

split ▸ two systems

Which one do you mean?

Both live under Settings, both say “notifications”, and they do not share a channel list. Start here:

Monitor and automation channelsNine providers, configured once for the organization. A monitor detects a change, or an automation reaches a notify step, and the message goes out. Recipients are addressed as "channel:id".
Platform notification preferencesSix channels across seven categories, per user. Security, billing, team, runs, agents, marketplace and support events — the things Writ tells you about your own account.

channels ▸ nine

Set up a channel.

Configure each one once for the organization, then add recipients to it. Every channel has a test send.

ChannelAPI keyWhat you provide
PushoverpushoverApplication token and user key. Optionally a title, a message, a priority from −2 to 2, a sound, a link title, and HTML on or off.
Email (SMTP)emailHost, port, username, password, from address, from name, and TLS on or off.
Email (Google Workspace)emailConnect with OAuth instead of SMTP. The config reports which provider is active and which account is connected, and you can disconnect.
SMStwilioAccount SID, auth token, and a sending phone number in E.164 form. The auth token is never returned once saved.
WhatsAppwhatsappA sending number in whatsapp:+1234567890 form. It rides on the same SMS provider credentials, so it only becomes usable once both are configured.
SignalsignalThe URL of a signal-cli REST server you run yourself, plus the sender number in E.164 form.
SlackslackA channel webhook URL per recipient. The API only ever shows the host back, never the full URL.
DiscorddiscordA channel webhook URL per recipient. The API only ever shows the host back, never the full URL.
TelegramtelegramThe token BotFather gives you, plus a chat id per recipient. The token is masked in responses.
WebhookwebhookYour own URL, with optional custom headers and HMAC signature verification. Deliveries retry with exponential backoff.

Credentials go in and never come back out: the SMS auth token is not returned once saved, Slack and Discord URLs are shown as a bare host, and the Telegram token is masked. If you need to change one, replace it — you cannot read it back to check it.

The webhook channel signs every delivery. The exact signature contract, header names and a verification snippet live on Webhooks.

Webhooks →

recipients ▸ channel:id

Naming who to reach.

Once a channel has recipients, an automation refers to them as "channel:id" strings — for example ["pushover:1","email:3"]. One call lists every recipient across every channel, each with its identifier masked, which is what a picker renders from.

recipients

{
  "channels": ["pushover", "email"],
  "recipients": ["pushover:1", "email:3"]
}

Note the key for SMS is twilio, not sms — that is the value the API expects in a channel list. In the platform preferences grid further down, the same idea is spelled sms. They are different systems.

controls ▸ tenant-wide

Delivery controls.

These apply across the organization, not per channel — so a noisy night does not become a hundred messages:

ControlWhat it does
Quiet hoursA start and an end time during which nothing is delivered.
Rate limitingA maximum number of messages per period.
BatchingCollect what happens inside a window, in minutes, and send it as one message.
Agent-error alertsTell me when an agent errors, with a threshold and a delay in minutes.
Target-health alertsTell me when a target keeps failing, with a threshold and a check interval.
System-health alertsTell me when an agent disconnects, after a delay.

placeholders ▸ change alerts

What you can put in the message.

Change notifications render these placeholders into your message template. Group them by what they answer:

What was watched {url} {target_id} {target_name} {selector} {selector_name} {agent_id} {agent_platform} {change_count} {check_count}
What changed {diff} {detected_change} {previous_content} {new_content} {content_hash} {previous_hash}
When {timestamp} {date} {time}

An unknown placeholder is left as-is rather than failing, so a typo costs you a literal token in the message, not a lost alert. Previews are truncated: {diff} and {detected_change} at 500 characters, {previous_content} and {new_content} at 200.

relay ▸ from a device

A linked device handing off to the cloud.

A device runs automations locally, but some channels need the cloud to deliver. Only email, pushover, SMS, WhatsApp and Signal relay; webhook, Slack, Discord and Telegram are delivered by the device itself, because it can reach them directly.

relay.sh

curl -X POST https://api.usewrit.app/api/notifications/relay \
  -H "Authorization: Bearer $WRIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "channels": ["email", "pushover"],
    "recipients": ["email:3", "pushover:1"],
    "title": "Price dropped",
    "message": "Now $41.00, was $49.00.",
    "url": "https://example.com/product/42"
  }'
FieldRule
channels1 to 8, and a subset of the relayable set. Anything else is refused with 400.
recipientsRecipient references, the same "channel:id" strings.
titleUp to 200 characters.
message1 to 4000 characters.
priorityOptional.
urlOptional link, up to 2000 characters.
email_subjectOptional, up to 300 characters.

Delivery goes to your own configured recipients — never an arbitrary address supplied in the call. Relayed notifications are rate-limited to 120 per hour per organization, so a flapping local automation cannot drain an SMS budget in minutes.

preferences ▸ the other grid

Platform notification preferences.

This is the second system: what Writ tells you, per user, at GET and PUT /api/notifications/preferences. Different channel set, different purpose.

Six channels

Not the same nine as above:

in_appThe bell and the inbox inside Writ.
emailYour account email.
smsA personal contact point on your preference row.
whatsappThe same personal contact point.
signalThe same personal contact point.
pushoverYour own Pushover user key.

Seven categories

Events are grouped so you can reason about them without reading every row:

SecurityAccount security activity.
Billing & paymentsBalance, receipts, payment problems.
TeamInvites and membership.
Automations & runsWhat your workflows did.
Agents & devicesWhat your machines did.
Marketplace & creatorListings, installs, earnings.
SupportTicket activity.

Four rules that surprise people

  1. The grid is sparse. Only the channels an event actually offers are shown — a blank cell is not a switch you are missing.
  2. Some cells are locked and always on: security alerts, payment problems and team invites. The API will not store an override for them.
  3. Some events are email-only, by design.
  4. Per-monitor change alerts deliberately live outside this grid, with their own per-target settings.

The self-hosted build ships a trimmed catalog, without the billing and marketplace events — there is no billing or marketplace in that build to notify you about.

faq

Notification questions, answered.

Why are there two different notification settings?
Because they answer different questions. The nine channels are how your monitors and automations reach whoever needs to know. The platform preferences are how Writ reaches you about your own account — security, billing, team, runs, agents, marketplace and support. They do not share a channel list.
Why can I not read back my SMS token or my Slack URL?
Credentials go in and never come back out. The SMS auth token is not returned once saved, Slack and Discord URLs come back as a bare host, and the Telegram token is masked. If a credential needs changing, replace it rather than checking it.
Why is WhatsApp not working when I have configured it?
WhatsApp rides on the same provider credentials as SMS, so it only becomes usable once both are configured. Set the SMS account SID, auth token and sending number first, then add the WhatsApp sending number in whatsapp:+1234567890 form.
What happens if I mistype a placeholder?
It is left as-is rather than failing, so the message still goes out with the literal token in it. Previews are truncated: {diff} and {detected_change} at 500 characters, {previous_content} and {new_content} at 200.
Which channels does a linked device relay through the cloud?
Only email, pushover, SMS, WhatsApp and Signal — the ones that need cloud credentials. Webhook, Slack, Discord and Telegram are delivered by the device itself. A relay call takes 1 to 8 channels, a title up to 200 characters, a message of 1 to 4000, and an optional link up to 2000.
Can I turn off security alerts?
No. Security alerts, payment problems and team invites are locked cells: they are always on, and the preferences API will not store an override for them. Everything else in the grid is yours to set.

end ▸ wire it

Send yourself a test.

Configure one channel, add a recipient, and fire the test send before you need it.