Skip to main content

Push Notifications

Система массовых push-уведомлений через Telegram Bot API с планировщиком, батчевой рассылкой, retry логикой и аналитикой кликов.


1. Summary

Goal: Отправка push-уведомлений пользователям через Telegram Bot API с возможностью планирования, массовой рассылки и отслеживания эффективности.

User Value: Информирование пользователей о событиях, акциях и обновлениях; возврат неактивных пользователей; рекламные интеграции с отслеживанием кликов.

Admin-only домен

Весь функционал push-уведомлений управляется из админ-панели. User API отсутствует — пользователи являются получателями, а не инициаторами.


2. Business Logic

Lifecycle уведомления

1. Создание (PENDING)

  • Администратор создаёт push через админку: title, message, imageUrl, buttonText, buttonUrl
  • Опционально указывается scheduledFor (дата/время в UTC, подсказка "UTC (МСК −3ч)" в форме)
  • Опционально указывается advertiser для рекламных push
  • targetAll определяет аудиторию (все vs сегментированные)
  • minActivityDays — фильтр по минимальной активности пользователя

2. Отправка (SENDING)

  • Планировщик (cron каждую минуту) находит PENDING push с наступившим scheduledFor
  • Или администратор нажимает "Отправить сейчас" (send-now) / "Broadcast"
  • Статус переключается на SENDING

3. Доставка

  • Broadcast разбивает пользователей на батчи по 25 (Telegram лимит: 30 msg/sec)
  • Задержка между батчами: 1000ms
  • Каждому пользователю отправляется текст или фото с inline-кнопкой
  • Персонализация: {name} placeholder заменяется на имя пользователя

4. Результат (SENT / FAILED / CANCELLED)

  • SENT — рассылка завершена успешно
  • FAILED — больше ошибок, чем успехов (или все retry исчерпаны)
  • CANCELLED — администратор отменил активную рассылку

Scheduling (Планировщик)

Cron job запускается каждую минуту (* * * * *) и выполняет:

  1. Обработка pending: Находит push с status = PENDING и scheduledFor <= now()
  2. Обработка retry: Находит push с status = FAILED, retryCount < maxRetries, nextRetryAt <= now()
  3. Восстановление stuck: Push в статусе SENDING более 10 минут → сброс на PENDING
Mutex

Планировщик использует флаг isRunning для предотвращения параллельного выполнения. Если предыдущий цикл не завершился, новый пропускается.

Retry Logic (Exponential Backoff)

Формула retry
delay = min(baseDelay * 3^retryCount, maxDelay)
ПараметрЗначение
maxRetries3
baseDelayMinutes5
maxDelayMinutes60
Retry #Задержка
0 → 15 min (5 * 3^0)
1 → 215 min (5 * 3^1)
2 → 345 min (5 * 3^2)

После 3 неудачных попыток push остаётся в FAILED с nextRetryAt = null.

Broadcast System

Массовая рассылка push-уведомлений с прогресс-трекингом:

  1. Получение eligible users (chatId != null, canReceivePush = true, botStatus != BLOCKED)
  2. Разбиение на батчи по batchSize (default: 25)
  3. Параллельная отправка внутри батча
  4. Задержка delayBetweenBatches (default: 1000ms) между батчами
  5. Запись в PushBatch для каждого батча
  6. Возможность отмены активной рассылки (CANCELLED)
Telegram Rate Limits

Telegram Bot API ограничивает 30 сообщений в секунду. batchSize=25 выбран с запасом для надёжности.

Click Tracking

При trackingEnabled = true (по умолчанию):

  1. PushRedirectService создаёт tracking URL: {REDIRECT_PUSH_URL}/{brand}/{push_hash}
  2. Brand извлекается из оригинального URL (t.me → channel_username, youtube.com → youtube, и т.д.)
  3. shortCode генерируется как push_{md5_hash[0:8]}
  4. При клике пользователя: redirect на оригинальный URL + инкремент clicksCount + запись PushAnalytics (action: CLICKED)

Audience Targeting

  • targetAll = true — все eligible users
  • targetAll = false — сегментированная аудитория
  • minActivityDays — минимальное количество дней с момента регистрации
  • Audience types: ALL, PREMIUM_ONLY, NON_PREMIUM_ONLY

Bot Block Detection

При отправке push, если Telegram API возвращает ошибку блокировки:

  • Пользователь помечается как botStatus = BLOCKED, canReceivePush = false, chatId = null
  • Создаётся запись UserBotInteraction для аналитики
  • Push помечается как FAILED для этого пользователя (без retry)

Onboarding Push (автоматический)

Автоматическая отправка одноразового push новым пользователям, не обнаружившим квизы:

Триггер: Cron job каждый час (:30)

