Scaling Past Telegram's Rate Limits: Queues, Backoff and What Not to Try

Scaling Past Telegram's Rate Limits: Queues, Backoff and What Not to Try

Start with the part that saves you a week: you cannot engineer your way past Telegram's rate limits.

They are enforced server-side, per bot. Faster code does not help. More instances do not help — they share the same bot token and the same ceiling. The only supported way to raise the limit is Paid Broadcasts, and most bots cannot enable it.

So this is not about going faster. It is about behaving correctly at the ceiling.

The limits

ScopeLimit
Single chat~1 message/second
Group20 messages/minute
Bulk broadcast~30 messages/second
With Paid Broadcastsup to 1,000 messages/second

For the single-chat limit, short bursts are tolerated — "We may allow short bursts that go over this limit, but eventually you'll begin receiving 429 errors." Telegram documents no equivalent tolerance for the bulk ceiling: "The API will not allow bulk notifications to more than ~30 users per second – if you go over that, you'll start getting 429 errors."

Paid Broadcasts costs 0.1 Stars per message above the free 30/second, and requires 100,000 Stars on the bot's balance and 100,000 monthly active users to enable. Treat it as unavailable unless you clearly qualify.

Handling 429 correctly

When throttled, the API returns a retry_after value — "the number of seconds left to wait before the request can be repeated."

Use it. Exactly.

on 429:
    wait(response.parameters.retry_after)
    retry once

Three failure patterns to avoid:

Immediate retry. Deepens the throttle and can escalate it. The most common mistake.

Fixed backoff that ignores retry_after. Telegram told you the answer; guessing a different number is either too slow or gets you throttled again.

Retrying forever. Some failures will not clear on a retry — a user who blocked the bot, a deleted chat, a bad chat ID. Distinguish 429 (wait retry_after, then retry) from other 4xx (stop). One documented exception: a failed request whose response carries parameters.migrate_to_chat_id"The group has been migrated to a supergroup with the specified identifier" — should be retried against that new ID, and the new ID stored. And mark a blocked subscriber inactive rather than dead-lettering them for good: my_chat_member is delivered in private chats "only when the bot is blocked or unblocked by the user", so you find out if they come back. Retrying a failure that will not clear is how a queue backs up behind a user who blocked you in 2024.

The queue

Above a few thousand subscribers, sending directly from your handler stops working. You need a queue between "decide to send" and "actually send."

The essentials:

A single rate-limited sender. One component owns the outbound rate. If several workers send independently they will collectively exceed the limit no matter how well each behaves.

Persistence. A queue in memory loses your campaign on restart. This matters most during a large broadcast, which is exactly when a restart is most likely.

Per-chat throttling separate from global. Two limits, two mechanisms: one message/second to any single chat, ~30/second overall. A subscriber receiving a three-part sequence needs pacing that global throttling will not provide.

Priority lanes. Transactional messages — payment confirmations, support replies, /paysupport responses — must not queue behind a 50,000-person broadcast. This is the design decision people regret skipping. A user who paid and waits nearly half an hour for a confirmation — 50,000 messages at ~30/second is about 28 minutes — because a marketing campaign is running will assume the payment failed.

Dead-letter handling. Messages that fail for a reason a retry cannot fix go somewhere inspectable, not into a retry loop.

Design around the ceiling, not against it

Spread large broadcasts deliberately. Telegram's own guidance for large lists is to spread sends over 8–12 hours. A planned 8-hour send behaves predictably; one throttled unpredictably does not.

Prefer sequences to broadcasts. Sequences send on each subscriber's own timeline, so volume spreads naturally across the day and rarely approaches the limit at all. This is a genuine architectural advantage as you grow — see broadcast vs sequence.

Segment. Sending to 20,000 relevant people instead of 100,000 is five times faster and produces fewer blocks. Frequently the best performance fix is sending less.

Reuse file_ids. Re-uploading media on every send wastes bandwidth and time. A file_id for something already on Telegram's servers sends instantly with no size limit.

Set allowed_updates explicitly. Receive only the update types you handle. Less inbound traffic, less parsing. Two traps: chat_member, message_reaction and message_reaction_count are excluded unless you list them, and "If not specified, the previous setting will be used" — omitting the parameter inherits your last subscription rather than resetting it.

Tune max_connections on setWebhook — 1 to 100, default 40. Lower protects a small server; higher increases throughput on a capable one.

What not to try

Multiple bot tokens to multiply throughput. Splitting your audience across several bots to get several rate limits is a bad idea: users are fragmented across bots, your sequences and history split, and it is obvious circumvention. Solve the problem by sending less or spreading wider.

Ignoring retry_after and hoping. It escalates.

Sending from many workers without a shared limiter. Each worker is polite, the aggregate is not.

Treating the limits as approximate. They are enforced. Build for them.

A sane architecture at scale

event → queue (persistent, prioritised)
          ↓
     single rate-limited sender
       ├── global limiter (~30/sec)
       ├── per-chat limiter (1/sec)
       └── 429 handler → wait(retry_after) → retry once
          ↓
    success → log  |  unretryable failure → dead letter

That structure serves a very large bot inside Telegram's free 30/second allowance, without Paid Broadcasts. Most bots that "hit scaling problems" are missing the queue and the shared limiter, not the throughput.

The reframe

The ceiling is roughly 30 messages/second, and it is not moving. Everything worth doing follows from accepting that:

Spread sends over time. Prefer sequences. Segment harder. Never let a marketing broadcast delay a payment confirmation. Respect retry_after precisely.

Done well, the limits are almost never the thing that constrains a Telegram business. Done badly, they look like an outage.


Limits and parameters quoted from Telegram's Bot FAQ and Bot API documentation, verified September 2026.