Skip to main content

ntfy phone notifications

The browser worker sends phone push notifications through a self-hosted ntfy server. It sends them only for things that need you: a failed run, an expired ESPN session, or a trade to review. Each push is a short summary. Tapping it opens the matching page of the web UI, and you approve anything there.

Which events send a push?​

EventSent whenPriorityTap opens
Bot run failedAny instrumented bot run ends in failureHigh/activity/runs/<run id>
ESPN session expiredThe session healthcheck fails (replaces the generic failure push)Urgent, bypasses Do Not Disturb/settings
Trade ready for approvalA proposal or counter-offer is staged as awaiting_approvalHigh/trades?offer=<offer key>
New incoming trade offerThe trade inbox refresh stores an offer for the first timeHigh/inbox?offer=<ESPN transaction id>

A deep-linked page scrolls to the matching card and outlines it. If the card isn't there (already handled, hidden or expired), the page shows its normal list.

How do I set up my phone?​

Prerequisites
  • An Android phone with the ntfy app from Google Play or F-Droid.
  • The phone user's password, stored in OpenBao: bao kv get -field=password secret/fantasy/ntfy-phone.
  1. Open ntfy, then go to Settings → Manage users → Add user. Enter the service URL https://ntfy.roo-net.cc, the username phone and the password from OpenBao, then save.
  2. On the main screen, tap + to subscribe. Enter the topic fantasy.
  3. Check Use another server and enter https://ntfy.roo-net.cc.
  4. Check Instant delivery, then tap Subscribe. Without it, a self-hosted server only gets periodic polling on Android, so pushes can arrive many minutes late.
  5. When Android asks, allow notifications and let ntfy ignore battery optimization. Instant delivery holds one connection open in the background, and battery savers kill it.
  6. Optional: to let urgent pushes (an expired ESPN session) through Do Not Disturb, open the ntfy app's notification settings in Android and allow the highest-priority channel to override Do Not Disturb.

Verify: the subscription shows recent messages, such as the "ntfy is set up" test push, since the server keeps 48 hours of history. To send a fresh test from the server:

kubectl exec deploy/ntfy -- ntfy publish --token "$(bao kv get -field=token secret/fantasy/ntfy)" \
--title "Test" https://ntfy.roo-net.cc/fantasy "Phone setup works."
ProblemFix
"Unauthorized" when subscribingThe user from step 1 is missing or its URL doesn't match the subscription's server exactly.
Pushes arrive late or in burstsInstant delivery is off, or battery optimization is stopping ntfy. Redo steps 4 and 5.
Tapping a push shows a Cloudflare loginExpected the first time: fantasy.roo-net.cc sits behind Cloudflare Access. Sign in once in the phone's browser.

Why ntfy?​

OptionWhy not
AWS SNS mobile pushStill delivers through Firebase, so it needs a custom app as well as a new AWS dependency.
Discord webhookPlain webhooks can't carry interactive buttons. Real buttons need a bot with a public interactions endpoint.
Custom Android appWeeks of work (app, Firebase, device registry, sideloading) for a single user.

ntfy already has an Android app, runs in the homelab, and takes a single HTTP POST per push.

How is it secured?​

  • Server: reachable at ntfy.roo-net.cc through the Cloudflare tunnel, without Cloudflare Access, because the Android app can't do a browser login. Instead, ntfy's own auth denies all anonymous reads and writes.
  • Tokens: the phone uses a read-only user. The worker uses a write-only token stored in OpenBao at secret/fantasy/ntfy.
  • Approvals: a push carries only links, never an action. Approving still goes through the web UI, which Cloudflare Access protects.

What happens when something fails?​

SituationEffect
ntfy is down or rejects the pushThe worker logs a warning and carries on. Sending never fails a bot.
NTFY_URL or NTFY_TOKEN is unsetNothing is sent. This is the default for local runs and before the token is provisioned.
A bot keeps failing with the same errorOne push per bot and error per hour. Each offer is announced once per 24 hours.
Redis is unreachableThe push is sent anyway: a duplicate is better than a missed alert.
The phone's connection dropsntfy keeps 48 hours of messages and replays them when the app reconnects.
  • Worker configuration and event code: the browser-worker README, "Phone notifications (ntfy)".
  • Server setup (users, tokens, tunnel hostname, phone app): homelab-deployment's README, "ntfy: phone push notifications".
  • OpenBao for how the worker's token reaches the pod.