Mast for ham radio

Get paged for the spots you can't miss, and know when the shack Pi goes down.

Mast Pager is a pager app for iPhone and Android, and it's free: no price, no account, no subscription, no server to run. For a ham it does two jobs. A spot from HamAlert, the free service that watches the cluster, the RBN, SOTAwatch and pota.app for you, rings the phone even on silent and keeps ringing until you answer. And the Pi in the shack checks in with Mast every minute, so you find out when it goes down instead of finding out the next time you key up.

when the HamAlert app isn't enough

HamAlert's own app is free and it works. Use it for the everyday stuff. The problem is the one spot you need, an ATNO or a park you've been after all year. It shows up at 3 a.m., or while the phone is face down on the bench on silent. The notification pops once and sits there. You see it an hour later and the station is QRT.

When the spot lands and…HamAlert appMast
the ringer switch is offsilent bannerrings anyway on a page
you don't hear the first onethat was itrings again every 60 s until you tap Acknowledge
you sleep through itnothinga second phone in the house starts ringing too, iPhone or Android (Mast calls it the escalation partner)
you want to know it was deliveredyou don'tthe alert screen shows fired, delivered, repeated, acknowledged

Keep the everyday triggers on HamAlert's push. Send the few you'd get out of bed for to Mast. Every trigger has its own tick-boxes for what to do with a match: the app, Threema, telnet, URL. So both can be on at once.

what it costs: nothing

Mast Pager is free on the App Store and on Google Play, for iOS 17.2 and Android 12 or later. Install it and make a channel; there's nothing to sign up for. On its own the app gives you 2 channels, 1 vital and 1,000 messages a month, and everything on this page fits in that: a spots channel for HamAlert and a shack-pi vital for the Pi.

Heartbeats don't count as messages, so a Pi checking in every minute uses none of the 1,000. Only the spots that page you do, and 1,000 a month is about 30 a day.

If you want more, a free Tissue Mast account takes an email address and no credit card: 10 channels, 10 vitals and 10,000 messages a month, and your channels survive a reinstall or a new phone. You sign in from the app's settings and nothing you've set up changes. The paid plans are for checking websites every 30 seconds and for teams taking turns on call. Nothing on this page needs one.

how a spot gets to your phone

There's nothing to build. When a trigger matches, HamAlert can fetch a web address of your choosing. In Mast, every channel has a web address of its own; a channel is one pager line with its own name, its own volume and its own address. Paste the Mast address into HamAlert and you're done.

The whole path, left to right
activator on a summit or park self-spots, or the RBN hears their CQ SOTAwatch pota.app Reverse Beacon DX cluster HamAlert your triggers: band, mode, ref, source polls SOTA every 30 s, POTA every 60 s Mast your channel "spots" GET one URL, no retry on HamAlert's side your phone push rings again every 60 s until you tap Acknowledge

HamAlert watches the spots. Mast rings the phone. You supply the address in the middle.

setup, about ten minutes

You need Mast Pager on your phone (free, on iPhone or Android, no sign-up) and a HamAlert account with at least one trigger set up. If you already get HamAlert pushes, you're halfway there. Three things to do: make a channel, decide how hard it should ring, and paste the address into HamAlert.

Make a channel in Mast

In Mast, go to Channels and tap New channel. Call it spots. Set Highest priority to Page. That's the loudest the channel is allowed to get; if you leave it lower, the page you ask for in the next step gets turned down to match. The send address shows up as soon as the channel exists. Tap Copy.

Mast, the channel screen

spots

loud · 12 messages

Send address

mast.tissue.dev/mk_1c9f7d3e6b4a8052d7f31e6c9b40a2e58d7c3f16
CopyShare

Anyone with that address can page you. Treat it like a password. If it gets out, rotate it in the app; the old one keeps working for seven days so nothing breaks mid-swap.

Tell it how hard to ring

When a spot matches, HamAlert fetches your address with the spot details added on the end. Anything you've already written on the end stays there, and that's where you say how loud, how often, and for how long. Paste the address from Mast into the box, pick how you want to be woken, and copy the result.

