Push Relay: a Web Push notification service, and how it became reusable infrastructure
How I built Push Relay — a self-hosted browser-notification service on the open Web Push standard — then made it reusable infra with per-app keys and client SDKs.

I kept using email for "your site is back up" and "deploy finished" — a heavy hammer. I wanted something lighter: a browser notification that lands even with the tab closed, no third-party SaaS and no native app. That's exactly what the Web Push standard gives you, so I built a small service around it that any of my apps can call — as a portfolio piece and as real infrastructure I'd actually use.
How Web Push works
The part that clicked for me: no polling, no socket, and the push service in the middle can't read your messages. A VAPID keypair identifies the sender (the browser only ever sees the public key); at subscribe time the browser hands over its own encryption keys, so the payload is end-to-end encrypted — only that browser can open it. A Service Worker wakes on the push and calls showNotification(), which is why it works with the tab closed.
The stack is deliberately boring and cheap: Django + Postgres + pywebpush, with Procrastinate (a Postgres-backed job queue, no Redis) for scheduled sends, deployed on Railway.
Making it reusable — the interesting half
A notification service is only infrastructure if other apps can drop it in. That became the richest part of the build:
- Two-tier keys. A browser-safe publishable key that only registers a device, and a server-only secret key that sends. A send key must never touch the browser (the Stripe model).
- Three client SDKs — browser subscribe, plus Node and Python send — published so adopting it is a few lines instead of ~40 lines of Service-Worker plumbing.
- A real consumer. I wired my own uptime monitor to it, then built a standalone React app that installs the published packages and runs the whole loop. If a separate app can adopt it easily, it's real infrastructure.
Things that surprised me
- iOS is its own world. Web Push works there only for sites added to the Home Screen (16.4+), and you can't trigger "Add to Home Screen" from JavaScript — app-icon badges have the same rule.
- No delivery receipt. The network only confirms the push was accepted, not shown — ad-blockers and OS Focus/DND can suppress it silently. I built for that honestly rather than pretending.
- The boring stuff bites. Publishing packages, a stray CORS trailing slash, getting the deploy to detect the runtime — these ate more time than the cryptography did.
What I'd take to the next one
Design how other people will consume your thing — the SDKs, the key model, an example app — as early as you design the UI. That's most of what makes something reusable. And verify the deploy path itself early, not just the API: half my surprises were at build/deploy time, not in the code.
It's all open source — have a poke around:
- Live app + interactive docs
- The demo React consumer
- Source on GitHub