Recipes
A problem, and the send that does it. Grouped by what went wrong, not by which field fixes it, because that's the order you arrive in. Each one is a single request, with nothing to install. Every field is on Sending alerts.
Ordinary sends
A line of text is a whole send
Nothing else is required. The body is the message, and the channel supplies the priority.
$ curl -d "replication stopped" https://mast.tissue.dev/mk_1c9f…Form-encoded, JSON or a bare body, by POST or GET. Use whichever your sender can already do.
Say which machine, then what happened
The title is the thing, the body is what it did. On the lock screen you read the title first, so make it the word you would search for at three in the morning.
$ curl -d title="db-01" \
-d body="no WAL segment in 11 minutes" https://mast.tissue.dev/mk_1c9f…title up to 250 characters, body up to 4096. One of the two is required.
Tell me without waking me
Deploys, backups that worked, the nightly report. Keep the record without the sound. A channel can default to this so the sender never has to say it.
$ curl -d title="2026.09.029" -d body="live on both edges" \
-d priority=quiet https://mast.tissue.dev/mk_1c9f…priority is quiet, normal, loud or page, and falls back to the channel's setting.
Give me somewhere to go
The notification gets a tap target: the failing CI run, the dashboard, the log line. You are holding the phone in the dark. Put the run one tap away instead of making yourself find it.
$ curl -d title="release 2026.09.029" \
-d body="build failed on the tag" \
-d url="https://github.com/…/runs/35055965614" \
-d url_title="Open the run" https://mast.tissue.dev/mk_1c9f…url and url_title. Only http:// and https://. A custom scheme in a public URL would be a way to reach another app on your phone.
Let me know what it is before I read it
A different sound per channel, or per send. Disk alerts and door sensors do not have to be told apart on the screen.
$ curl -d body="front door opened" \
-d sound=chirp https://mast.tissue.dev/mk_1c9f…sound overrides the channel's sound for one message. Six ship with the app: default, bleep, chirp, klaxon, pager and rising.
Write it down, but do not notify me
The feed becomes a log. Record every deploy, every sweep, every probe that came back fine, and let only the failures make a sound. When something does break, the run-up to it is in the same place.
$ curl -d body="sweep ok, 1,284 rows" \
-d silent=1 https://mast.tissue.dev/mk_1c9f…silent=1 stores the message and sends no push at all. It cannot be combined with ack or with a page, because a page nobody sees would repeat forever.
The event happened before the send did
A batch job that collects failures and reports them at the end can say when each one actually happened.
$ curl -d body="job 4 failed" \
-d timestamp=1789412264 https://mast.tissue.dev/mk_1c9f…timestamp takes Unix time or ISO-8601 and is range-checked. The feed still orders on arrival, so a sender with a wrong clock cannot reorder your inbox.
Incidents
Four hundred failures, one card
A checkout endpoint starts throwing and every request pages you. Give the incident a name and the repeats fold into the message already on the phone, counting up in place.
$ curl -d priority=page -d key=checkout-500 \
-d title="checkout" -d body="500 from /pay" https://mast.tissue.dev/mk_1c9f…
{"ok":true,"state":"deduped","duplicate":true}key, up to 120 characters. Repeats inside the channel's dedupe window fold; past it, a new card. The answer names the message they folded into.
Close it when it comes back
The card on the phone stops repeating, turns green, and its text is replaced by the recovery text. Every device the page reached gets the quiet update, a partner's phone included. On most alert apps a recovery is one more banner arriving underneath the one that is still going off. Here it replaces it.
$ curl -d resolve=1 -d key=checkout-500 \
-d title="checkout recovered" \
-d body="error rate back to zero" https://mast.tissue.dev/mk_1c9f…resolve=1 closes the open message carrying that key. The answer says how long it was open, in seconds, as open_for.
Close the exact one I raised
For a sender that kept the id from its own answer and would rather not rely on a key matching.
$ id=$(curl -sS -d title="db-01 unreachable" -d priority=page \
https://mast.tissue.dev/mk_1c9f… | jq -r .id)
# …later
$ curl -d resolve=$id -d body="back up" https://mast.tissue.dev/mk_1c9f…resolve=<message id>. A resolve that finds nothing open is not an error. It is stored quietly and wakes nobody, so a recovery that overtakes its own alert still lands.
Make an ordinary alert wait for a human
Not everything that needs an answer deserves the pager tier. A loud that asks to be acknowledged repeats and escalates like a page, without sounding through the ringer switch.
$ curl -d priority=loud -d ack=required \
-d title="certificate expires in 3 days" https://mast.tissue.dev/mk_1c9f…ack=required. A page carries it already, and that is the difference between the two loudest tiers.
Stop it repeating every thirty seconds
A disk filling slowly does not need the interval a dead database gets. Set the repeat to the pace of the problem.
$ curl -d priority=page -d retry=600 \
-d title="/var 91% full" https://mast.tissue.dev/mk_1c9f…retry in seconds, 30 to 86,400, or 0 to alert once and stop. The channel has a default.
Give up after an hour
A queue that is backed up at midnight is usually drained by morning. If nobody has answered in an hour, stop the repeat. The card stays in the feed marked expired, so you can still see that it happened and that nobody answered.
$ curl -d priority=page -d expire=3600 \
-d title="queue depth 14k" https://mast.tissue.dev/mk_1c9f…expire in seconds, 60 to 604,800, or 0 for no expiry.
Wait for a human before the deploy continues
The script pages, then blocks on its own message until somebody acknowledges it from the phone or the watch. A channel key can read the messages it sent and nothing else.
$ id=$(curl -sS -d title="promote 2026.09.029?" -d priority=page \
https://mast.tissue.dev/mk_1c9f… | jq -r .id)
$ until [ "$(curl -sS https://mast.tissue.dev/mk_1c9f…/messages/$id \
| jq -r .state)" = "acked" ]; do sleep 10; doneNo extra field. The answer carries the id, and /messages/<id> under the same key carries its state, which device acknowledged it and when.
If I do not answer, wake somebody else
Nothing on the send says it. Who a page goes to next is a property of the channel: an escalation policy that walks your rotation, or, with no account, one partner who agreed to be woken.
Watching for silence
Page me when the cron job stops running
This is the failure nothing else catches: the job did not fail, it stopped. A vital expects to hear from you on a schedule, and when it does not, it pages, instead of waiting for a sender that is never going to call.
17 3 * * * /usr/local/bin/backup.sh \
&& curl -fsS https://mast.tissue.dev/mk_8e2a… >/dev/nullA heartbeat is a send with no text, which is why the crontab line can be one curl. The channel holds the period and the grace.
The job that knows it failed
Do not make it wait out the grace window. A job that has already seen its own exit code can flatline the vital immediately and say why.
$ curl -fsS -d body="backup exited 1" https://mast.tissue.dev/mk_8e2a…/fail/fail on the end of the channel URL, with an optional body.
The heartbeat closes the page its own silence opened
Nothing to send. When the job runs again, the red card turns green, stops repeating, stops escalating, and says how long the vital was flat. If several pages were opened while the failure changed shape, the beat closes all of them.
$ curl -fsS https://mast.tissue.dev/mk_8e2a…
{"ok":true,"state":"alive"}A beat arriving with no page open is a quiet card. There is nothing on the phone for it to answer.
Say what the job did, not just that it ran
A vital takes text like any other channel, and a send that carries text counts as proof of life too.
$ curl -fsS -d body="swept 1,284 rows in 41s" \
-d silent=1 https://mast.tissue.dev/mk_8e2a…Same URL, same fields. The beat and the report are one request.

Every recipe above, on one screen. The address the crontab line holds, then the period and the grace that decide when silence becomes late, then what a missed beat is allowed to do.
Silence, on your side
Restart the service without paging yourself
The deploy script silences its own channel for the length of the restart, and the silence expires on its own, so a script that dies halfway through cannot leave the channel muted forever. This one needs an API token from your Tissue Mast account: a channel key can send, and only send.
$ curl -X POST https://api.tissue.systems/v1/mast/channels/mc_2b7d…/mute \
-H "Authorization: Bearer $TISSUE_TOKEN" \
-d '{"until": "2026-09-16T05:20:00Z"}'POST /v1/mast/channels/<id>/mute with an instant. Sending until in the past clears it early.
Never at night, for this one channel
The chatty channel gets a window in which it is delivered silently. Set once on the channel, not on every send.
$ curl -X POST https://api.tissue.systems/v1/mast/channels \
-H "Authorization: Bearer $TISSUE_TOKEN" \
-d '{"name": "deploys", "quiet_start": 1320,
"quiet_end": 420, "quiet_tz": "America/Los_Angeles"}'quiet_start and quiet_end are minutes past midnight, and a window that wraps midnight is written the way you say it: 1320 to 420. quiet_tz takes an IANA zone name, resolved at the moment the window is judged, so it follows daylight saving. A fixed offset is accepted, but it cannot do that. It is one number, right for half the year.
This channel must never be able to page anyone
The ceiling belongs to the channel, and every send is clamped to it. A channel created from a token starts at loud, and only the app on a registered phone can raise it to the pager tier. No API token can do it, on create or on patch. So a leaked URL cannot promote its own alerts, and neither can the script that made the channel.
$ curl -d priority=page -d body="…" https://mast.tissue.dev/mk_1c9f…
# delivered loud, on a channel whose ceiling is loudThe message keeps the tier it asked for beside the tier it was delivered at, so the card can tell you asked for page · sent loud.
I am in a meeting
One tap silences everything for half an hour, an hour or two, critical alerts included, and shows you a banner the whole time so you cannot forget it is on. When it ends, everything that was held rings. End it early and they ring straight away.
# the app → Pause pages → 1 hour
# banner: pages paused · until 14:30On the phone, not in the API. A pause is the only thing in Mast that silences a page.
Except the smoke alarm
A channel can be marked as breaking through a pause, and then it sounds no matter what. The pause screen lists every channel that will still ring, by name, so the exception is never a surprise.
# the app → the channel → During a pause
# "Still sounds" / "Quiet"Per channel, in the app. The pause screen names them while it is running.
Twenty alerts in five minutes
A monitor with no memory sends one alert per check, so something that flaps sends one every thirty seconds. Past the threshold the pushes collapse into one summary card that counts up, and the messages are all still in the feed. There is nothing to configure at the sender, and a page is never the message that gets folded away.
# the 21st send in the window
{"ok":true,"state":"suppressed"}Twenty messages in 300 seconds to begin with, per channel.
Move a key without downtime
Someone committed the URL to a public repo, or the laptop it lived on walked off. Rotate, update the senders, and the old key keeps working for seven days while you find them all.
$ curl -X POST https://api.tissue.systems/v1/mast/channels/mc_2b7d…/rotate \
-H "Authorization: Bearer $TISSUE_TOKEN"A key that was never written down is not lost. /v1/mast/state carries the live key of every channel.