通知インフラをゼロ円で作った

年間数百万円のSaaSを、GitHubとCloudflareで置き換えた設計

この記事で言いたいこと:プッシュ通知は「借りるもの」ではなく「作れるもの」だ。7万社規模に届けても$0のアーキテクチャは、既存のインフラを組み合わせるだけで成立する。SaaSの月額料金は、設計コストの代替として支払われている。設計できるなら、払う必要はない。

1. 見積もりを見て笑った

あるPWAにプッシュ通知を実装しようとして、市販のプッシュ通知サービスの料金ページを開いた。無料枠は1万ユーザーまで。それを超えると月額数万円から、規模によっては数十万円になる。年換算すると数百万円。エンタープライズ向けのプランは「お問い合わせください」で価格すら書いていない。

笑ったのは、プッシュ通知の仕組みを知っていたからだ。ブラウザとスマートフォンが持つWeb Push APIは、もともと無料のプロトコルだ。GoogleやAppleのサーバーが中継を担い、送信者はVAPIDという鍵ペアを持つだけでいい。料金が発生するのは、サービスがその上に乗せている「管理UI」「ダッシュボード」「サポート」に対してだ。通知そのものは、ただ届くだけだ。

2. 必要なものを分解する

プッシュ通知を自前で動かすために必要なものは、実は3つしかない。サブスクリプションの保管場所、送信処理、そして受け口だ。

サブスクリプション保管 → プライベートリポジトリ(JSON)
送信処理 → GitHub Actions + web-pushライブラリ
受け口 → Cloudflare Worker(認証付きエンドポイント)

どれも既存のインフラだ。新しいサービスは何も必要ない。Cloudflare Workerの無料枠は1日10万リクエスト。GitHub ActionsはパブリックリポジトリであればCI実行時間が無制限だ。web-pushはnpmパッケージで、使用料はゼロ。

3. キュー設計が肝だった

単純に「ユーザーが登録したらGitHubにコミットする」設計では、同時登録が集中したときに詰まる。gitのpushは逐次処理だからだ。

解決策はキューだ。Workerが受け取ったサブスクリプションをプライベートリポジトリのqueue/ディレクトリにコミットし、毎朝の送信バッチがそれをsubscriptions/にフラッシュしてから送信する。

登録時:Worker → queue/{uid}.json にコミット(即レスポンス)
送信時:queue/ → subscriptions/ にフラッシュ → 全員に送信

ユーザーは登録と同時に完了を受け取れる。cloneの重さは非同期バッチに吸収される。登録と送信が完全に分離されるため、どちらかが詰まってももう一方には影響しない。

4. 並列化で規模の上限を突破する

1ジョブで順番に送ると、10万人で14時間かかる計算になる。GitHub Actionsの1ジョブ上限は6時間だ。このままでは4万人が現実的な上限だった。

解決はmatrix戦略による並列化だ。UIDの文字を使って36シャードに分割し、36並列で同時送信する。

matrix: shard: [0,1,2,...,9,a,b,...,z]
各シャードが独立して起動 → 36並列で送信
全体の処理時間 = 1シャードの時間(約20分)

36並列なら360万人でも6時間以内に完了する。パブリックリポジトリのActionsは無制限なので、追加コストはゼロのままだ。

5. SaaSの価格は何に対して払っているか

7万社規模のユーザーベースを持つ業界団体を想定して、各サービスの料金を調べた。大手プッシュ通知サービスは年間数百万円から。エンタープライズ向けのプラットフォームになると年間数千万円を超える。

その差額は何に対して払われているのか。管理画面の使いやすさ、セグメント配信の柔軟性、サポート対応、SLA保証。それらは確かに価値がある。だが、毎朝同じメッセージを全員に届けるだけであれば、それらは不要だ。

SaaSの料金は「設計しなくていい権利」の対価だ。設計できるなら、その権利を買う必要はない。

6. Cloudflare Workerが橋になった

このアーキテクチャで唯一の課題は、PWAからプライベートリポジトリへの書き込みにGitHub PATが必要なことだ。PATをブラウザのJSに埋め込めば、誰でも読み取れてしまう。

Cloudflare Workerがその橋を担う。PWAはWorkerにサブスクリプションを送る。WorkerはWEBHOOK_SECRETで認証し、PATを使ってリポジトリにコミットする。PATはCloudflareのサーバーサイドに閉じていて、ブラウザには一切露出しない。

ユーザーは合言葉(WEBHOOK_SECRET)だけ知っていればよく、PATは知らなくていい。Workerは1日10万リクエストまで無料で動く。これで設計が完結した。

7. 実装の朝、通知が届いた

朝5時に実装を始め、4時間後にiPhoneへ最初のプッシュ通知が届いた。エラーは多かった。認証のズレ、キャッシュの壁、YAMLの構文ミス。それでも1つずつ潰して、最終的に動いた。

感動したのは通知が届いた瞬間だけではない。月額ゼロ円でそれが動いていることへの感触だ。誰かの請求書に自分の使用量が記録されていない。誰かのサーバーに依存していない。自分のリポジトリに、自分の鍵で、自分のコードが動いている。

8. インフラは育つ、SaaSは消耗する

道具は使うほど消耗する。借りたインフラは使うほど請求が増える。自分で作ったインフラは使うほど育つ。コードの理解が深まり、設計が洗練され、問題が起きたときに自分で直せる。

プッシュ通知の実装を通じて改めて確認したのは、「作れるかどうか」を問い続ける姿勢だ。多くのものは、実は作れる。作れないのではなく、作る前にSaaSの価格ページを見てしまうからだ。

設計コストを払えるなら、SaaSの月額は不要だ。インフラは育つ。道具は消耗する。

トキストレージは、声・画像・テキストを1000年残すインフラを目指すプロジェクトです。設計の記録もすべて公開しています。

トキストレージを知る すべてのエッセイを読む