I Built Push Notification Infrastructure for Free

Replacing SaaS worth hundreds of thousands a year with GitHub and Cloudflare

The point of this essay: push notifications are something you can build, not just rent. An architecture that delivers to 70,000 organizations for $0 is achievable by combining existing infrastructure. The monthly SaaS fee is the price you pay to avoid designing it yourself. If you can design, you don't need to pay.

1. I Laughed at the Pricing Page

I was adding push notifications to a PWA when I opened the pricing page of a popular push notification service. The free tier capped at 10,000 users. Beyond that, it was tens of thousands of yen per month, scaling into the hundreds of thousands for larger audiences. Annualized, that's millions of yen. The enterprise plan just said "contact us."

I laughed because I already knew how push notifications work. The Web Push API built into modern browsers is a free, open protocol. Google and Apple relay the messages at no charge to the sender. All you need is a VAPID key pair. The fees on these services aren't for the notifications — they're for the management UI, dashboards, and support layered on top.

2. Break It Down to the Essentials

Running push notifications yourself requires exactly three things: somewhere to store subscriptions, a process to send them, and an endpoint to receive new subscribers.

Subscription storage → private GitHub repository (JSON files)
Sending → GitHub Actions + web-push npm library
Endpoint → Cloudflare Worker (authenticated)

All of it is existing infrastructure. Cloudflare Worker's free tier handles 100,000 requests per day. GitHub Actions on a public repository has no execution time limit. web-push is an open-source npm package. The cost is zero across the board.

3. The Queue Design Was the Key

A naive approach — commit to GitHub every time a user registers — would bottleneck under concurrent registrations. Git pushes are sequential.

The solution is a queue. The Worker commits each incoming subscription to a queue/ directory in a private repository and responds immediately. The morning send job flushes the queue into subscriptions/ before sending.

Registration: Worker → queue/{uid}.json (instant response)
Send time: queue/ → subscriptions/ flush → send to all

Registration and delivery are fully decoupled. A spike in registrations doesn't slow down delivery. A long send job doesn't affect new registrations.

4. Parallelism Removes the Scale Ceiling

Sending sequentially to 100,000 users takes about 14 hours — well over GitHub Actions' 6-hour job limit. The practical ceiling was around 40,000 users.

Matrix strategy solves this. Split subscriptions into 36 shards by the leading character of the UID, run all 36 in parallel.

matrix: shard: [0–9, a–z]
Each shard runs independently → 36 parallel jobs
Total time = time for one shard (~20 minutes)

At 36x parallelism, 3.6 million users complete within 6 hours. Public repository Actions runs for free. The cost stays at zero.

5. What Are You Actually Paying For?

I priced out push notification services at the scale of a 70,000-organization industry group. Major platforms ran from hundreds of thousands to millions of yen per year. Enterprise-tier platforms exceeded tens of millions annually.

What is that money buying? A polished admin interface. Flexible segmentation. Support SLAs. Those things have genuine value. But if your use case is sending the same message to everyone every morning, none of them matter.

SaaS pricing is the cost of not having to design it yourself. If you can design it, you don't need to buy that option.

6. Cloudflare Worker as the Bridge

The one challenge in this architecture is that writing to a private GitHub repository requires a PAT — and embedding a PAT in browser-side JavaScript exposes it to anyone who reads the source.

The Cloudflare Worker bridges that gap. The PWA sends a subscription to the Worker. The Worker authenticates with a WEBHOOK_SECRET, then uses the PAT to commit to the repository. The PAT never leaves Cloudflare's server side. The browser only ever knows the shared secret.

Users need only the shared secret. The PAT stays invisible. The Worker runs for free within its daily limit. The design is complete.

7. The Morning the Notification Arrived

Implementation started at 5 AM. Four hours later, the first push notification arrived on an iPhone. There were plenty of errors along the way — authentication mismatches, cached old code, YAML syntax failures. Each one got resolved.

The satisfaction wasn't just that it worked. It was that it cost nothing. No one's billing system recorded the usage. No external server was keeping the lights on. My repository, my keys, my code — running quietly on infrastructure I own.

8. Infrastructure Grows. Tools Wear Out.

Tools wear out with use. Rented infrastructure gets more expensive with use. Infrastructure you built yourself grows with use — deeper understanding, cleaner design, the ability to fix things yourself when they break.

What this build confirmed is the habit of asking "can I build this?" before reaching for a pricing page. Most things can be built. The reason they aren't isn't inability — it's that the pricing page comes first.

If you can pay the design cost, you don't need the monthly SaaS fee. Infrastructure grows. Tools wear out.

TokiStorage is a project building infrastructure to preserve voices, images, and text for 1,000 years. All design records are published openly.

Explore TokiStorage Read all essays