Address builder

page rings through a silent phone. On an iPhone that takes one switch in the app's Settings, Critical alerts → Turn on, off until you press it; after that a page also gets past Do Not Disturb. On Android it plays the pager tone on the alarm stream, so vibrate and silent don't stop it, and the Waking you row in the app's Settings checks the rest for you, with a Fix button for each thing that would keep a page quiet. A page always has to be acknowledged, meaning you tap it to say you've seen it, so that box is locked for it. "Give up after: never" is fine for a shack alarm and wrong for a spot that's gone in half an hour.

If you're comfortable in a terminal (Terminal on a Mac, PowerShell on Windows), you can test the address right now. If not, skip this and use HamAlert's Simulate button in step 3, which does the same job.

curl -d "title=spots works" "https://mast.tissue.dev/mk_1c9f7d3e6b4a8052d7f31e6c9b40a2e58d7c3f16?priority=page&ack=required&retry=60&expire=900"

Anything after -d reaches Mast as a field. title= is the line you see on the lock screen, body= the smaller one under it. Mast answers with a single line, {"ok":true,"id":"mm_…","state":"queued"}, and the phone rings a second or two later. Tap Acknowledge to stop it.

Paste the address into HamAlert

  1. Sign in at hamalert.org and open Destinations.
  2. Under URL notifications, set the method to GET (the plain kind of web request; the other two are for programmers) and paste the address. Save.
  3. Open the trigger you care about. In its list of actions, tick URL next to the push one. Save that too.
  4. Open Simulate from the menu at the top. It's a page of its own: type in a callsign, a frequency, a mode and a source, and send it. HamAlert runs that spot past your own triggers as if it had come off the cluster, and everything ticked fires, URL included. The phone should ring in a few seconds.

That's it. Every trigger with the URL action ticked now pages you through Mast. HamAlert allows one address per account, so all of them ring the same way. If you want one trigger to page and another to just beep, that's the optional last section, two channels from one HamAlert URL.

what it looks like when it fires

HamAlert writes a one-line title for each spot and Mast shows it as is. Here's what a SOTA spot and a POTA spot look like on the lock screen.

Lock screen, held down to show the actions

MAST · SPOTS 03:12

SOTA HB9DQM/P on HB/BE-101 (14.062 CW)

repeating every 60s · gives up 03:27

Acknowledge Resolve Mute channel 1h

The same, for a park

MAST · SPOTS 16:40

POTA K1ABC in US-1518 (14.0620 CW)

repeating every 60s · gives up 16:55

Acknowledge Resolve Mute channel 1h

Acknowledge is right on the notification, one tap, no unlock. Resolve next to it says it's over, and Mute channel 1h quiets that channel. On an iPhone the page also sits in the Dynamic Island until it's answered, and the Action button, Siri or a Shortcut acknowledges it without the phone leaving your pocket. Until then it comes back every 60 seconds, and a second phone, if you've set one up as escalation partner, joins in. After 15 minutes it stops. That's about right: a POTA spot ages off pota.app 30 minutes after the last respot, and a SOTA pileup is mostly done in five.

Mast, the alert screen

DELIVERED · 03:12 · unacknowledged

SOTA HB9DQM/P on HB/BE-101 (14.062 CW)

spots · page

AcknowledgeResolve
fired · priority page
delivered · iPhone
repeated (1)
repeated (2)
escalated

Tap the notification and you get this screen: when it fired, that it reached the phone, and how long it's been ringing. Then decide if it's worth turning the rig on.

settings that decide whether it rings

Everything you put on the address can also be set as a default on the channel in the app, and the channel's ceiling always wins. Four settings to check before the first real spot.

