Массовая рассылка — самая частая причина, по которой бот получает ограничения от Telegram. Платформа жёстко контролирует темп отправки и поведение пользователей: достаточно нескольких жалоб «Report Spam», и бот уезжает в shadow-ban или, в худшем случае, блокируется полностью. Разберём, как правильно строить рассылки, чтобы оставаться в пределах официальных лимитов, какие архитектурные паттерны выдерживают базы в сотни тысяч подписчиков и как не нарушить 152-ФЗ и закон «О рекламе».
Какие лимиты у Bot API
Telegram официально декларирует:
- не больше 30 сообщений в секунду разным пользователям суммарно от одного бота;
- не больше 1 сообщения в секунду одному пользователю в личке;
- не больше 20 сообщений в минуту в одну группу или канал.
На практике это значит, что базу в 100 000 человек теоретически можно разослать за час — если качественно соблюдать темп. Но Telegram отслеживает не только частоту: важно соотношение «отправлено / прочитано / репорт». Если из 1000 сообщений 50 были репорнуты как спам — бот получит ограничения, даже если каждый отдельный запрос укладывался в 30 RPS.
Лимиты не публикуются полностью: часть из них — soft, часть — hard. Hard ограничения возвращают 429 Too Many Requests с заголовком retry_after. Soft — это то, что Telegram применяет молча: shadow-ban, замедление доставки, понижение видимости бота в поиске.
Tier-аккаунты и расширенные лимиты
Для крупных проектов Telegram даёт повышенные лимиты по запросу через @BotSupport. Условия:
- бот существует не менее 3-6 месяцев;
- средняя нагрузка стабильно упирается в стандартные лимиты;
- в боте нет жалоб и нарушений ToS;
- описан бизнес-кейс с цифрами.
После аппрува можно получить от 100 до 1000+ сообщений в секунду. Но это не «купить лимит» — это исключение для нагруженных продуктов уровня крупных СМИ, маркетплейсов или новостных каналов.
Что такое FloodWait и как с ним работать
FloodWait — это исключение, которое возвращает Bot API при превышении лимитов. В теле ответа есть parameters.retry_after — количество секунд, которые нужно подождать перед повторной попыткой.
Пример в Python с aiogram:
import asyncio
from aiogram import Bot
from aiogram.exceptions import TelegramRetryAfter, TelegramForbiddenError, TelegramBadRequest
async def safe_send(bot: Bot, chat_id: int, text: str, max_retries: int = 3):
for attempt in range(max_retries):
try:
await bot.send_message(chat_id, text)
return "ok"
except TelegramRetryAfter as e:
wait = e.retry_after + 1
await asyncio.sleep(wait)
except TelegramForbiddenError:
return "blocked_by_user"
except TelegramBadRequest as e:
if "chat not found" in str(e).lower():
return "chat_not_found"
raise
return "retry_exhausted"
Главное правило: retry_after — это не рекомендация, а обязательство. Если проигнорировать, следующий retry_after будет в 2-3 раза больше, и так до временной полной блокировки бота.
Главный риск — массовые жалобы
Если 1-2% получателей нажимают «Report Spam» в первые минуты после рассылки, бот быстро улетает в ограничения. Сообщения начинают «теряться» без явной ошибки — Bot API возвращает 200, но пользователю ничего не приходит. Это и есть тот самый shadow-ban.
Чтобы этого не происходило, важно:
- слать только тем, кто сам начал диалог с ботом (Telegram физически не даёт писать тем, кто не нажал
/start); - давать понятную причину сообщения («вы оставили заявку», «вы подписались на канал»);
- встроить кнопку «отписаться» в каждое массовое сообщение;
- сегментировать базу и не слать одинаковый текст всем подряд;
- следить за метрикой
complaint rateи останавливать рассылку при превышении 0.5%.
Согласие на рассылку и 152-ФЗ
В России рассылка рекламы регулируется законом «О рекламе» (ст. 18) и 152-ФЗ «О персональных данных». Базовые требования:
- нужна прямая выраженная воля на получение рекламы (галочка по умолчанию выключена, отдельный чекбокс под рекламные сообщения);
- хранение Telegram ID — обработка ПДн, нужна политика конфиденциальности;
- кнопка «отписаться» должна работать в один клик;
- хранить лог согласий с timestamp, IP и явной формулировкой согласия.
Штраф ФАС за рассылку без согласия — от 100 000 до 500 000 ₽ за факт. РКН за нарушение 152-ФЗ — отдельно. Подтверждать согласие лучше через бота: пользователь жмёт /start, бот показывает тариф/услугу и кнопку «Получать рассылку», бот пишет в БД флаг и timestamp.
Правильная архитектура отправки
Минимальная отказоустойчивая схема:
- Очередь сообщений (Redis, RabbitMQ) — все рассылочные задачи кладутся туда.
- Worker с rate-limiter — берёт по N задач в секунду, не превышая 30 RPS.
- Обработка ошибок Bot API:
429 Too Many Requestsсretry_after— подождать, повторить;403 Forbidden(бот заблокирован пользователем) — пометить чат как неактивный, больше не слать;400 chat not found— то же самое, удалить из базы рассылок.
- Логирование доставки и реакций — для аналитики и сегментации.
- Дедупликация — один и тот же
chat_idне должен попасть в очередь дважды за рассылку.
Без этого механизма рассылка либо «упрётся» в 429-е и растянется в 10 раз, либо начнёт долбиться в неактивные чаты и заработает плохую репутацию.
Брокеры очередей: что выбрать
| Брокер | Язык бэкенда | Когда брать |
|---|---|---|
| Celery | Python | Классика для Django/FastAPI, много готовых паттернов, тяжеловат |
| arq | Python (asyncio) | Лёгкий, нативно асинхронный, идеально под aiogram |
| RQ | Python | Самый простой, без сложных сценариев |
| BullMQ | Node.js | Лучший выбор под grammy/telegraf, отличный UI (Bull Board) |
| Sidekiq | Ruby | Если бэкенд на Rails |
| RabbitMQ | любой | Когда нужны сложные роутинги, приоритеты, fan-out |
Для большинства Telegram-проектов хватает Redis + arq (Python) или Redis + BullMQ (Node). RabbitMQ оправдан только при многотысячных RPS и сложной логике маршрутизации.
Rate limiter: token bucket per chat
Базовый паттерн — token bucket: на каждый chat_id ведётся «корзина» токенов, токен расходуется при отправке, восстанавливается с заданной скоростью. Глобально — sliding window на 30 RPS.
Пример на Python с Redis:
import time
import redis.asyncio as redis
class TelegramRateLimiter:
def __init__(self, r: redis.Redis):
self.r = r
self.global_key = "tg:global:rate"
self.global_limit = 28 # запас в 2 RPS
async def acquire(self, chat_id: int) -> float:
now = time.time()
# Глобальный sliding window 1s
pipe = self.r.pipeline()
pipe.zremrangebyscore(self.global_key, 0, now - 1)
pipe.zcard(self.global_key)
pipe.zadd(self.global_key, {f"{now}:{chat_id}": now})
pipe.expire(self.global_key, 2)
_, count, _, _ = await pipe.execute()
if count >= self.global_limit:
return 1.0 # подождать секунду
# Per-chat limit: 1 msg/sec
chat_key = f"tg:chat:{chat_id}"
last = await self.r.get(chat_key)
if last and now - float(last) < 1.0:
return 1.0 - (now - float(last))
await self.r.set(chat_key, now, ex=5)
return 0.0
В вызывающем коде:
wait = await limiter.acquire(chat_id)
if wait > 0:
await asyncio.sleep(wait)
await safe_send(bot, chat_id, text)
Очередь рассылки на Celery
Полный пример производственной задачи рассылки на Celery с обработкой 429/403/400:
from celery import Celery
from celery.exceptions import Retry
from telegram import Bot
from telegram.error import RetryAfter, Forbidden, BadRequest
app = Celery("broadcast", broker="redis://localhost:6379/0")
bot = Bot(token=os.environ["BOT_TOKEN"])
@app.task(bind=True, max_retries=5, acks_late=True)
def send_broadcast_message(self, chat_id: int, text: str, broadcast_id: str):
try:
bot.send_message(chat_id=chat_id, text=text, parse_mode="HTML")
DeliveryLog.create(
broadcast_id=broadcast_id,
chat_id=chat_id,
status="delivered",
)
except RetryAfter as e:
raise self.retry(countdown=e.retry_after + 1)
except Forbidden:
Subscriber.deactivate(chat_id, reason="bot_blocked")
DeliveryLog.create(
broadcast_id=broadcast_id,
chat_id=chat_id,
status="blocked",
)
except BadRequest as e:
msg = str(e).lower()
if "chat not found" in msg or "user is deactivated" in msg:
Subscriber.deactivate(chat_id, reason="chat_not_found")
else:
raise
def enqueue_broadcast(broadcast_id: str, text: str):
subscribers = Subscriber.active().values_list("chat_id", flat=True)
for chat_id in subscribers:
send_broadcast_message.apply_async(
args=[chat_id, text, broadcast_id],
queue="broadcast",
rate_limit="25/s",
)
rate_limit="25/s" — это Celery-уровень throttling с запасом ниже 30 RPS. Дополнительно рекомендуется ограничить параллелизм воркеров: celery -A app worker --concurrency=4 -Q broadcast.
Параллельные воркеры с лимитом на инстанс
Если запустить 10 воркеров, каждый с лимитом 30 RPS, суммарно бот будет слать 300 RPS и моментально получит бан. Поэтому:
- лимит должен быть глобальным (через Redis), а не локальным (в памяти процесса);
- альтернативно — ровно один воркер-отправитель на бота, остальные занимаются подготовкой данных, рендерингом шаблонов, логированием;
- при горизонтальном масштабировании заменить инстансы на «диспетчер + N исполнителей», где диспетчер раздаёт токены через Redis.
Обработка ошибок: таблица реакций
| Код / Исключение | Причина | Что делать |
|---|---|---|
429 Too Many Requests | Превышен rate limit | Подождать retry_after секунд, повторить |
403 Forbidden: bot was blocked | Пользователь заблокировал бота | Деактивировать chat_id в БД, не пытаться больше |
400 chat not found | chat_id не существует | Деактивировать в БД |
400 user is deactivated | Аккаунт удалён | Деактивировать в БД |
400 message is too long | Превышение 4096 символов | Разрезать сообщение, повторить |
400 can't parse entities | Битый HTML/Markdown | Залогировать, отправить как plain text |
5xx | Временная ошибка Telegram | Exponential backoff: 1s, 2s, 4s, 8s; после 5 попыток — DLQ |
Connection timeout | Сетевая проблема | Retry с backoff |
DLQ (dead-letter queue) — отдельная очередь, куда падают сообщения, не доставленные после всех попыток. Раз в день эту очередь надо просматривать вручную или скриптом.
Шаблоны и персонализация
Сырой текст в коде — путь к ошибкам. Используйте Jinja2 для Python, MJML для HTML-писем (если параллельно есть email-канал) или Handlebars для Node. Пример:
from jinja2 import Template
template = Template("""
Привет, {{ name }}!
У вас есть промокод {{ promo }} со скидкой {{ discount }}% до {{ expires_at.strftime('%d.%m') }}.
<a href="https://example.com/?code={{ promo }}">Перейти к покупке</a>
<i>Чтобы отписаться, нажмите /unsubscribe</i>
""")
text = template.render(
name=user.first_name or "друг",
promo="SALE26",
discount=15,
expires_at=datetime(2026, 3, 31),
)
Хранить шаблоны лучше в БД (с версионированием) или в Git — чтобы не катить деплой ради правки одной запятой.
A/B-тесты копирайта
Половина успеха рассылки — текст. Тестируйте варианты:
- Split 50/50 — две версии текста, по 50% базы на каждую;
- Multivariate — 3-5 вариантов с разной длиной, эмодзи, CTA;
- Темпоральный — одна и та же версия в разное время (утро vs вечер).
Минимальный объём для статзначимости — около 1000 получателей на вариант. Метрика — CTR (нажатие кнопки / переход по ссылке) или конверсия в целевое действие. Если CTR разнятся в 1.5-2 раза — победителем катите по основной базе.
Сегментация повышает доходимость
Сегментация — это не маркетинговая модель, а способ снизить долю репортов. Простейшие срезы:
- по источнику регистрации (откуда пришёл — сайт, лендинг, реклама);
- по дате последней активности (активные за 30 дней vs «спящие»);
- по поведению (купил vs только смотрел каталог);
- по интересам (категории товаров, тематика);
- по тарифу/сегменту клиента;
- по географии (для офлайн-офферов).
Чем ближе сообщение к интересу пользователя — тем меньше жалоб и выше конверсия. На практике сегментированная рассылка в 1000 человек часто даёт больше заказов, чем «по всем» в 50 000.
Метрики, которые надо считать
| Метрика | Формула | Норма |
|---|---|---|
| Delivery rate | доставлено / отправлено | больше 95% |
| Read rate | прочитано (если доступно) / доставлено | 60-80% |
| CTR | кликов по кнопке / доставлено | 5-15% для промо |
| Conversion rate | целевое действие / доставлено | 1-5% для продаж |
| Unsubscribe rate | отписок / доставлено за рассылку | менее 1% |
| Complaint rate | репортов / доставлено | менее 0.1% |
При complaint rate выше 0.5% — рассылка должна быть остановлена автоматически. Это hard-сигнал: продолжать = шаг к бану бота.
Время отправки и ритм
Несколько эмпирических правил, которые подтверждаются на больших объёмах:
- утро 9:00-10:00 и вечер 19:00-21:00 — самые открываемые слоты;
- не больше 1-2 рассылок в неделю на сегмент, иначе репорты растут;
- транзакционные сообщения (статус заказа, напоминание) воспринимаются лучше промо;
- триггерные цепочки (брошенная корзина, реактивация) работают надёжнее «ковровых» рассылок;
- учитывайте часовые пояса: 9:00 МСК — это полночь в Магадане.
Отписка одной кнопкой
Кнопка «Отписаться» — must-have по UX и по закону. Минимальная реализация:
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
unsubscribe_kb = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="Отписаться", callback_data="unsub")],
])
@dp.callback_query(F.data == "unsub")
async def handle_unsub(callback):
Subscriber.set_marketing_consent(callback.from_user.id, False)
await callback.message.edit_reply_markup(reply_markup=None)
await callback.answer("Вы отписаны от рассылки", show_alert=True)
Альтернатива — команда /unsubscribe или раздел в главном меню бота. Но кнопка под каждым промо-сообщением работает лучше всего.
Юридическая часть
Массовые рекламные сообщения подпадают под 152-ФЗ и закон «О рекламе»:
- нужно согласие на получение рекламы (явное или вытекающее из оферты);
- в каждом рекламном сообщении должна быть возможность отписаться;
- хранение базы подписчиков — это обработка ПДн с соответствующей политикой;
- для трансграничной передачи (а Telegram — это передача за рубеж) — отдельное уведомление в РКН.
Если бот находится в реестре операторов ПДн и предлагает «отписаться» в один клик — это сильно снижает риски как со стороны Telegram, так и со стороны регулятора. ФАС может оштрафовать на сумму от 100 000 до 500 000 ₽ за рассылку рекламы без согласия.
Что точно не стоит делать
- Покупать готовые базы Telegram ID и слать им. Это прямой путь в бан.
- Слать всем одинаковый текст «по всей базе» без сегментации.
- Игнорировать
403 Forbiddenи продолжать отправлять заблокировавшим бота. - Использовать сторонние «массовые рассыльщики», работающие через юзер-аккаунты, — это нарушает ToS Telegram и грозит блокировкой основного аккаунта владельца.
- Парсить участников чужих чатов и каналов — двойное нарушение (и Telegram, и 152-ФЗ).
- Прятать кнопку отписки или делать её неработающей.
Итого
Рассылка в Telegram-боте — это технический процесс с жёсткими лимитами, а не «нажать кнопку и разослать всем». Корректная архитектура с очередью (Celery/arq/BullMQ), глобальным rate-limiter через Redis (token bucket per chat + sliding window 30 RPS), обработкой ошибок (429 → wait, 403/400 → деактивация в БД) и сегментацией даёт миллионы доставленных сообщений без бана. Юридическая часть — согласие через бота, отписка одной кнопкой, политика ПДн. Игнорирование правил приводит к shadow-ban через 2-3 «ковровых» рассылки и потере канала продаж, плюс штрафу ФАС до 500 000 ₽.
Частые вопросы
Какие официальные лимиты у Telegram Bot API на рассылки?
Telegram декларирует три ограничения. Не больше 30 сообщений в секунду разным пользователям. Не больше 1 сообщения в секунду одному пользователю. Не больше 20 сообщений в минуту в одну группу. На практике это значит, что базу в 100 000 человек теоретически можно разослать за час — если качественно соблюдать темп. Но Telegram отслеживает не только частоту: важно соотношение «отправлено / прочитано / репорт». При плохом соотношении срабатывает shadow-ban даже в пределах формальных лимитов. Для крупных проектов через @BotSupport можно запросить расширенные лимиты до 1000+ RPS, но только при стабильной нагрузке и отсутствии нарушений.
Что такое FloodWait и как его обрабатывать?
FloodWait — это исключение, которое возвращает Bot API с кодом 429 Too Many Requests при превышении лимитов. В теле ответа есть параметр retry_after — количество секунд, которые нужно подождать перед повторной попыткой. Главное правило: retry_after — это не рекомендация, а обязательство. Если проигнорировать, следующий retry_after будет в 2-3 раза больше, и так до временной полной блокировки бота. В коде нужно ловить TelegramRetryAfter (aiogram), Forbidden, BadRequest по отдельности и реагировать корректно: для 429 — ждать и повторять, для 403/400 — деактивировать chat_id в БД и больше не слать.
Что такое shadow-ban в Telegram-боте и как его избежать?
Если 1-2% получателей нажимают «Report Spam» в первые минуты после рассылки, бот быстро улетает в ограничения. Сообщения начинают «теряться» без явной ошибки — Bot API возвращает 200, но пользователю ничего не приходит. Это и есть shadow-ban. Чтобы этого не происходило, важно: слать только тем, кто сам начал диалог с ботом; давать понятную причину сообщения; встроить кнопку «отписаться» в каждое массовое сообщение; сегментировать базу и не слать одинаковый текст всем подряд. Метрика complaint rate должна быть ниже 0.1%, при превышении 0.5% рассылку нужно автоматически останавливать.
Как технически устроена отказоустойчивая рассылка в Telegram?
Минимальная схема из четырёх компонентов. Очередь сообщений (Redis + arq/Celery, RabbitMQ, BullMQ для Node) — все рассылочные задачи кладутся туда. Worker с rate-limiter — берёт по N задач в секунду, не превышая 30 RPS, лимит должен быть глобальным через Redis, а не в памяти процесса. Обработка ошибок Bot API: 429 с retry_after — подождать, повторить; 403 Forbidden — пометить чат как неактивный, больше не слать; 400 chat not found — то же самое. Логирование доставки и реакций для аналитики. Дедупликация: один chat_id не должен попасть в очередь дважды за рассылку. DLQ для сообщений, не доставленных после всех попыток.
Как сегментация базы помогает избежать бана от Telegram?
Сегментация — это не маркетинговая модель, а способ снизить долю репортов. Простейшие срезы: по источнику регистрации, по дате последней активности (активные за 30 дней vs «спящие»), по поведению (купил vs только смотрел каталог), по интересам, по тарифу клиента, по географии. Чем ближе сообщение к интересу пользователя — тем меньше жалоб и выше конверсия. На практике сегментированная рассылка в 1000 человек часто даёт больше заказов, чем «по всем» в 50 000. A/B-тесты копирайта (split 50/50 или multivariate с 3-5 вариантами) на выборке от 1000 человек на вариант помогают найти лучшую формулировку.
Какие юридические требования к рассылкам в Telegram-боте?
Массовые рекламные сообщения подпадают под 152-ФЗ и закон «О рекламе» (ст. 18). Нужно согласие на получение рекламы — прямая выраженная воля, отдельный чекбокс под рекламные сообщения, не галочка по умолчанию. В каждом рекламном сообщении должна быть возможность отписаться в один клик. Хранение базы подписчиков — это обработка ПДн с соответствующей политикой, регистрация в реестре операторов ПДн. Для Telegram дополнительно — уведомление о трансграничной передаче ПДн в РКН. Штраф ФАС за рассылку без согласия — от 100 000 до 500 000 ₽ за факт. Подтверждать согласие лучше через самого бота с логом timestamp.
Какие метрики надо считать у Telegram-рассылки?
Шесть ключевых метрик. Delivery rate (доставлено / отправлено) — норма выше 95%. Read rate (прочитано / доставлено, если доступно) — 60-80%. CTR (кликов по кнопке / доставлено) — 5-15% для промо. Conversion rate (целевое действие / доставлено) — 1-5% для продаж. Unsubscribe rate (отписок / доставлено) — норма меньше 1% за рассылку. Complaint rate (репортов / доставлено) — критичная метрика, норма меньше 0.1%, при превышении 0.5% рассылку нужно автоматически останавливать. Без этих метрик невозможно понять, выгорает база или растёт.