Telegram Bot APIの429対策:retry_afterで送信キューを制御する
Telegram Bot APIの送信が流量制御で拒否され、parameters.retry_after が返った場合は、指定された秒数以上待ってから再試行します。期限を永続化し、同じボットを使う全ワーカーで共有してください。1つのワーカーを停止しても、他のワーカーは送信を続けられます。一方、応答のないネットワークタイムアウトは送信済みの可能性があるため、同じ自動再試行の分岐には入れません。
要点
descriptionの文章から数値を抽出せず、構造化された応答を読みます。- 公式の制限値はスケジューリングの条件であり、処理能力の保証ではありません。
- 受信データの永続化と送信の待機処理を分離します。
- 再起動後も期限を守り、遅延した返信がまだ必要か確認します。
Telegramの送信制限が適用される範囲
公式Bots FAQ は、1つのチャットで毎秒1件を超える送信を避けるよう案内しています。グループは毎分20件、通常の一斉通知は有料ブロードキャストを有効にしない場合で毎秒約30件です。一時的なバーストも、やがて 429 につながる場合があります。チャット単位とボット全体の制約は別なので、宛先を分散させても全体の制約はなくなりません。
これらを下回れば必ず成功する、という保証ではありません。メソッド固有の条件と実際の応答も確認します。この記事の対象は通常の送信キューであり、有料ブロードキャストの設定や、あらゆる 429 の原因を扱うものではありません。
Bot APIリファレンス では、ResponseParameters.retry_after は任意の整数で、流量制御を超えた際に再リクエストまで待つ残り秒数と定義されています。これはJSONフィールドであり、汎用的なHTTPヘッダー契約ではありません。フィールドがない場合にゼロ秒と解釈してはいけません。
結果を確認するワーカーでは、独立したAPIリクエストを使います。Webhook応答と個別sendMessageの比較 では、Telegramのwebhook応答にメソッドを埋め込むと、その実行結果を取得できない点を説明しています。
sleepループではなく永続的な送信レーンを作る
ここからはアプリケーション設計の提案であり、Telegramの追加保証ではありません。
送信前に、返信の意図ごとにローカルジョブを保存します。ボットの識別情報、宛先、承認済み本文、有効期限の方針、試行回数、次に実行できる時刻を保持してください。これらはアプリ側のフィールドであり、Bot APIの追加パラメーターではありません。同じ受信更新の再配信から複数の返信ジョブが生まれないよう、業務上の意図を重複排除します。
スケジューラーには3つの判定を設けます。
- ボット単位:同じボットを使う全プロセスの合計トラフィックを調整する。
- チャット単位:グループの制限も含め、宛先ごとの送信間隔を制御する。
- ジョブ単位:再試行期限、有効期限、キャンセル、実行リースを確認する。
ジョブ取得はアトミックに行い、2つのワーカーが同じ送信を実行しないようにします。ネットワーク呼び出しの直前にも共有状態を確認します。流量制御の応答を受けたら、待機期限をアトミックに延長し、ジョブをスケジューラーに戻します。待機中にwebhookのHTTP接続を開いたままにしないでください。
retry_after は、どの制限範囲に達したかを示しません。最初は、そのボットの送信レーン全体を指定時間停止する保守的な方針が考えられます。他のチャットも遅れますが、「このチャットだけの制限」と断定せずに済みます。アプリ独自の方針と明記し、サーバー内部の推測ではなく観測に基づいて調整します。
retry_afterを保存可能な期限に変換する
次のJavaScriptは結果の分類だけを示す例です。送信処理やデータベース実装は含まず、実行済みの本番テストでもありません。receivedAtMs はクライアントが応答を受けた時刻、currentNotBeforeMs は保存済みの共有待機期限です。
function telegramFloodDecision(body, receivedAtMs, currentNotBeforeMs = 0) {
if (body?.ok !== false || body.error_code !== 429) {
return { action: 'classify_other_result' };
}
const seconds = body.parameters?.retry_after;
if (!Number.isSafeInteger(seconds) || seconds < 0) {
return { action: 'hold_for_review' };
}
const deadline = receivedAtMs + seconds * 1000;
if (!Number.isSafeInteger(deadline)) {
return { action: 'hold_for_review' };
}
return {
action: 'reschedule',
notBeforeMs: Math.max(currentNotBeforeMs, deadline)
};
}
戻り値の名前はローカルアプリ用です。最大の期限をトランザクションで保存してください。単純な読み取りと無保護の書き込みでは、別ワーカーが保存した、より長い期限を上書きするおそれがあります。ワーカー間で時計を同期するか、共有のスケジューリング時計を使います。ジッターを加える場合も、待機時間を短縮してはいけません。
期限到来は再試行の資格を意味するだけで、成功を保証しません。試行回数とジョブの経過時間に上限を設けます。次の応答が待機を延長したら新しい期限を保存し、期限切れや担当者の返信済みの場合はキャンセルまたは確認に回します。これらの上限は業務ルールであり、Telegramの制限値ではありません。
拒否、結果不明、受信を分ける
| 観測した結果 | ワーカーの処理案 |
|---|---|
有効な流量制御応答と retry_after | 待機期限を保存し、全条件が許可してから再試行 |
使用可能な待機値のない 429 | 保留して分類。即時ループやゼロ秒扱いをしない |
sendMessage 成功 | 返された Message を保存し、同じ意図を再登録しない |
| タイムアウト、応答消失 | 結果不明として照合または確認してから判断 |
| その他のAPIエラー | 権限、宛先、リクエスト内容を別々に確認 |
ローカルジョブIDは内部の二重登録を防ぐもので、リモート側の厳密な1回実行を保証しません。送信成功後、応答を保存する前にクラッシュすれば、結果不明の状態は残ります。outboxだけで重複送信を完全に防げるとは説明できません。
受信の健全性も別の境界です。更新を認証して永続保存したら、送信の待機とは独立して受信確認を返します。更新自体が届かない場合は getWebhookInfo診断ガイド を参照してください。pending_update_count は配信待ちの更新数であり、送信キューの長さではありません。
UnifyPortの契約とは分ける
UnifyPortの非公式インターフェースは接続済みメッセージアカウントを使い、別のテキスト送信契約 に従います。エラーには error.code、error.numeric_code、error.message と、追跡用の request_id があります。エラーリファレンス はBot APIの parameters.retry_after を定義していないため、上の分類器をそのまま使わないでください。
LINEなどを含む複数チャネルのワーカーでは、ジョブ保存は共通化しても、結果の分類は契約別に実装します。HTTPステータスと構造化エラーを保存します。リクエスト追跡ガイド が説明するように、X-Request-Id は送信の冪等性キーとして保証されていません。非公式インターフェースでも上流の制約はなくならず、結果不明の送信を安全に繰り返せるわけではありません。
送信エラーと、自分の受信サーバーが返すステータスも分けます。UnifyPortのwebhook配信仕様 では再試行は即時に行われ、Retry-After は考慮されません。受信サーバーから 429 を返すことは、送信の待機を予約する手段ではありません。
確認項目とFAQ
Telegramへ意図的に大量送信するのではなく、ローカルの応答フィクスチャで検証します。同じボットの2ワーカー、待機中の再起動、後から届く長い待機値、retry_after の欠落、待機中のキャンセル、送信応答の消失を確認してください。保存済み期限より早く実行されず、結果不明が別状態になることを確かめます。これは提案する確認項目であり、実施済み結果ではありません。
retry_afterの単位は秒ですか、ミリ秒ですか?
秒です。ミリ秒で保存するスケジューラーでは明示的に変換します。
ワーカーを増やせば429は解消しますか?
増やすだけでは解消しません。同じボットの制約を共有するため、調整のない並列化は拒否を増やす場合があります。
待てば必ず送信できますか?
いいえ。今回の待機条件を満たすだけです。その他の条件、権限、後続のAPI応答も有効です。
タイムアウトも同じ方法で再試行しますか?
いいえ。応答のないタイムアウトは流量制御による拒否と確認できず、送信結果が不明な可能性があります。
次のステップと参考資料
既存の送信処理に永続的な待機期限を追加し、並列数を増やす前にクラッシュ境界を確認します。接続済みアカウントのクライアントは、Bot API互換を仮定せず UnifyPortエラーリファレンス から始めてください。
公式資料の確認日:2026-10-18。
メッセージ連携を安定したプロダクトパイプラインへ。
まずは 1 つの API で送信を始め、標準イベントですべての inbound メッセージを業務システムへ戻しましょう。