SettingWhereWhat it does
Highest priorityon the channelThe ceiling. Ask for page on a channel capped at loud and you get loud.
Quiet hourson the channelHours when normal and loud wait until morning. page ignores them. In your own time zone.
Escalation partneron the channelA second phone with Mast Pager, iPhone or Android, that starts ringing after a delay if you haven't acknowledged. Free, no account on either phone.
Critical alerts (iPhone)once, in the app's SettingsLets page ring through the silent switch and Do Not Disturb. Off until you press Turn on under this device; iOS asks once per install. If you said no, it's Settings → Notifications → Mast → Critical Alerts on the iPhone.
Waking you (Android)in the app's SettingsReads the four Android settings a page depends on, Do Not Disturb access and the alarm volume among them, and offers a Fix for each one that's off. Send yourself a test page afterwards.
Mast, a channel's delivery settings
Default priorityLoud
Highest priorityPage
Quiet hoursOff
Escalation partnerKI7KLT's iPad

And two on HamAlert's side. Its Limits page caps you at 100 alerts an hour and won't let you switch that off, so a night of chasing can use it up. On the same page, turn on the limit for the same callsign, frequency and mode. Without it a station that gets respotted every two minutes pages you every two minutes.

POTA and SOTA: the spots that don't wait

A DXpedition is on for two weeks. A summit or park activator has a battery, a wire in a tree and maybe an hour before the weather turns. Here's how short the window is.

20 to 60 mina typical SOTA activation, first CQ to QRT
under 5 minthe pileup after a fresh spot
30 minuntil a POTA spot ages off pota.app without a respot
16,937POTA spots through HamAlert on one recent day. SOTAwatch the same day: 390
One activation, and where you find out
0 10 min 20 30 40 activator on the air pileup packed up, walking down spot goes up self-spot, or the RBN matched their CQ page rings, seconds later you happen to look at the phone 20-odd minutes in, if it was a normal notification

The spot comes with no warning. A self-spot goes out on one bar of cell signal; RBNHole posts when the skimmers hear the first CQ. By the time you notice a quiet banner, the pileup is over.

HamAlert has a condition for most of this already. Here are five triggers to start with, using HamAlert's own names for the conditions. Add the trigger, tick URL in its actions, done. The ones that mention "the bridge" need the optional last section; skip that line if you're on one channel.

Summits in my own associations · SOTA, a chaser's staple

Source = SOTAwatch, SOTA summit = the associations you can hear from home, say W7W and W7O, plus your bands and modes. HamAlert checks SOTAwatch every 30 seconds. QRT spots are filtered out, so they never ring you.

Title: SOTA W7ABC on W7W/LC-001 (14.062 CW). Summit name, height and points come along as separate fields; the bridge Cell below can put them in the body.

Summit-to-summit while I'm out · the one trigger that should page

Same conditions, no summit filter, trigger comment s2s. Turn it on the night before, off when you're home. On the summit the phone is in the pack and the radio is in your hand; a page is the only thing you'll notice, and on an iPhone the Action button acknowledges it without taking the phone out. HamAlert sends the trigger comment as triggerComment, so the bridge can send s2s to page and the rest to loud.

An S2S is chaser points for both of you, on top of the activation.

The parks I'm still missing · POTA, by reference

Source = POTA, WWFF/POTA reference = the parks you still need. References look like US-0001 or CA-5082. The old K- prefix went away in March 2024. HamAlert polls pota.app once a minute and throws out anything older than five, so the spot is fresh.

Title: POTA K1ABC in US-1518 (14.0620 CW). The park reference and name come along as wwffRef and wwffName.

Sixteen thousand POTA spots a day will bury a pager. Filter by reference, state, or band and mode. Leave the wide-open trigger on HamAlert's push.

Park-to-park from the picnic table · POTA, while activating

Source = POTA, the band you're on, trigger comment p2p. The other activator is on the air about as long as you are. Enable it when you set up, disable it when you pack up.

Did my own spot go up? · the confirmation trigger

Full callsign = yours. When you self-spot on one bar, or count on the RBN to spot you, this tells you it went through. Keep it quiet, not a page. With the bridge, route it by trigger comment. Without, leave it on HamAlert's push.

Why spots come out of nowhere. Plenty of summits and parks have no cell coverage. RBNHole watches the RBN for a CQ from a call with a posted SOTA alert and spots it; POTA's RBN gateway does the same for a scheduled activation. The spot goes up the moment they start calling, and the pileup starts with it.

is the shack Pi still up?

