Web Push setup
IAMScouting Network ships a Web Push opt-in on /network/account. This doc
covers what you have to wire up before pushes actually fire.
1. Generate VAPID keys (one time)
node scripts/generate-vapid.mjs
Output:
VAPID_PUBLIC_KEY=...
VAPID_PRIVATE_KEY=...
VAPID_SUBJECT=mailto:start@iamscouting.com
Paste those three lines into .env (local) and /opt/iamscouting/.env on
the VPS. The private key never leaves the server. Anyone with it can
sign pushes as IAMScouting.
The public key is fetched anonymously by the browser via
GET /api/push/vapid-public. That's by design — the key is, well, public.
2. Apply the migration
ssh myserver "docker exec -i iams-pg psql -U postgres -d iamscouting" \
< supabase/migrations/0046_push_subscriptions.sql
ssh myserver "docker exec -i iams-pg psql -U postgres -d iamscouting \
-c \"NOTIFY pgrst, 'reload schema';\""
3. Deploy
bash scripts/deploy.sh — the new env vars are read by lib/push.ts at
import time. The first push after deploy initializes web-push with the VAPID
material; subsequent pushes reuse the same config (module singleton).
4. Test in-browser
- Sign in on https://iamscouting.com/network/account.
- Scroll to the "Notification settings" card.
- Click Enable notifications. Grant the permission prompt.
- To smoke-test, fire a DM to yourself from a second account — the
receiving session should get a browser toast.
If the button does nothing:
- Check the browser console for
vapid_not_configured— server is missing
env vars.
- Check that
/sw.jsis reachable (200, not cached as 404 by an old SW). - Verify in chrome://settings/content/notifications that the site is not
blocked.
5. Where pushes fire from
| Trigger | Location |
|------------------------------------|-----------------------------------------------------|
| New DM to user | app/api/network/dm/route.ts (POST) |
| Lead matches a saved alert | app/api/network/leads/route.ts (alert fan-out) |
| Agent license approved | app/api/admin/agent-verify/route.ts (approve) |
All three are fire-and-forget — push failures never block the originating
request. The receiver-side cleanup (404/410 → delete subscription, 5xx →
increment failure_count) lives in lib/push.ts sendPushToUser.
6. Operational notes
- Stale subscriptions: deleted automatically on 404/410. No periodic
cleanup needed.
- One subscription per (user, browser+device):
endpointis UNIQUE.
Re-enabling on the same browser overwrites in place.
- Rotate VAPID: regenerate, swap env vars, redeploy. All existing
subscriptions are invalidated and will get one 410 → auto-deleted on the
next push attempt. Users will need to re-opt-in.
- iOS Safari: Web Push requires iOS 16.4+ AND the site to be installed
to the home screen (PWA). Desktop Safari, Chrome, Firefox, Edge all
support push directly.