プッシュ通知の実装と設計|FCM・APNs配信基盤と到達率改善・外注の判断軸
コセケン
テクラル合同会社

プッシュ通知を本番運用に乗せるうえで押さえるべきは、Android は FCM(Firebase Cloud Messaging)の HTTP v1 API、iOS は APNs(Apple Push Notification service)のトークン認証(.p8)を前提に、トークン管理・配信ログ・リトライ・許諾設計までを「配信基盤」として一体で組むことです。通知が届かない原因の大半は、通信レイヤーではなくこの配信基盤の設計不足にあります。本記事では FCM/APNs の現行仕様に沿った実装の勘所と、到達率を落とさないための設計、そして内製と外注をどこで線引きするかの判断軸を整理します。
読者として想定しているのは、自社プロダクトに通知を組み込む事業責任者・PdM・開発責任者と、実装を担うエンジニアです。コードと設定の解像度を保ちつつ、最終的に「自社で抱えるか、設計から委託するか」を判断できる材料を提示します。
配信経路は2系統に分かれる(FCMとAPNsの役割)
プッシュ通知の配信経路は、Android(および Web)向けの FCM と、iOS/iPadOS/macOS 向けの APNs の2系統です。アプリ側で発行された端末トークンをサーバーが保管し、サーバーから各プラットフォームのゲートウェイへ送信する、という流れはどちらも共通します。
実務では FCM を一段上のハブとして使う構成が一般的です。FCM は Android にネイティブ配信しつつ、iOS 向けには内部で APNs に中継してくれるため、サーバー側の送信コードを FCM に一本化できます。一方で、iOS 配信を細かく制御したい、あるいは Firebase への依存を避けたいケースでは、APNs へ直接 HTTP/2 で送る構成を選びます。
| 観点 | FCM 経由(iOSもFCMで中継) | APNs 直接送信 |
|---|---|---|
| サーバー実装 | 送信先を FCM に一本化できる | プラットフォーム別に2系統実装 |
| iOS の制御粒度 | FCM のペイロード仕様に従う | APNs ヘッダを直接制御できる |
| 依存関係 | Firebase プロジェクトに依存 | Apple のみに依存 |
| 配信ログ | FCM 側のレスポンスに集約 | APNs の応答を自前で集約 |
| 向くケース | 多くの一般的なアプリ | iOS 比率が高く制御要件が厳しいプロダクト |
判断としては、まず FCM 一本化で始め、iOS の配信制御に固有要件が出てきた段階で APNs 直接送信を検討する、という順序が堅実です。最初から2系統を抱えると運用コストが二重になります。
FCMはHTTP v1 + OAuth2が前提(レガシーAPIは廃止済み)
FCM のサーバー送信は、現在 HTTP v1 API と OAuth2 アクセストークンが唯一の選択肢です。かつての「サーバーキー(Server key)」を使うレガシー HTTP API および XMPP プロトコルは2024年7月に廃止されており、いまから組むなら HTTP v1 一択になります。サーバーキーを前提にした古い実装サンプルは動かないため、そのまま流用してはいけません。
HTTP v1 ではサービスアカウントの秘密鍵から OAuth2 アクセストークン(有効期限は約1時間)を発行し、Authorization: Bearer ヘッダに載せて送信します。Google が提供する Firebase Admin SDK を使えば、このトークン発行と自動更新を SDK が肩代わりするため、自前で JWT を組む必要はありません。
// Node.js / Firebase Admin SDK(2025年時点)
import { initializeApp, cert } from "firebase-admin/app";
import { getMessaging } from "firebase-admin/messaging";
initializeApp({
credential: cert(process.env.GOOGLE_APPLICATION_CREDENTIALS),
});
// 単一端末への送信
await getMessaging().send({
token: deviceToken,
notification: { title: "新着メッセージ", body: "1件の未読があります" },
android: { priority: "high" },
apns: { headers: { "apns-priority": "10" } }, // iOS中継時のヘッダ
});
SDK を介さずに REST を直接叩く場合は、サービスアカウントで https://www.googleapis.com/auth/firebase.messaging スコープのアクセストークンを取得し、エンドポイント https://fcm.googleapis.com/v1/projects/{PROJECT_ID}/messages:send に POST します。
curl -X POST \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "message": { "topic": "news", "notification": { "title": "速報", "body": "新しいお知らせがあります" } } }' \
https://fcm.googleapis.com/v1/projects/your-project-id/messages:send
設計上の要点は、アクセストークンを毎回発行し直さないことです。約1時間の有効期限の範囲でキャッシュし、期限が近づいたら再発行します。送信のたびにトークン発行を走らせると、レイテンシとレート制限の両方で不利になります。
APNsはトークン認証(.p8)をデフォルトにする
iOS 向けに APNs へ直接送るなら、認証方式はトークンベース(.p8 キー)を選ぶのが現在のベストプラクティスです。従来の証明書ベース(.p12)はアプリごとに証明書を発行し毎年更新する運用が必要でしたが、.p8 キーは1つで同一チーム配下の全アプリに使え、有効期限による失効もありません(必要時にリボークする運用)。運用負荷が明確に下がるため、新規は .p8 を前提にします。
| 項目 | トークン認証(.p8) | 証明書認証(.p12) |
|---|---|---|
| 適用範囲 | チーム配下の全アプリで共用 | アプリごとに個別 |
| 有効期限 | 失効なし(リボークで無効化) | 約1年で更新が必要 |
| 接続 | HTTP/2 + JWT を毎リクエストに付与 | TLSクライアント証明書 |
| 運用負荷 | 低い(更新作業なし) | 高い(年次更新の管理が必要) |
| 推奨度 | 新規・移行ともに推奨 | 既存資産がある場合のみ |
APNs は HTTP/2 で通信し、.p8 キーから署名した JWT を authorization ヘッダに載せます。実装上で見落としがちなのは、JWT を毎リクエスト新規発行しないこと、そして HTTP/2 コネクションを使い回すことです。Apple は接続を開いたまま保つことを推奨しており、毎回 TLS ハンドシェイクからやり直すと数秒単位のオーバーヘッドが乗ります。
# APNs リクエストのヘッダ構造(HTTP/2)
:method = POST
:path = /3/device/{device_token}
authorization = bearer {.p8から署名したJWT}
apns-topic = com.example.app # バンドルID
apns-push-type = alert # alert / background / voip など
apns-priority = 10 # 10=即時, 5=省電力配慮
apns-expiration = 0 # 0=即時破棄, それ以外=保持期限のUNIX時刻
apns-push-type と apns-expiration は到達率に直結するヘッダです。とくにバックグラウンド更新を alert で送ると配信が抑制されるなど、タイプの取り違えは届かない原因になります。
「届かない」の正体は到達率の設計不足
通知が届かない問題の多くは、ネットワーク障害ではなく、許諾・トークン鮮度・優先度・OS制約という設計レイヤーで起きています。配信基盤を組むときは、この4点を最初から運用前提に織り込みます。
第一に許諾です。iOS は通知許諾を取らないと一切表示されません。初回起動でいきなり許諾ダイアログを出すと拒否率が上がるため、価値が伝わった文脈で要求する、あるいは仮許諾(provisional authorization)で静かに通知センターへ配信して関心を測り、後から正式許諾へ昇格させる設計が有効です。
// iOS: 仮許諾で静かに開始し、後で正式許諾へ昇格(iOS 12以降)
import UserNotifications
UNUserNotificationCenter.current().requestAuthorization(
options: [.alert, .sound, .badge, .provisional]
) { granted, error in
// .provisional はダイアログを出さず静かに通知センターへ配信される
}
第二にトークン鮮度です。端末トークンはアプリ再インストールやバックアップ復元で変わります。古いトークンへの送信は FCM/APNs から「無効」応答が返るので、その応答を捕捉してデータベースから即座に削除・更新する仕組みが必須です。これを怠ると、無効トークンが蓄積して見かけの送信数だけ増え、実際の到達率が下がります。
第三に優先度とタイプです。FCM の priority: high や APNs の apns-priority: 10 は即時配信を意図しますが、乱用すると OS のレート制限や省電力制御で抑制されます。即時性が必要な通知だけを高優先度にし、それ以外は通常優先度に分けます。
第四に OS の省電力制約です。Android の Doze モード、iOS のバックグラウンド配信制限など、端末側の都合で配信が遅延・統合されることがあります。確実に届けたい重要通知と、ベストエフォートで構わない通知を分類し、前者は到達確認とフォールバック(メール・アプリ内通知など)まで設計に含めます。
配信ログとリトライを持って初めて「基盤」になる
送信処理を書いただけでは配信基盤とは呼べません。本番運用では、送信1件ごとの結果ログ、無効トークンの自動回収、一時エラーのリトライ、配信状況の可視化までを備えて初めて運用に耐えます。
最低限そろえるべき構成要素は次の通りです。
- 送信キュー:大量配信時にレート制限へ当てないためのスロットリングと再送制御
- 結果ログ:端末トークン・送信時刻・レスポンスコード・成功/失敗を記録し、到達率を後から計測できる状態にする
- 無効トークン回収:FCM/APNs の
Unregistered/BadDeviceToken等の応答を捕捉し、対象トークンを論理削除 - リトライ:5xx 系の一時エラーは指数バックオフで再送、4xx 系(無効トークン等)は再送せず破棄
- 計測ダッシュボード:セグメント別の送信数・到達率・開封率を継続的に観測
// 送信結果を分岐して回収・リトライにつなぐ(擬似コード)
const res = await sendToFcm(message).catch((e) => e);
if (res?.errorInfo?.code === "messaging/registration-token-not-registered") {
await tokens.deactivate(deviceToken); // 無効トークンを回収
} else if (res?.status >= 500) {
await queue.retryWithBackoff(message); // 一時エラーは再送
}
ここまで作ると、通知は「送りっぱなし」から「到達率を数値で管理できる運用資産」に変わります。逆に言えば、この層を持たないまま通知を増やすと、届いていないことに気づけないという最も避けたい状態に陥ります。
内製と外注の判断軸
プッシュ通知の内製/外注は、通知が「プロダクトのコア体験か」「到達率を経営指標として管理する必要があるか」で線引きします。送信処理そのものは難しくありませんが、到達率を維持する配信基盤の設計・運用には継続的な知見が必要だからです。
判断の目安を整理します。
| 状況 | 推奨 | 理由 |
|---|---|---|
| シンプルな告知通知で量も少ない | 内製 | SDK の標準機能で十分。基盤投資が過剰になる |
| 通知が継続率・再訪を左右するコア体験 | 設計から外注 or 伴走 | 許諾・到達率・セグメント設計の巧拙が事業指標に直結 |
| iOS/Android 両対応+配信ログ+リトライが必要 | 外注を検討 | 配信基盤の設計工数と運用知見が内製の負担を超える |
| 既存アプリで通知が届かない問題が頻発 | 原因調査から外注 | トークン鮮度・許諾・優先度の複合要因で、切り分けに経験が要る |
要するに、通知が事業KPIに効くプロダクトほど、送信コードではなく「配信基盤の設計」に投資価値が出ます。逆に、通知が補助的なら標準SDKの内製で十分です。自社プロダクトで通知の到達率や開封率が伸び悩んでいる、あるいは新規にiOS/Android両対応の通知基盤をゼロから設計する局面では、配信基盤を一体で設計できる体制を持っているかどうかが、その後の運用コストと事業インパクトを大きく左右します。この線引きを早い段階で見極めておくことが、通知を「届く仕組み」に育てる第一歩になります。
参考(一次情報):
この記事を書いた人

コセケン
テクラル合同会社
スタートアップでのCTO経験を経て、現在はテクラル合同会社にてシステム開発全般を牽引しています。アプリおよびWebの開発から、バックエンド、インフラ構築に至るまで幅広い技術領域に対応可能です。スピード感を持った品質の高いシステム開発を得意としており、新規プロダクトの立ち上げを一気通貫で支援します。本ブログでは実践的な開発ノウハウを発信していきます。