Most shacks have a Pi doing something nobody watches. A Pi-Star or WPSD hotspot. An AllStarLink node. A Direwolf iGate, a Winlink gateway, rigctld for the remote rig, a weather station feeding CWOP. It runs for months. Then one day you key up and nothing happens. On the AllStarLink forum one owner only noticed because the LEDs were solid instead of blinking. Pi-Star users talk about hotspots that lock up about once a week. In every case someone had to walk over and look.

A Pi can't tell you it's dead. And from the outside, every way it dies looks the same. So turn it around: have the Pi check in with your phone every minute, and let the phone notice when it stops.

Six different failures, one symptom
power supply sagged, Pi rebooted SD card gave up Wi-Fi power save dropped the link router rebooted, Pi got a new IP the internet is down, Pi is fine mmdvmhost crashed, Pi is fine no heartbeat arrives grace runs out shack-pi stopped reporting your phone, loud or page Mast doesn't know which one it was. You know to go look, and the /fail step below covers the ones the Pi can name.

Mast calls that kind of channel a vital. The Pi fetches the channel's address on a schedule, with nothing attached; that's the heartbeat. When the gap between heartbeats runs past the period plus a grace, the phone rings: shack-pi stopped reporting. The next heartbeat that gets through closes it and sends a quiet shack-pi recovered. An internet blip at 4 a.m. that fixes itself leaves nothing for you to clean up.

The heartbeat starts on the Pi and goes all the way out to the internet. One line checks the power, the SD card, the Wi-Fi, the router and the ISP at once. A watchdog that runs on the Pi and logs to the Pi tells you nothing when the Pi is the problem.

You'll need to type a few commands on the Pi. SSH in from your computer, or plug in a keyboard and screen. On Pi-Star, the web dashboard has a terminal under Configuration → Expert → SSH Access, and you type rpi-rw first because Pi-Star keeps the card read-only until you do.

Make a vital, and pick how late is too late

New channel, pick Vital, call it shack-pi. Two numbers. Heartbeat period is how often the Pi checks in. Grace is how long past that Mast waits before it rings you. Under When it stops pick Page if a dead hotspot should wake you up, or Loud if it can wait till morning.

When does the phone ring?

last heartbeat next beat within 1 min grace 5 min phone rings

One minute is right for something you want to know about now. For a Pi on a cellular link, or a weather station that only uploads every ten minutes, hourly is fine, and that's where a new vital starts: every hour, ten minutes of grace. The grace covers the normal reasons a beat is late: slow DNS, a Wi-Fi reconnect, the router rebooting overnight. Five minutes of grace on a one-minute beat is five missed beats before anyone gets woken up.

Send the heartbeat

The heartbeat is one command, curl, which fetches a web address. It's already on the Pi, on Windows and on a Mac. The machine's own scheduler runs it every minute. Pick your machine:

The scheduler on Linux is called cron. Type crontab -e. The first time it asks which editor to use; pick nano. Add this line at the bottom, then Ctrl-O, Enter, Ctrl-X to save and leave:

* * * * * curl -fsS -m 20 https://mast.tissue.dev/mk_7b2e4c91a3d5f60817e2b9c4d5a6f7081920a3b4 >/dev/null 2>&1

The five stars mean "every minute". -fsS keeps curl quiet unless something breaks, and -m 20 makes it give up after 20 seconds so a stuck request can't pile up behind the next one. Nothing to install.

Watch it arrive

Within a minute the channel screen in Mast shows the last beat and how long ago, and the home-screen widget draws each vital as a dot, late ones marked. Now test it: pull the power on the Pi and wait six minutes. The phone rings. Plug it back in, and a minute or two after boot the page clears itself.

The flatline page, as Mast words it

MAST · SHACK-PI04:17

shack-pi stopped reporting

No heartbeat for 360s; expected every 60s.

Acknowledge Resolve Mute channel 1h

And the recovery, quiet

MAST · SHACK-PI04:31

shack-pi recovered

Heartbeat received after 14m 12s.

beat only when the program is running