Критерии eligible пользователя:

  • Зарегистрирован > 24 часов назад
  • onboardingPushSentAt IS NULL (ещё не получал)
  • canReceivePush = true, chatId IS NOT NULL, botStatus != BLOCKED
  • isBanned = false, deletedAt = null
  • Нет записей UserSeasonStats с scrapFromQuizzes > 0 (не отвечал на квизы)

Контент: Текст с подсказкой про квизы как основной источник скрапа + inline-кнопка на канал.

Идемпотентность: Поле User.onboardingPushSentAt помечается сразу после отправки (даже при ошибке — чтобы не застревать на одном юзере).

Batch limit: 50 пользователей за цикл.

Отличие от Broadcast

Onboarding push не использует PushNotification модель и Broadcast систему. Это отдельный lightweight механизм с прямым вызовом Telegram Bot API (sendMessage), без retry, без аналитики PushAnalytics.

Edge Cases

СитуацияПоведениеКод/Статус
📬 Onboarding push: юзер уже отвечал на квизыПропуск (фильтр scrapFromQuizzes > 0)
📬 Onboarding push: ошибка отправкиПомечается onboardingPushSentAt всё равно (не застревать)
❌ Пользователь заблокировал ботаСтатус → BLOCKED, chatId → null, skip retryBOT_BLOCKED
❌ chatId отрицательный (группа/канал)Пропуск (SKIPPED)GROUP_CHANNEL_CHAT_ID
❌ Нет chatId или push не найденПропуск (SKIPPED)MISSING_CHAT_ID_OR_PUSH
⏱️ Push застрял в SENDING > 10 минАвтосброс → PENDINGStuck recovery
⏱️ Все retry исчерпаны (3)Push остаётся FAILED, nextRetryAt = nullFinal failure
✅ Broadcast отменёнСтатус → CANCELLED, прекращение обработкиCANCELLED
✅ Нет eligible usersПустой результат (success: true, sentTo: 0)Empty broadcast

3. ADR (Architectural Decisions)

Почему Telegram Bot API, а не Web Push?

Проблема: Нужны push-уведомления для пользователей Telegram Mini App.

Решение: Отправка через Telegram Bot API (sendMessage / sendPhoto).

Альтернативы (отклонены):

  • Web Push — ограниченная поддержка в TMA, требует Service Worker
  • Email — не подходит для мобильного UX, у пользователей нет email в системе

Последствия: Зависимость от Telegram API лимитов (30 msg/sec), но гарантированная доставка для активных пользователей бота.

Почему PushRedirect вместо прямых ссылок?

Проблема: Нужно отслеживать клики по кнопкам в push-уведомлениях.

Решение: Создание промежуточных redirect-ссылок через PushRedirectService. URL формата {host}/{brand}/{push_hash} перенаправляет на оригинальный URL с записью клика.

Альтернативы (отклонены):

  • Telegram callback_data — ограничен 64 байтами, требует webhook обработки
  • Внешние аналитические сервисы — усложняют архитектуру

Последствия: Собственный redirect-сервис даёт полный контроль над аналитикой и позволяет извлекать бренд из URL для читаемых ссылок.

Почему все datetime в UTC?

Проблема: datetime-local HTML input отдаёт строку без timezone. new Date("2024-01-15T15:30") интерпретирует её как local browser timezone — баг.

Решение: Все datetime инпуты в админке работают в UTC. Утилиты formatUTCForInput() / parseInputAsUTC() из admin/src/utils/date.utils.ts добавляют Z suffix при парсинге. Каждый инпут показывает подсказку "UTC (МСК −3ч)".

Timezone

scheduledFor хранится в UTC. Админ вводит время в UTC. Конвертации не требуется — frontend отправляет ISO UTC строку напрямую.


4. Architecture

Services Overview

Key Components

КомпонентПутьОписание
AdminPushControllerbackend/src/domains/push/controllers/admin-push.controller.ts21 эндпоинт: CRUD, broadcast, audience, stats, scheduler, test send, autocomplete
PushSchedulerServicebackend/src/domains/push/services/push-scheduler.service.tsCron планировщик (каждую минуту): pending, retry, stuck recovery
PushSendingServicebackend/src/domains/push/services/push-sending.service.tsОтправка push одному пользователю через Telegram Bot API
BroadcastServicebackend/src/domains/push/services/broadcast.service.tsМассовая рассылка: батчи, прогресс-трекинг, отмена
PushAnalyticsServicebackend/src/domains/push/services/push-analytics.service.tsСтатистика: chart data, summary, фильтрация по периоду/advertiser
PushRedirectServicebackend/src/domains/push/services/push-redirect.service.tsTracking URL: создание short code, redirect, подсчёт кликов
AudienceServicebackend/src/domains/admin/services/audience.service.tsАудитория: stats, preview, availability, premium фильтры
OnboardingPushServicebackend/src/domains/push/services/onboarding-push.service.tsАвтоматический onboarding push: поиск eligible юзеров, отправка через Telegram Bot API
OnboardingPushJobbackend/src/domains/push/jobs/onboarding-push.job.tsCron job (каждый час в :30): запуск OnboardingPushService
PushUtilsbackend/src/domains/push/utils/push.utils.tsEligible users, canReceivePush, user push stats
Routesbackend/src/domains/push/routes/admin-push.routes.tsAdmin API маршруты (21 endpoint)
Schemasbackend/src/domains/push/schemas/admin-push.schemas.tsFastify JSON Schema для валидации
Typesbackend/src/domains/push/types/push.types.tsTypeScript типы для аналитики

