One day on the pager
A Tuesday with a bad night in it, and every control the app has, in the order it came up.
Nothing below is unusual. It is one ordinary day of one person carrying one phone, and it happens to use every field a send takes and every control the phone has over what arrives. The times are local; the sends are the real ones.
the six channels this day runs on
None of them was made that morning. A channel is a name, an address and a short list of defaults: how loud it is allowed to get, what it does at night, and who it rings if nobody answers. The address is the whole integration. Whatever holds it can post to that channel and to nothing else, so the deploy script and the database monitor never have to know about each other.
| channel | what posts to it | highest priority | and |
|---|---|---|---|
| deploys | the release script, before and after | normal | the script mutes it for the length of a restart |
| builds | CI, on a red run | loud | quiet hours 19:00–07:00 |
| jobs | the queue consumer and the nightly import | loud | — |
| api-health | the health check, whenever it flaps | loud | storm control folds a flap into one card |
| paging | the monitors that mean get up | page | repeats every 2 m, escalates to a partner |
| backups | the nightly backup, when it lands | page | a vital: the alert is the beat not arriving |
Four of the six are ordinary channels, sitting there waiting to be written to. The other two are vitals, which work the other way round: nothing posts to say something broke, something posts to say it is still alive, and the alert is the beat that never comes. Both kinds share one list on the phone. The vitals go at the top as a wall of dots, because there is only ever one thing to know about each.
Nothing in the commands below chooses a sound, a repeat, an escalation partner or a quiet window. All of that lives on the channel and is set once. A send asks for a level and passes the text. priority=page asks for the top of the channel’s range, and the highest priority in the table above decides whether it gets there.
Tuesday
the deploy silences its own channel
The restart is going to produce every error it always produces. The script mutes the channel for ten minutes before it starts. The mute has its own end time, so a script that dies halfway through cannot leave you deaf.
$ curl -X POST https://api.tissue.systems/v1/mast/channels/mc_2b7d…/mute \
-H "Authorization: Bearer $TISSUE_TOKEN" \
-d '{"until": "2026-09-15T14:22:00Z"}'
a channel mute, from an account token rather than a channel key
the deploy finishes and says so, quietly
Nothing needs to happen. You want the line in the feed for the next time you are working out when something changed.
$ curl -d silent=1 -d body="deployed 2026.09.029 · s10, s33" "$MAST_URL"
silent: stored, no push
a build breaks while you are making coffee
Not a page. It can wait until you sit down, but you want it on the lock screen and not buried in a list you check twice a day, and you want the run one tap away instead of going to look for it.
$ curl -d priority=loud \
-d title="nightly build failed" \
-d url="https://github.com/…/actions/runs/35055965614" \
-d url_title="Open the run" "$MAST_URL"
priority=loud · url · url_title
a queue starts failing, four hundred times
Every consumer retry posts. Without a key that is four hundred notifications. With one it is a single card counting up, and the phone buzzes once.
$ curl -d key=queue-consumer -d priority=loud \
-d title="consumer cannot reach redis" "$MAST_URL"
{"ok":true,"state":"deduped","duplicate":true}
key: the answer comes back deduped
a batch job reports what happened an hour ago
The job ran at ten past ten and only got around to telling you at eleven. Send the time the thing happened, not the time the request left.
$ curl -d timestamp=2026-09-15T17:10:00Z \
-d title="import finished" \
-d body="18,204 rows · 2 rejected" "$MAST_URL"
timestamp: unix time or ISO-8601
you go into a meeting
One tap on the phone. Everything is silent for an hour, pages included, and a banner stays up the whole time so you cannot forget you turned it on. A channel marked as breaking through still rings, and the pause screen names it while the window runs.
# the app → Pause pages → 1 hour
# banner: pages paused · until 14:00
# still rings: Home · smoke
a pause, and the one exemption, set on the phone and never from a token
something pages you anyway
It is held, not dropped, and the feed says so while the pause runs. Nothing else in Mast does this to a page. A mute would have delivered it at full volume, and quiet hours would have let it through too.
# 202 {"ok":true,"state":"queued"}
# the feed: held · pages paused until 14:00
held: a line in the message's own log
the meeting ends and the held page rings
You could have ended the pause early from the banner and had it ring straight away. Either way nothing was lost, and the card says how long it sat.
# the card: asked for page · held 22 min
the per-message log
a disk is filling, slowly
This is a page, because at some hour it will take the database with it. It is not an every-thirty-seconds page, though. Set the repeat to the pace of the problem, and give it a stopping point so it does not follow you into next week.
$ curl -d priority=page -d key=disk-var \
-d retry=600 -d expire=86400 \
-d title="/var 91% full" "$MAST_URL"
retry · expire · key
you clear 40 GB and the alert closes itself
The same key, one more field. Every device the page reached gets the quiet update, a partner's phone included, and the card says how long it was open.
$ curl -d key=disk-var -d resolve=1 \
-d title="/var back to 61%" "$MAST_URL"
{"ok":true,"state":"resolved","open_for":5700}
resolve: the answer carries open_for, in seconds
quiet hours start on the chatty channel
The build channel has nothing to say at night that cannot wait until morning. Quiet hours deliver silently instead of dropping the message, so the feed is still complete when you look.
# the app → the channel → Quiet hours → 19:00–07:00
per channel · a page ignores it
twenty alerts in five minutes
A flapping health check. Past the threshold the pushes collapse into one summary that counts up, and the individual messages are all still in the feed. A page is never the thing folded away.
# one notification: 34 messages · api-health
# answers come back {"state":"suppressed"}
storm control: twenty in five minutes, to begin with
Wednesday
the one you bought the app for
Replication has stopped. This sounds with the ringer switch off, through a Focus, through the channel's mute and through its quiet hours, and it repeats every two minutes until a human says otherwise.
$ curl -d priority=page -d key=repl-lag \
-d sound=klaxon -d title="db-01" \
-d body="no WAL segment in 11 minutes" "$MAST_URL"
priority=page · sound · key
you acknowledge it from the Dynamic Island
The repeat stops. Nothing is resolved and nothing is pretending to be. The incident is still open; you have only said that a person is awake. The log keeps both times.
# 03:04 acked · 6 min after fired
# still open
acknowledge is not resolve
the second one, and nobody answers it
After the escalation window the page moves to your partner's phone and rings there. It goes one person deep, on purpose. They can resolve it from their side, and it closes on yours.
# 03:31 escalated · partner
# nothing on the send says this — escalation
# is a property of the channel, set in the app
the escalation partner
the backup does not run
Nothing failed, so nothing sent, and that is the failure everything else misses. The vital was expecting a beat at 03:17, never got one, and paged on its own once the grace ran out.
17 3 * * * /usr/local/bin/backup.sh \
&& curl -fsS https://mast.tissue.dev/mk_8e2a…
a vital channel: a period, and how late is still not a problem
the beat arrives and closes its own page
You rerun the job by hand. The first beat after a silence closes the page that silence opened, with nothing extra to send.
# 07:20 the crontab line runs
# 202 {"ok":true,"state":"alive"} — no card, so no id
# the open page: resolved · open for 1 h 20 m
one beat, two jobs
you read the day back
All of it is in one feed, including the sends that made no sound: the silent deploy line, the folded storm, the page that was held through the meeting. Each card carries its own history.
# fired → escalated → acked → resolved
# with the time of each
the per-message incident log, kept for thirty days
All eighteen land in the same list, in the order they happened, including the ones that made no sound.
where each control appeared
So that nothing is hiding in the story.
| control | in the day |
|---|---|
| title, body | throughout |
| priority | 09:05 loud · 02:58 page |
| key | 10:20, 16:10, 17:45, 02:58 |
| resolve | 17:45 |
| ack | not in this day. It makes an ordinary alert wait for a human the way a page does |
| retry, expire | 16:10 |
| url, url_title | 09:05 |
| sound | 02:58 |
| silent | 07:41 |
| timestamp | 11:02 |
| channel mute | 07:12, expiring on its own |
| quiet hours | 19:00 |
| the ceiling | set on all six channels, though none of them had to clamp anything down on this day |
| pause, and breaking through one | 13:00, 13:38, 14:00 |
| storm control | 22:31 |
| vitals | 06:00, 07:20 |
| the escalation partner | 03:31 |
Two of the eighteen moments are a person answering, and one is a job answering on its own. The rest of the day the phone is silent, which is where a pager sits almost all of the time.
The overview and the price are on the Tissue Mast page, and everything else is in the docs.