Email, Discord, Slack
Who receives what, through which channel, and why an alert sometimes doesn't go out.
Updated on 25 September 2026
On this page
- What PostShip sends
- The email subject
- When several URLs go down together
- “It may not be you”
- Set it up
- What triggers an alert, and what holds it back
- Limits and plans
- Troubleshooting
- Why did the email arrive but not the Discord message?
- Why don't I receive recoveries?
- Connect Discord
- Create the webhook
- What you receive
- Disconnect or change channel
- Limits and plans
- Troubleshooting
- Connect Slack
- Create the webhook
- What you receive
- Disconnect or change channel
- Limits and plans
- Troubleshooting
- Outbound webhook
- What PostShip sends
- Set it up
- What triggers a send
- Limits and plans
- Troubleshooting
- Daily digest
- What PostShip sends
- Set it up
- When it goes out
- Limits and plans
- Troubleshooting
An alert goes out when a URL fails, when it recovers, when its content changed after a deploy, or when the domain's DNS moved. Email is the base channel, on all plans. Discord and Slack, on every plan, and the outbound webhook, on a paid plan, receive the same thing in addition, project by project. Nothing is sent as long as everything is green: it's the product's principle, not an option.
What PostShip sends
4 kinds of messages, with the same label everywhere:
| Kind | Label | When |
|---|---|---|
fail | Failing | A URL just went into failure (or the outage changed nature: a 503 turned 404) |
recovered | Recovered | The URL goes back to green, and a failure alert had indeed gone out before |
mutated | Content changed | What changed, a latency anomaly or a third-party script that changed |
dns | DNS drift | The domain's records changed since the last deploy |
Each item carries the URL, a sentence naming the cause (“The page answers 200 but a static file is missing: /app.js.”, “The price no longer appears on …”), the HTTP status and the TTFB, and a link to the URL's detail. Each sentence comes from a table of codes, the same on all channels; nothing is generated by a model.
The email subject
Project name — 2 URLs failing, or — 1 URL recovered, or — DNS drift detected. The body starts with the recap (“2 failing, 0 recovered.”) then one card per URL. When the alert follows a deploy, the recap says “Since the last deploy: …”.
When several URLs go down together
From 3 URLs going down at the same time for the same reason — the server doesn't respond, or it answers the same 5xx error — PostShip doesn't make 3 cards but a single one, titled Site unreachable · 7 URL or Server error 503 · 7 URL, with the matching sentence: “7 URLs stopped responding at the same time: it's the whole site, not these pages. Look at the host or the DNS before the pages.” The list stops at 6 URLs, then “… and 3 more”. A content failure (missing tag, missing file) is never grouped: it isn't a site outage.
“It may not be you”
When something goes down, PostShip reads the state of the providers your site uses (Vercel, Cloudflare, Supabase…). If one of them reports an incident, the alert adds under the recap: “It may not be you: Vercel reports an incident (Elevated errors on deployments).” It's the first thing to know before searching on your side.
Set it up
- Email: Account → Notifications. 3 checkboxes: “A URL fails” (unticking turns off all alert emails), “A URL is recovered”, “Content changed after a deploy”. These boxes only concern email.
- Discord, Slack: Project → Settings → Integrations, Outgoing section, one card per channel. See Connect Discord, Connect Slack. These channels receive everything, recoveries included.
- Test: the “Send a test alert” button on Integrations sends a real alert, on all connected channels, respecting the project's rules. 3 tests per minute at most.
What triggers an alert, and what holds it back
A failure alert goes out at the transition: when the streak of consecutive failures reaches the number chosen in Rules (1 by default). A page down all night writes once, not 144 times. It writes again only if the outage changes nature. An identical alert (same URL, same fingerprint) is never sent again in less than 10 minutes.
What holds a send back, on all channels at once:
- Muted alerts (“Mute 1 h / 4 h / 24 h” on the Incidents page or in Rules).
- A maintenance window, one-off or recurring, in progress.
- The project's quiet hours.
- The silencing of a precise URL (“Mute alerts for 4 h” in a URL's menu): only that URL is removed from the message.
In all these cases the check did run and the history is written: it's the sending that stops, not the monitoring.
Limits and plans
| Discover | Pro | Pro Plus | |
|---|---|---|---|
| Yes | Yes | Yes | |
| Discord, Slack | Yes | Yes | Yes |
| Outbound webhook | No | Yes | Yes |
| Indexability and DNS drift alerts | No (visible in the app) | Yes | Yes |
See the plans.
Troubleshooting
Why did the email arrive but not the Discord message?
A webhook deleted on Discord's side answers 404: PostShip treats it as a failure, doesn't record it as sent, and the email goes out anyway. Reconnect the channel from Integrations. Each channel is independent: one's outage doesn't prevent the others.
Why don't I receive recoveries?
Either the “A URL is recovered” box is unticked on your account, or no failure alert had gone out for that outage (for instance with a confirmation at 3 failures and a 2-run outage): PostShip doesn't announce the recovery of an incident it never reported to you.
Connect Discord
Receive alerts in a Discord channel by pasting a webhook address. On every plan.
Discord receives alerts through a webhook: an address Discord creates for a channel, which you paste into PostShip. No bot is added to the server, and it's included in every plan, Discover too. All it takes is someone with the “Manage webhooks” permission giving you the address.
Create the webhook
The webhook is created from Discord, in the channel's settings.
- In Discord, right-click the channel → Edit channel → Integrations → Webhooks.
- Create a webhook, give it a name (“PostShip”), then click “Copy webhook URL”.
- In PostShip, open Project → Settings → Integrations, paste the URL in the Discord card and save. Only a
https://discord.com/api/webhooks/...address is accepted.
“Test alerts”, on the same page, sends a test message to the channel.
What you receive
One message per check that changes something, posted under the name “PostShip”: a box coloured by state (red for failing or DNS drift, green for recovered, orange for content changed), the title (“2 URLs failing”, “Site unreachable · 7 URL”), the recap, then one field per URL with the failure reason in plain words, the HTTP status and the TTFB, and a link to the URL's page. At the foot: “Dashboard · Incidents · Manage alerts”. Beyond 8 URLs in the same message, the others are counted (“+3 more URLs on the dashboard”). The detail of the labels is on the alert channels.
The same channel also receives the team messages, in plain text: “marie takes charge of the incident” (taking charge), the escalation and, on a paid plan, the daily digest.
Disconnect or change channel
The “Disable” button deletes the saved URL: PostShip immediately stops writing there. To change channel, paste the new channel's webhook instead. To silence alerts without disconnecting anything, use “Mute 1 h / 4 h / 24 h”, the quiet hours or a maintenance window.
Limits and plans
| Discover | Pro | Pro Plus | |
|---|---|---|---|
| Discord alerts | Yes | Yes | Yes |
| Daily digest in the channel | No | Yes | Yes |
The plan that counts is the project owner's. See the plans.
Troubleshooting
Why is the URL refused?
Only https://discord.com/api/webhooks/... addresses are accepted. A server invite link or a channel's address isn't a webhook: follow the steps above to create one.
Why does an @everyone in my alert mention nobody?
Deliberate: what comes from your site (a page title, an excerpt of changed content) is neutralised before being written in the message, so that a text fetched from a monitored page never rings the whole team.
Connect Slack
Receive alerts in a Slack channel by pasting an incoming webhook address. On every plan.
Slack receives alerts through an incoming webhook: an address Slack gives you for a channel, which you paste into PostShip. It's included in every plan, Discover too.
Create the webhook
The incoming webhook is created from your own Slack application.
- Go to https://api.slack.com/apps and create an application (“From scratch”), attached to your workspace.
- In the left menu, open “Incoming Webhooks” and enable the option.
- Click “Add New Webhook to Workspace”, choose the channel, authorise, then copy the URL starting with
https://hooks.slack.com/services/. - In PostShip, open Project → Settings → Integrations, paste the URL in the Slack card and save. Only a
hooks.slack.comaddress is accepted.
“Test alerts”, on the same page, sends a test message to the channel.
What you receive
A structured message: a title with the project and the state (“My site — 2 URLs failing”), a reminder of the number of failing, recovered or changed URLs, one section per URL with the failure reason in plain words, the HTTP status and the TTFB, and 3 buttons: “Open the dashboard”, “Incidents”, “Manage alerts”. The main button turns red when something is failing or a DNS drift is detected. Beyond 8 URLs, the others are counted. The detail of the labels is on the alert channels.
The same channel also receives the team messages in plain text: taking charge, escalation and, on a paid plan, the daily digest.
Disconnect or change channel
The “Disable” button deletes the saved URL. To change channel, create a webhook for the new channel and paste it instead: a Slack incoming webhook is tied to a single channel. To silence alerts without disconnecting anything, see Rules and Maintenance.
Limits and plans
| Discover | Pro | Pro Plus | |
|---|---|---|---|
| Slack alerts | Yes | Yes | Yes |
| Daily digest in the channel | No | Yes | Yes |
The plan that counts is the project owner's. See the plans.
Troubleshooting
Why is the URL refused?
Only https://hooks.slack.com/... addresses are accepted. A channel's address (https://acme.slack.com/archives/...) isn't a webhook: follow the steps above to create one.
Why does my Slack message only show 8 URLs?
Slack limits the size of a message. The following URLs are counted (“+4 more URLs — see the dashboard”) and the Incidents page lists them all.
Outbound webhook
Available on the Pro and Pro Plus plans.
Receive every alert as HMAC-signed JSON on your own endpoint, and choose the events.
In addition to email, Discord and Slack, PostShip can send every alert as a POST to an HTTPS URL of yours, signed with HMAC-SHA256. It's the channel for automations: open a ticket, cut a deploy, turn on a lamp. It obeys the same rules as the other channels (silence, quiet hours, confirmation, maintenance) because it receives exactly the same batch of items, once filtered.
What PostShip sends
One request per kind of event: a batch that mixes a failure and a recovery becomes 2 requests, one fail and one recovered.
POST /hooks/postship HTTP/1.1
Content-Type: application/json
X-PostShip-Event: fail
X-PostShip-Version: 1
X-PostShip-Delivery: 6d0c1a2e-…
X-PostShip-Signature: sha256=3f1a…e9c2{
"version": 1,
"id": "6d0c1a2e-…",
"event": "fail",
"projectId": "8b2f…",
"projectName": "My site",
"at": "2026-09-11T14:03:12.418Z",
"items": [
{ "url": "https://mysite.fr/checkout", "httpStatus": 503, "missing": null },
{ "url": "https://mysite.fr/", "httpStatus": 200, "missing": ["asset:script:/app.js"] }
],
"test": false
}| Field | Value |
|---|---|
version | The payload version, 1. It only changes when something breaks: an added field doesn't change it, a renamed or removed field does |
id | The delivery id, also in X-PostShip-Delivery. A replay keeps the same one: deduplicate on it |
event | fail, recovered, mutated or dns |
projectId, projectName | The project |
at | The time of sending, ISO 8601 in UTC |
items[].url | The URL concerned (the host name for dns) |
items[].httpStatus | The HTTP status, or null without a response |
items[].missing | The failure codes (asset:…, price_token, meta_noindex…), or null |
test | true only for the “Send a test” button |
The signature
X-PostShip-Signature is sha256= followed by the hexadecimal HMAC-SHA256 digest of the request's raw body, computed with your secret. Verify it before reading the content: without it, anyone who knows the URL can make you open a ticket.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(rawBody, signature, secret) {
const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(signature ?? "");
return a.length === b.length && timingSafeEqual(a, b);
}The body must be read as is (raw text) before any JSON decoding: the signature covers the bytes sent, and a re-serialisation changes the order of the keys or the whitespace.
Replay
Every delivery is kept as sent — the last 20 per project, with the status received — in the Outbound webhook card. “Replay” sends the same body under the same id, signed with the current secret, with an extra X-PostShip-Replay: true header. A receiver that deduplicates on id won't open 2 tickets; a receiver that was down gets what it missed. Replaying is a project editor's action, never automatic.
What PostShip requires of your endpoint
- A public HTTPS URL. A private or local address, or one that resolves to an internal network, is refused at registration, and again on every send.
- A 2xx response in under 4 seconds. Beyond that, the send is abandoned, without retry.
- No redirect: a 3xx isn't followed, it counts as a failure. The signed body must not be handed to another host than the one you declared.
An endpoint that fails prevents neither the other channels nor the next run: the error is noted on PostShip's side, nothing else.
Set it up
Project → Settings → Integrations, “Outgoing” section, “Outbound webhook” card.
- Paste the URL in “Outbound webhook URL” and save.
- Click “Regenerate the secret”. The secret (64 hexadecimal characters) is shown only once: “Copy this secret now — it will never be shown again.” Store it in an environment variable on your side. As long as no secret exists, nothing is sent.
- Click “Send a test”: a
failrequest with a singlehttps://example.comitem in 500 and"test": true. Your endpoint can thus tell a probe from a real incident. - Choose the “Events sent”: Failure, Recovery, Change, DNS. All ticked by default. Unticking everything is refused: “Choose at least one event — or disable the webhook.”
“Disable” erases the URL and the secret. “Regenerate the secret” invalidates the old one immediately.
What triggers a send
Exactly what triggers an alert on the other channels, see Email, Discord, Slack and Rules: failure transition, recovery of a reported incident, content changed after deploy, DNS drift. Sends held back by silence, quiet hours or a maintenance aren't replayed. Sorting by event happens here and nowhere else: the other channels receive everything.
Limits and plans
| Discover | Pro | Pro Plus | |
|---|---|---|---|
| Outbound webhook | No | Yes | Yes |
See the plans. To read a project's state rather than waiting for it to write to you, see the API.
Troubleshooting
Why does “Send a test” answer “Set the URL first and generate a secret”?
Both are needed: without a secret, PostShip doesn't sign, so doesn't send. Click “Regenerate the secret”.
Why does the signature never match?
You're probably verifying the re-serialised JSON rather than the raw body. Read the body as text before JSON.parse, compute the HMAC on it, and compare with the value after sha256=.
Why does my endpoint receive fail but never recovered?
Look at the “Events sent” boxes. And a recovery is only sent if the failure had been: see Rules.
Daily digest
Available on the Pro and Pro Plus plans.
A message every morning at 8 am on your channels — the night's rate, open incidents, yesterday's deploys.
An alert says something is happening; silence doesn't say everything is fine, it says just as well that PostShip went down. A message at a fixed time, every day, is the only proof that a silence is a good silence. And for a team, it's the morning check-in without a meeting: the night's rate, what is still red, what was deployed. It goes out on the project's channels, never by email.
What PostShip sends
At 8 am, in the project's time zone, on each connected channel (Discord, Slack), a text message:
All green — My site
Success rate over 24 h: 99.7% (720 checks)
No production deploy yesterday.
Error budget (99.9%): 3 h 12 min left.
https://postship.fr/…1 ongoing incident — My site
Success rate over 24 h: 96.2% (720 checks)
• https://mysite.fr/checkout — /checkout answers 503.
2 production deploys over 24 h.
https://postship.fr/…Line by line:
| Line | Where it comes from |
|---|---|
| The title | “All green” or “N ongoing incidents”, preceded by a green or red dot, and the project name |
| The rate | Successful runs over the last 24 h, out of the total; “No check in the last 24 h.” if there was none |
| The incidents | Up to 5 failing URLs with the sentence naming the cause, then “… and N more” |
| The deploys | The production deploys recorded over 24 h, see Deploy webhooks |
| The error budget | Only if a target is set (Settings → Monitoring, “Uptime target” row): the downtime you have left over the window, or “exceeded” |
| The link | The project's dashboard |
Short, on purpose: you read it on a phone.
Set it up
Project → Alerts → Notifications, “Every morning” section — or under the 3 channels, in Project → Settings → Integrations: the “Digest every morning at 8 am” switch. It saves by itself. Without any connected channel, it stays greyed out and the row says so: “Connect a channel first.”
The time isn't adjustable, the time zone is: it's the project's quiet hours zone (Project → Alerts → Rules, “Quiet hours” card, Time zone field), the only time zone a project knows. Without a setting, Europe/Paris.
When it goes out
PostShip's engine runs every 2 minutes, whatever the plan. The digest goes out on the first run after 8 am local time that finds it hasn't gone out yet today: a run missed at 8:00 doesn't push the digest to tomorrow, it goes out at 8:05. One per local calendar day, whatever the number of runs; 2 overlapping runs cannot send it twice.
It goes out even if everything is green: that's its role. It also goes out during quiet hours, a maintenance or a “Mute 24 h”: an appointment isn't muted like an alert. A channel that answered an error (webhook deleted) doesn't block the others.
Limits and plans
| Discover | Pro | Pro Plus | |
|---|---|---|---|
| Daily digest | No | Yes | Yes |
The plan that counts is the project owner's. A project that fell back to Discover keeps its setting but no longer receives anything, like its channel alerts. See the plans.
Troubleshooting
Why does the digest arrive at 9 am?
The project's time zone isn't yours. Set the quiet hours' Time zone field in Rules: the digest follows it.
Why didn't the digest arrive this morning?
3 possible causes: no connected channel (the checkbox flags it), all channels refused the message (webhook deleted, bot removed from the group: reconnect from Integrations), or the owner's plan no longer allows it.
Why does the digest's rate differ from the Status page's?
The digest counts all the project's runs over 24 h, all URLs together. The status page only counts the components it displays, over its own window. See Status page.