The Pi being up isn't the same as the node being up. There's an AllStarLink thread where the Pi answers on the network, the web page loads, and the radio does nothing. So don't beat just because the Pi is on. Beat because the program you care about is running. Put a check in front of the curl, joined with &&, which means "and only if that worked". If the check fails, no beat goes out, and the vital does the rest.

What's on the PiPut this in front of the curl
Pi-Star, WPSDsystemctl is-active --quiet mmdvmhost && curl …
AllStarLinksystemctl is-active --quiet asterisk && curl …
Direwolf iGatepgrep -x direwolf >/dev/null && curl …
rigctld, remote rigrigctl -m 2 -r localhost:4532 f >/dev/null 2>&1 && curl …
Anything with a web pagecurl -fsS -m 5 http://localhost/ >/dev/null && curl …

Each check asks the Pi whether that program is running. The rig one in full, as it goes in the crontab:

* * * * * rigctl -m 2 -r localhost:4532 f >/dev/null 2>&1 && curl -fsS -m 20 https://mast.tissue.dev/mk_7b2e4c91a3d5f60817e2b9c4d5a6f7081920a3b4 >/dev/null 2>&1

Model 2 is Hamlib's network rig. This asks rigctld for the frequency. If rigctld is dead or the rig is off, it fails and no beat goes out.

Let the Pi say what's wrong

A missed heartbeat says something's wrong. The Pi can also say what: send a message to /fail on the end of the same address and Mast pages right away with your text, no waiting for the grace. The one every Pi should have is the power check. Low voltage is behind most of the lockups on the Pi-Star forum, and the Pi knows when it's happening. Save this as /home/pi/power-check.sh, run chmod +x /home/pi/power-check.sh once, and add the second line to your crontab.

#!/bin/sh
# /home/pi/power-check.sh   run from cron every 5 minutes
url=https://mast.tissue.dev/mk_7b2e4c91a3d5f60817e2b9c4d5a6f7081920a3b4
t=$(vcgencmd get_throttled | cut -d= -f2)
if [ $(( t & 1 )) -eq 1 ]; then
  # bit 0 = undervoltage right now. Tell Mast once, not every 5 minutes.
  if [ ! -e /tmp/undervolt ]; then
    curl -fsS -m 20 -X POST "$url/fail" \
      -d "body=Undervoltage right now ($t). Check the supply and the cable." >/dev/null
    touch /tmp/undervolt
  fi
else
  rm -f /tmp/undervolt
fi
*/5 * * * * /home/pi/power-check.sh

A /fail with no title shows as shack-pi failed, with your text under it. Same trick for anything the Pi can measure: disk almost full, CPU at 80 °C, SWR from the amp, an RTL-SDR that dropped off lsusb.

Three things that stop most of these pages

Pi or internet? If anything else in the house can run the same one-line heartbeat, a NAS, another Pi, a Home Assistant box, give it its own vital. If both go quiet, it's the internet or the power. If only the Pi goes quiet, it's the Pi.

Remote site. A Pi at the repeater site or in a friend's barn can't be power-cycled from the couch. The vital tells you it's down. Getting it back is a smart plug, a DTMF reboot on an AllStar node, or a drive. You still find out at 04:17 and not next Tuesday.

what HamAlert puts on the address

You can stop here. One channel, one address, spots and a shack heartbeat on your phone. The two sections below are for anyone who wants different triggers to ring in different ways, and they involve a little JavaScript.

HamAlert adds every detail of the spot to the end of the address, each as a named field. Mast reads the ones it knows and ignores the rest. From HamAlert's server code, the useful ones:

FieldExampleMast does
titleSOTA W7ABC on W7W/LC-001 (14.062 CW)uses it as the alert title
fullCallsign, callsignW7ABC/P, W7ABCignores; a bridge can use them
frequency, band, mode14.062, 20m, CWignores
sourcesotawatch, pota, rbn, clusterignores
summitRef, summitName, summitPointsW7W/LC-001, Mount Saint Helens, 10ignores
wwffRef, wwffNameUS-1518, the park's nameignores (POTA references travel in the WWFF fields)
spotter, snr, commentK1RA-4-#, 18, the spot's commentignores
triggerComments2signores; the bridge routes on it

