If a line gets throttled or flagged, you will usually not get an error. The provider accepts the request, returns a success, and delivery quietly degrades. Businesses routinely lose a week to this, and the week is entirely avoidable.
The four signals, in order of how early they fire
| Signal | Fires | What it means |
|---|---|---|
| Canary miss | Within the hour | Something is wrong right now |
| Time-to-delivered creeping up | Hours before a rate drop | Throttling starting |
| Delivered rate falling | Same day | Already losing messages |
| Reply rate falling | Days later | You found out late |
The canary
The cheapest monitoring you will ever build: one scheduled message to a phone you control, on each line, every hour. If it does not arrive, alert. Twenty minutes of work and it catches the failure mode that matters most.
// Hourly: send a canary on every active line.
export async function sendCanaries() {
for (const line of await activeLines()) {
const key = `canary:${line.id}:${new Date().toISOString().slice(0, 13)}`;
await sendMessage({
to: process.env.CANARY_PHONE!,
body: `canary ${line.id} ${new Date().toISOString()}`,
idempotencyKey: key,
lineId: line.id,
});
}
}
// Also hourly, offset by 15 minutes: did the last one arrive?
export async function checkCanaries() {
for (const line of await activeLines()) {
const delivered = await db.messageEvents.findFirst({
where: {
type: "delivered",
message: { lineId: line.id, play: "canary" },
occurredAt: { gt: minutesAgo(75) },
},
});
// No delivered canary in 75 minutes means the line is not delivering,
// whatever the API said when we sent it.
if (!delivered) await alert(`Line ${line.id}: no canary delivered in 75m`);
}
}The canary and its checker. The point is the second half — sending is useless without something that notices the absence.
Thresholds worth alerting on
- Delivered rate under 90% over a rolling hour, with at least 20 sends in the window. The volume floor stops a quiet Tuesday paging you.
- Median time-to-delivered more than double the 7-day median. This is the earliest reliable warning.
- Any new failure reason appearing that has not been seen in 30 days. New reasons are more informative than volume.
- Opt-out rate on a single play above your own baseline, which tells you the content is the problem rather than the line.
Alert on the ratio, not the count
"20 failures today" is meaningless without the denominator — it is fine on 5,000 sends and a crisis on 40. Every threshold here is a rate with a minimum volume attached, and getting that wrong is why most messaging alerts get muted within a fortnight.
What to do when it fires
- Stop the scheduled sends. Not the replies — the outbound campaigns. Pushing more volume into a degraded line makes it worse.
- Check whether it is one line or all of them. One line is your sending behaviour; all lines is the provider.
- Tell the provider, with numbers. "Delivered rate on line X fell from 98% to 61% at 14:00" gets a useful response. "Messages aren't working" does not.
- Switch the play to your fallback channel while you wait. This is why you keep one.
- When it recovers, resume slowly. Treat it as a fresh warm-up rather than returning to previous volume. The ramp.
And afterwards, work out what caused it — almost always volume ramped too fast, message bodies too similar, or too many cold first-contacts. The usual causes.