Telegram
12 мин·

Массовая отправка в Telegram без блокировок

Архитектура очередей для массовой рассылки в Telegram: rate limiter, DLQ, мониторинг и отказоустойчивость.

Почему массовая отправка в Telegram — это сложно

Telegram разрабатывался как мессенджер для личного общения, а не как канал массовых рассылок. Из-за этого Bot API имеет ограничения на скорость отправки: обычно порядка 30 сообщений в секунду в разные чаты и около 20 сообщений в минуту в один чат (значения могут меняться). Подробнее — в нашей статье лимиты Telegram API.

При наивной реализации рассылки на 100 000 подписчиков вы столкнётесь со следующими проблемами:

  • Время отправки: при лимите порядка 30 msg/s 100 000 сообщений займут около часа
  • Ошибки 429: неизбежны без rate limiter (см. разбор ошибки 429)
  • Потеря сообщений: если процесс упадёт на 50%, вы не знаете кому отправлено
  • Заблокированные пользователи: часть пользователей блокирует бота
  • Вариативные ошибки: CHAT_NOT_FOUND, USER_DEACTIVATED, BOT_BLOCKED
Наблюдение: по данным проектов в Релая, при массовых рассылках заметная доля сообщений получает ошибки, не связанные с rate limiting (бот заблокирован, чат удалён и т.д.). Это нормально и должно быть предусмотрено в архитектуре.

Архитектура с учётом rate limiting

Правильная архитектура массовой отправки выглядит так:

Рендер схемы...

Ключевые компоненты:

  1. Scheduler — разбивает рассылку на батчи и помещает в очередь
  2. Message Queue — надёжное хранилище задач (Redis, RabbitMQ)
  3. Rate Limiter — контролирует скорость выдачи задач воркерам
  4. Worker Pool — отправляет сообщения через Telegram API
  5. DLQ — хранит неудачные отправки для анализа
  6. Monitoring — отслеживает прогресс и ошибки

Проектирование очереди (Redis / RabbitMQ)

Выбор системы очередей зависит от масштаба. Для большинства случаев Redis (с использованием BullMQ) — практичный выбор:

КритерийRedis (BullMQ)RabbitMQ
Простота настройкиВысокаяСредняя
Rate limitingВстроен в BullMQЧерез плагин
МасштабируемостьДо ~1M задачМиллионы задач
Retry-логикаВстроенаЧерез DLX
МониторингBull BoardManagement UI
Гарантия доставкиAt-least-onceAt-least-once / At-most-once

Паттерн Worker Pool

Worker Pool — основа масштабируемой отправки. Вот реализация на Node.js с использованием BullMQ:

// worker.js — воркер массовой отправки в Telegram
import { Worker } from "bullmq";
import IORedis from "ioredis";

const connection = new IORedis({ host: "localhost", port: 6379 });
const TELEGRAM_TOKEN = process.env.TELEGRAM_BOT_TOKEN;

const worker = new Worker(
  "telegram-broadcast",
  async (job) => {
    const { chatId, text, parseMode } = job.data;

    const res = await fetch(
      `https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendMessage`,
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          chat_id: chatId,
          text,
          parse_mode: parseMode || "HTML",
        }),
      }
    );

    if (res.status === 429) {
      const body = await res.json();
      const retryAfter = body.parameters?.retry_after || 5;
      // Бросаем ошибку — BullMQ сделает retry через retryAfter сек
      throw new Error(`RATE_LIMITED:${retryAfter}`);
    }

    if (res.status === 403) {
      // Пользователь заблокировал бота — не ретраим
      return { status: "blocked", chatId };
    }

    if (!res.ok) {
      throw new Error(`Telegram API error: ${res.status}`);
    }

    return { status: "sent", chatId };
  },
  {
    connection,
    concurrency: 5, // 5 параллельных воркеров
    limiter: {
      max: 25,       // не более 25 задач
      duration: 1000, // за 1 секунду
    },
  }
);

worker.on("completed", (job, result) => {
  console.log(`✓ ${job.id}: ${result.status} (${result.chatId})`);
});

worker.on("failed", (job, err) => {
  console.error(`✗ ${job?.id}: ${err.message}`);
});

И код для добавления задач в очередь:

// enqueue.js — постановка задач рассылки в очередь
import { Queue } from "bullmq";
import IORedis from "ioredis";

const connection = new IORedis({ host: "localhost", port: 6379 });
const queue = new Queue("telegram-broadcast", { connection });

async function startBroadcast(userIds, message) {
  console.log(`Начинаем рассылку на ${userIds.length} пользователей...`);

  const jobs = userIds.map((chatId, idx) => ({
    name: `msg-${chatId}`,
    data: { chatId, text: message },
    opts: {
      attempts: 3,
      backoff: { type: "exponential", delay: 5000 },
      removeOnComplete: { age: 3600 },     // убираем через 1 час
      removeOnFail: { age: 24 * 3600 },    // ошибки храним сутки
      priority: idx < 1000 ? 1 : 10,       // первые 1000 — приоритет
    },
  }));

  // Добавляем батчами по 1000
  for (let i = 0; i < jobs.length; i += 1000) {
    const batch = jobs.slice(i, i + 1000);
    await queue.addBulk(batch);
    console.log(`Добавлено ${Math.min(i + 1000, jobs.length)} / ${jobs.length}`);
  }

  console.log("Все задачи добавлены в очередь");
}

// Запуск рассылки
const userIds = await getUserIdsFromDB();
startBroadcast(userIds, "🚀 Новая функция уже доступна!");