The whole list is in hamutil.js in HamAlert's server. HamAlert doesn't retry. If Mast can't be reached in the second the spot lands, that spot is gone. It hasn't been a problem so far.

what tissue cloud is, and what a Cell is

The next section puts a small program between HamAlert and Mast, and that program runs on Tissue Cloud, our compute platform.

Tissue Cloud is hosting for small programs. You write a file, run one command, and a few seconds later it's answering at a web address. There's no box to keep patched, nothing to renew, nothing listening on your home line. That last one is why the bridge below can't just be another script on the shack Pi: HamAlert's servers have to be able to reach it, and a Pi behind your router isn't reachable without port forwarding, a dynamic DNS name and a certificate to keep on top of.

One of those programs is a Cell. A Cell is a folder with two files in it: your code, and a short ribo.toml saying what it's called and what it needs. The code is a fetch handler, which means a request comes in and you return a response, written in plain JavaScript and running in its own V8 isolate. It imports nothing of ours. The forty-odd lines in the next section are a whole Cell, start to finish. If you'd sooner write Rust or C, it takes WebAssembly too.

Things attach to a Cell one line at a time in that manifest: a SQL database, a bucket for files, a secret, a schedule. The bridge below uses a secret, so the two Mast addresses aren't sitting in the code. A longer version of it could keep every spot that ever paged you in a SQL table, and serve you a page to look at them on.

Tissue Cloud's free plan

It's called Spore. Nothing to pay, no card, no trial running out, and a Tissue Mast account, if you made one, signs you in to it too. What's in it:

5Cells
3scheduled jobs
1 GiBSQL storage
5 GiBfile storage
2MCPU-milliseconds a month

Requests aren't counted and bandwidth isn't metered, on any plan. CPU time is the meter, and a spot bridge barely moves it: a few hundred spots a day works out well under one percent of the month's allowance. Spore is a ceiling rather than a bill, so if you did somehow reach it you'd be asked to upgrade, not charged. Custom domains start on the $5 plan and the bridge doesn't need one; a Cell gets an address on tissue.dev the moment you deploy it.

two channels from one HamAlert URL

One address per HamAlert account is the limit you run into. Say you want the ATNO as a page, S2S on a loud channel, and the summit name under the title. The fix is a small program that sits between the two. On Tissue Cloud that's called a Cell: a few lines of JavaScript that run on our servers and answer at a web address of their own. HamAlert calls the Cell, the Cell decides which Mast channel gets the spot. About forty lines, runs on the free plan. The two channels it sends to are the app's two free channels, with the shack-pi vital alongside, so it still needs no Mast account.

One URL in, two channels out
HamAlert one URL action GET, all fields Cell: hamalert reads triggerComment, builds a body, picks a channel everything else comment "s2s" dx page · rings through mute every 60 s s2s loud · normal sound every 60 s

The two Mast addresses live in the Cell's vault, the place a Cell keeps its secrets, not in HamAlert's settings and not in the code.

The manifest

Two files in a folder. The first tells Tissue Cloud what the Cell is called and which secrets it needs.

# ribo.toml
[cell]
name = "hamalert"
js   = "./hamalert.js"

# The two Mast send addresses. Set both with `ribo vault set` BEFORE the first
# deploy. A vault value that isn't there yet gets stored as a placeholder
# string, and the Cell then throws on every spot instead of sending.
[[bindings]]
type    = "vault"
binding = "DX_URL"

[[bindings]]
type    = "vault"
binding = "S2S_URL"

The Cell

The second is the program itself.