5. Database Schema

Models

МодельОписаниеКлючевые поля
PushNotificationPush-уведомлениеtitle, message, imageUrl, advertiser, scheduledFor, targetAll, status, buttonText, buttonUrl, trackingEnabled, sentCount, retryCount, nextRetryAt, lastError
PushAnalyticsАналитика действийpushId, userId, action (enum), sessionId, userAgent, ipAddress, messageId, errorCode, errorMessage
PushRedirectTracking redirectshortCode (unique), pushId, originalUrl, isActive, clicksCount
PushBatchБатч массовой рассылкиpushId, batchNumber, totalUsers, processedUsers, successCount, failureCount, status

Enums

EnumЗначенияОписание
PushStatusPENDING, SENDING, SENT, FAILED, CANCELLEDЖизненный цикл push
PushActionSENT, DELIVERED, OPENED, CLICKEDТипы аналитических событий
BotRegistrationStateNEW_USER, ACTIVE, BLOCKED, REACTIVATEDСтатус взаимодействия пользователя с ботом (используется в User модели)

Relationships


6. API Endpoints

Prefix: /admin/push

Auth: Admin JWT (adminJWTAuth)

CRUD

МетодЭндпоинтОписание
GET/admin/pushСписок push-уведомлений (пагинация, фильтры: status, advertiser, targetAll, search, сортировка)
POST/admin/pushСоздание push (title, message, imageUrl, advertiser, scheduledFor, targetAll, minActivityDays, buttonText, buttonUrl, trackingEnabled)
PUT/admin/push/:pushIdОбновление push (все поля опциональны, удаление старого imageUrl при замене)
DELETE/admin/push/:pushIdУдаление push (+ очистка физического файла imageUrl)

Broadcast

МетодЭндпоинтОписание
POST/admin/push/:pushId/send-nowТестовая отправка push первому eligible user
POST/admin/push/broadcastЗапуск массовой рассылки (фоновый процесс, batchSize=25, delay=1000ms)
GET/admin/push/:pushId/broadcast-statusСтатус рассылки (прогресс, батчи, success/failure counts)
POST/admin/push/:pushId/cancel-broadcastОтмена активной рассылки
GET/admin/push/active-broadcastsСписок всех активных рассылок с прогрессом

Audience

МетодЭндпоинтОписание
GET/admin/push/filtersДоступные фильтры (statuses с count, advertisers с count)
GET/admin/push/audience-statsСтатистика аудитории (total, eligible, blocked, premium)
POST/admin/push/preview-audiencePreview количества получателей по audienceType (ALL, PREMIUM_ONLY, NON_PREMIUM_ONLY)
GET/admin/push/audience-availabilityПроверка доступности аудитории

Stats

МетодЭндпоинтОписание
GET/admin/push/statsСтатистика push (chartData + summary, фильтры: period, pushId, status, title, advertiser, interval, customDateRange)
GET/admin/push/premium-statsСтатистика по Premium пользователям

Scheduler

МетодЭндпоинтОписание
GET/admin/push/scheduler-statsСтатистика планировщика (isRunning, lastRun, totalProcessed, averageProcessingTime)
POST/admin/push/scheduler-triggerРучной запуск планировщика

Test Send

МетодЭндпоинтОписание
GET/admin/push/test-recipientsСписок Telegram ID администраторов из TELEGRAM_ADMIN_IDS для быстрого выбора получателя тестовой отправки
POST/admin/push/:pushId/test-sendТестовая отправка push на конкретный chatId (body: { chatId }) через sendTestMessage() без изменения статуса push

Autocomplete

МетодЭндпоинтОписание
GET/admin/push/advertisersСписок уникальных рекламодателей (для автокомплита)
GET/admin/push/titlesСписок названий push-уведомлений (для автокомплита, с иконками статуса и датой)

  • Telegram Bot — отправка push через Telegram Bot API
  • Cases — CasePreviewModal показывает hint про квизы при insufficient balance
  • Banners — in-app рекламные баннеры (альтернативный канал)
  • Feed — лента событий (in-app уведомления)