Dead Letter Queue для неудавшихся сообщений

Dead Letter Queue (DLQ) — обязательный компонент production-системы. DLQ хранит сообщения, которые не удалось доставить после всех попыток. Причины попадания в DLQ:

  • Пользователь заблокировал бота (403 Forbidden)
  • Чат не найден или удалён (400 Bad Request)
  • Превышено максимальное количество retry
  • Невалидный формат сообщения

DLQ позволяет анализировать причины недоставок и автоматически обновлять базу подписчиков (удалять заблокировавших, деактивированных и т.д.).

Best practice: настройте задачу для периодического анализа DLQ. Если сообщение попало в DLQ по причине 403 (бот заблокирован) — помечайте пользователя как неактивного в базе. Это сэкономит ресурсы при следующей рассылке.

Мониторинг и алертинг

Без мониторинга массовая рассылка — «чёрный ящик». Вот ключевые показатели:

ПоказательОписаниеАлерт
Отправлено / всегоПрогресс рассылкиЕсли прогресс остановился >5 мин
Скорость (msg/sec)Текущая скорость отправки< 10 msg/sec при ожидаемых 25
Ошибки 429Rate limiting от Telegram> 5% от общего числа запросов
Размер очередиЗадачи, ожидающие обработкиЕсли растёт, а не уменьшается
DLQ размерНедоставленные сообщения> 10% от объёма рассылки
ETA завершенияОжидаемое время окончанияПревышение запланированного на 50%

Сегментация и батчинг

Эффективная массовая отправка начинается с правильной сегментации аудитории:

  1. Сегментируйте по активности: отправляйте сначала активным пользователям (взаимодействовали за последние 7 дней), затем менее активным
  2. Разбивайте на батчи по 5 000–10 000: это позволяет контролировать прогресс и останавливать при проблемах
  3. Добавляйте паузы между батчами: 30–60 секунд между сегментами снижают риск 429
  4. Персонализируйте: используйте имя пользователя, последнее действие — это повышает Open Rate
  5. A/B тестирование: отправьте два варианта сообщения первым 1000 пользователям, затем лучший — остальным

Тестирование массовой отправки

Перед запуском рассылки на всех пользователей обязательно протестируйте:

  • Тест на 10 пользователей: проверка контента и форматирования
  • Тест на 100 пользователей: проверка основного потока
  • Тест на 1 000 пользователей: проверка rate limiting и retry
  • Нагрузочный тест: эмулируйте полный объём с mock-сервером
// Простой mock-сервер для тестирования (Express.js)
import express from "express";

const app = express();
app.use(express.json());

let requestCount = 0;

app.post("/bot:token/sendMessage", (req, res) => {
  requestCount++;

  // Симуляция 429 при превышении лимита
  if (requestCount % 35 === 0) {
    return res.status(429).json({
      ok: false,
      error_code: 429,
      description: "Too Many Requests: retry after 3",
      parameters: { retry_after: 3 },
    });
  }

  // Симуляция заблокированного бота (5% случаев)
  if (Math.random() < 0.05) {
    return res.status(403).json({
      ok: false,
      error_code: 403,
      description: "Forbidden: bot was blocked by the user",
    });
  }

  res.json({ ok: true, result: { message_id: requestCount } });
});

app.listen(3001, () => console.log("Mock Telegram API on :3001"));

Массовая отправка через Релая

Всё вышеописанное — это серьёзная инженерная работа. Релая предлагает готовое решение, которое включает все компоненты:

КомпонентDIY-реализацияЧерез Релая
Очередь сообщенийRedis + BullMQ (настройка ~1-2 дня)Встроено
Rate limiterToken Bucket (реализация ~4 часа)Адаптивный, встроен
Retry + DLQBackoff + обработка ошибок (~1 день)Автоматически
МониторингНастройка аналитики (~2 дня)Дашборд из коробки
СегментацияРучная (SQL + код)UI для сегментов
МультиканальностьОтдельная интеграция каждого каналаTelegram + MAX + VK
Время запуска1–2 недели30 минут
Экономика: разработка DIY-системы массовой отправки стоит ~150–300 часов инженера. При стоимости часа 3 000 ₽ — это 450 000–900 000 ₽.Релая обойдётся значительно дешевле и начнёт работать сразу.

MAX как дополнительный канал

При массовой отправке через Telegram вы ограничены 30 msg/sec. Добавление MAX как второго канала даёт несколько преимуществ:

  • Параллельная доставка: сообщения уходят через оба канала одновременно, увеличивая охват
  • Резервный канал: если Telegram rate limit достигнут, критические уведомления идут через MAX
  • Растущая аудитория: MAX активно растёт в России, и ваша аудитория может быть там
  • Более предсказуемые лимиты: лимиты MAX API — прозрачны и задокументированы

Через Релая подключение MAX занимает минуты: единый API для всех каналов, единая очередь, единый дашборд. Подробнее о мультиканальном подходе — в статье устойчивая система доставки.

Итог: массовая отправка в Telegram — инженерно сложная задача. Правильная архитектура с очередями, rate limiting и DLQ критически важна. Если вы не хотите тратить недели на инфраструктуру — используйте Релая и сфокусируйтесь на содержании рассылок, а не на борьбе с лимитами.

Создайте бесплатный MAX-профиль

Если хочется не просто читать, а сразу проверить сценарий руками: подключите MAX, отправьте себе тестовое сообщение и уже потом решайте, нужны ли другие каналы.