// hamalert.js
export default {
  async fetch(request, env) {
    const q = new URL(request.url).searchParams;
    const title = q.get("title");
    if (!title) return new Response("no title", { status: 400 });

    // Route on the trigger comment. HamAlert sends it as triggerComment,
    // joined with ", " if more than one trigger matched.
    const comments = (q.get("triggerComment") || "").split(",").map((s) => s.trim());
    const s2s = comments.includes("s2s");

    // Body text. SOTA spots carry the summit, POTA spots the park;
    // the reference for both lands in the WWFF fields on POTA's side.
    const where = q.get("summitName")
      ? `${q.get("summitRef")} ${q.get("summitName")}, ${q.get("summitPoints")} pts`
      : q.get("wwffName")
        ? `${q.get("wwffRef")} ${q.get("wwffName")}`
        : "";
    const heard = q.get("spotter") ? `spotted by ${q.get("spotter")}` : "";
    const body = [where, q.get("comment"), heard].filter(Boolean).join(" · ");

    const alert = new URLSearchParams({
      title,
      body,
      priority: s2s ? "loud" : "page",
      ack: "required",
      retry: "60",
      expire: "900",
    });

    // Re-spots of the same station on the same band fold into one alert.
    // Only when HamAlert sent both, or every spot would share one key.
    const key = [q.get("callsign"), q.get("band")].filter(Boolean).join("-");
    if (key) alert.set("key", key);

    const res = await fetch(s2s ? env.S2S_URL : env.DX_URL, {
      method: "POST",
      headers: { "content-type": "application/x-www-form-urlencoded" },
      body: alert,
    });
    return new Response(res.ok ? "ok" : `mast said ${res.status}`, { status: res.ok ? 200 : 502 });
  },
};

The Cell waits for Mast's answer before it replies, so opening the Cell's own URL in a browser tells you whether the send worked. HamAlert won't tell you: it fires the URL and drops the reply on the floor, so over there the proof is still the phone ringing. The key means a respot of the same activator on the same band updates the alert you already have instead of ringing a new one.

Deploy it

  1. Install ribo, the Tissue Cloud command-line tool. One static binary, nothing to install alongside it. There's a Raspberry Pi build, so this can run on the shack Pi rather than your laptop if that's easier, along with builds for both kinds of Mac and for 64-bit Intel Linux; Windows runs the same line under WSL. They're all on the download page. The Pi build wants the 64-bit Raspberry Pi OS: if uname -m doesn't say aarch64 you're on the 32-bit image, the installer stops and names the architecture it found.
    curl -fsSL https://tissue.systems/install.sh | sh
  2. ribo login opens a browser and signs the CLI in. The free plan does it: five Cells, no card.
  3. Make a folder with the two files above and cd into it.
  4. Put the two Mast addresses in the vault, each from its own channel in the app:
    ribo vault set hamalert DX_URL     # prompts; paste the page channel's address
    ribo vault set hamalert S2S_URL    # prompts; paste the s2s channel's address
  5. ribo deploy. The last lines give you the URL:
    deployed  hamalert
    address   7f2a91c4h8k2m
    kind      js
    url       https://hamalert.quartz-4b.tissue.dev
    vault     DX_URL, S2S_URL
  6. In HamAlert, replace the Mast address with that url. Method stays GET. Simulate.

From here, change whatever you like. Drop spots under a certain snr. Send park spots to a third channel only when you're home. Flag a dxcc on your needed list. It's plain JavaScript and a redeploy takes a few seconds.

how it compares with other pager apps

There are other pager apps, and one of them is probably on your phone already. The closest costs a few dollars and it's good. Five things are different here.

what it doesn't do

HamAlert is by Manuel Kasper, HB9DQM. It's free and the server is open source; this page uses the URL action he built. Mast Pager is free, with no account, on the App Store and on Google Play. The bridge Cell runs on Tissue Cloud, and the free plan covers it.

using the appWhich level to reach for, making a page wake you, ceilings, quiet hours, pausing for a meeting, and answering from the lock screen. sending alertsEvery field a send takes, the sounds, and the limits. the iPhone appCritical alerts, the Action button and Siri, the widget, and what happens after a reinstall. the Android appThe four settings a page depends on, the Waking you checklist, and the tone on the alarm stream. home assistant, end to endThe same heartbeat idea for a Home Assistant box, with the YAML beside each screenshot.

Mast Pager on its own is on the Mast Pager page, and Tissue Mast, the monitoring service with the free account and the paid plans, is on the Mast page and its pricing. Corrections and questions reach support@tissue.systems.