A Dead-Man's Switch for Mobile Apps: North's Reconnect Nudges
- mobile
- push notifications
- Supabase
- Expo
- reliability
- North
The worst failure mode in a family location app isn't a crash — it's silence. A teenager swipe-kills the app, iOS stops the background tasks, and from the family's perspective the dot just… freezes. The app can't report its own death, because it's dead.
This is a distributed-systems problem wearing a consumer-app costume, and the classic answer applies: a dead-man's switch — a signal that fires unless the system keeps proving it's alive. North implements it in three layers, because each one covers a hole in the others.
Option A: the server notices
A Supabase Edge Function runs on a pg_cron schedule and looks for members who've gone dark. The selection logic is a security-definer database function with rules that matter more than the query itself:
- Location older than 4 hours → candidate for a reconnect push.
- No more than one reconnect push per 6 hours — a nag becomes noise instantly, so the throttle timestamp (
last_reconnect_at) lives in the database, not in app memory. - Only members of circles with at least one other active member. Waking a solo user helps nobody; this guard is equal parts battery courtesy and privacy hygiene.
Candidates get a visible, high-priority push: "open the app to resume sharing." Visible is the point — silent pushes don't work on force-stopped apps, but the notification itself still renders, putting a human back in the loop.
Option B: the phone notices, even with the app dead
The server-side check has up to four hours of latency. The client-side layer is faster and wonderfully low-tech: every successful location upload schedules a local notification two hours in the future — "you've stopped sharing, tap to reconnect."
Then the app spends its life cancelling that notification:
- Every location push re-schedules it (same static identifier, so it replaces rather than stacks — the dead-man's timer just keeps sliding forward).
- Foregrounding the app cancels it.
- Pausing sharing deliberately cancels it — pausing is a choice, and nagging someone about a choice is hostile.
If the app dies, nothing cancels the pending notification, and the OS — which doesn't care that the app is dead — delivers it. The app's last act, scheduled while alive, is reporting its own death.
The platform asymmetry is why Option A still exists: on iOS, scheduled local notifications survive app termination. On Android, a force-stop discards them — so the client-side switch covers iOS swipe-kills with two-hour latency, and the server-side push backstops Android with four.
Option C: design the social layer honestly
The third layer isn't code so much as a stance. Circle nudges ("ask them to reconnect") are always on — the toggle to disable them doesn't exist in settings, and the server normalizes an off preference to default sound, honoring only an explicit silent. That sounds heavy-handed until you think it through: a connection app where you can unknowingly become unreachable while everyone believes you're reachable is broken at the contract level. You can pause sharing — loudly and deliberately. What you can't do is silently rot.
And while all three layers work on reconnection, the UI tells the truth in the meantime: stale members show "last seen 2 hr ago" instead of a confident live dot — the honest staleness rule.
The pattern, generalized
Anything that should run continuously on a phone needs an answer to "who notices when I die?" The shape that works:
- A watchdog that outlives you — scheduled local notifications survive your process (on iOS); cron checks on a server survive everything.
- Arm on success, not on failure. You can't schedule anything from inside a crash. Every healthy heartbeat sets the next alarm; death is detected by absence.
- Throttle in durable storage — reminder state must survive restarts, or you'll double-nag.
- Let deliberate absence opt out. A dead-man's switch that fires on intentional pause teaches users to ignore it — the alarm-fatigue death spiral.
None of this makes background execution reliable. It makes unreliability visible, which on mobile is the best contract on offer.