Push Notifications
Система массовых push-уведомлений через Telegram Bot API с планировщиком, батчевой рассылкой, retry логикой и аналитикой кликов.
1. Summary
Goal: Отправка push-уведомлений пользователям через Telegram Bot API с возможностью планирования, массовой рассылки и отслеживания эффективности.
User Value: Информирование пользователей о событиях, акциях и обновлениях; возврат неактивных пользователей; рекламные интеграции с отслеживанием кликов.
Весь функционал 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 запускается каждую минуту (* * * * *) и выполняет:
- Обработка pending: Находит push с
status = PENDINGиscheduledFor <= now() - Обработка retry: Находит push с
status = FAILED,retryCount < maxRetries,nextRetryAt <= now() - Восстановление stuck: Push в статусе SENDING более 10 минут → сброс на PENDING
Планировщик использует флаг isRunning для предотвращения параллельного выполнения. Если предыдущий цикл не завершился, новый пропускается.
Retry Logic (Exponential Backoff)
Формула retry
delay = min(baseDelay * 3^retryCount, maxDelay)
| Параметр | Значение |
|---|---|
maxRetries | 3 |
baseDelayMinutes | 5 |
maxDelayMinutes | 60 |
| Retry # | Задержка |
|---|---|
| 0 → 1 | 5 min (5 * 3^0) |
| 1 → 2 | 15 min (5 * 3^1) |
| 2 → 3 | 45 min (5 * 3^2) |
После 3 неудачных попыток push остаётся в FAILED с nextRetryAt = null.
Broadcast System
Массовая рассылка push-уведомлений с прогресс-трекингом:
- Получение eligible users (chatId != null, canReceivePush = true, botStatus != BLOCKED)
- Разбиение на батчи по
batchSize(default: 25) - Параллельная отправка внутри батча
- Задержка
delayBetweenBatches(default: 1000ms) между батчами - Запись в PushBatch для каждого батча
- Возможность отмены активной рассылки (CANCELLED)
Telegram Bot API ограничивает 30 сообщений в секунду. batchSize=25 выбран с запасом для надёжности.
Click Tracking
При trackingEnabled = true (по умолчанию):
PushRedirectServiceсоздаёт tracking URL:{REDIRECT_PUSH_URL}/{brand}/{push_hash}- Brand извлекается из оригинального URL (t.me → channel_username, youtube.com → youtube, и т.д.)
- shortCode генерируется как
push_{md5_hash[0:8]} - При клике пользователя: redirect на оригинальный URL + инкремент
clicksCount+ запись PushAnalytics (action: CLICKED)
Audience Targeting
targetAll = true— все eligible userstargetAll = 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 != BLOCKEDisBanned = false,deletedAt = null- Нет записей
UserSeasonStatsсscrapFromQuizzes > 0(не отвечал на квизы)
Контент: Текст с подсказкой про квизы как основной источник скрапа + inline-кнопка на канал.
Идемпотентность: Поле User.onboardingPushSentAt помечается сразу после отправки (даже при ошибке — чтобы не застревать на одном юзере).
Batch limit: 50 пользователей за цикл.
Onboarding push не использует PushNotification модель и Broadcast систему. Это отдельный lightweight механизм с прямым вызовом Telegram Bot API (sendMessage), без retry, без аналитики PushAnalytics.
Edge Cases
| Ситуация | Поведение | Код/Статус |
|---|---|---|
| 📬 Onboarding push: юзер уже отвечал на квизы | Пропуск (фильтр scrapFromQuizzes > 0) | — |
| 📬 Onboarding push: ошибка отправки | Помечается onboardingPushSentAt всё равно (не застревать) | — |
| ❌ Пользователь заблокировал бота | Статус → BLOCKED, chatId → null, skip retry | BOT_BLOCKED |
| ❌ chatId отрицательный (группа/канал) | Пропуск (SKIPPED) | GROUP_CHANNEL_CHAT_ID |
| ❌ Нет chatId или push не найден | Пропуск (SKIPPED) | MISSING_CHAT_ID_OR_PUSH |
| ⏱️ Push застрял в SENDING > 10 мин | Автосброс → PENDING | Stuck recovery |
| ⏱️ Все retry исчерпаны (3) | Push остаётся FAILED, nextRetryAt = null | Final 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ч)".
scheduledFor хранится в UTC. Админ вводит время в UTC. Конвертации не требуется — frontend отправляет ISO UTC строку напрямую.
4. Architecture
Services Overview
Key Components
| Компонент | Путь | Описание |
|---|---|---|
| AdminPushController | backend/src/domains/push/controllers/admin-push.controller.ts | 21 эндпоинт: CRUD, broadcast, audience, stats, scheduler, test send, autocomplete |
| PushSchedulerService | backend/src/domains/push/services/push-scheduler.service.ts | Cron планировщик (каждую минуту): pending, retry, stuck recovery |
| PushSendingService | backend/src/domains/push/services/push-sending.service.ts | Отправка push одному пользователю через Telegram Bot API |
| BroadcastService | backend/src/domains/push/services/broadcast.service.ts | Массовая рассылка: батчи, прогресс-трекинг, отмена |
| PushAnalyticsService | backend/src/domains/push/services/push-analytics.service.ts | Статистика: chart data, summary, фильтрация по периоду/advertiser |
| PushRedirectService | backend/src/domains/push/services/push-redirect.service.ts | Tracking URL: создание short code, redirect, подсчёт кликов |
| AudienceService | backend/src/domains/admin/services/audience.service.ts | Аудитория: stats, preview, availability, premium фильтры |
| OnboardingPushService | backend/src/domains/push/services/onboarding-push.service.ts | Автоматический onboarding push: поиск eligible юзеров, отправка через Telegram Bot API |
| OnboardingPushJob | backend/src/domains/push/jobs/onboarding-push.job.ts | Cron job (каждый час в :30): запуск OnboardingPushService |
| PushUtils | backend/src/domains/push/utils/push.utils.ts | Eligible users, canReceivePush, user push stats |
| Routes | backend/src/domains/push/routes/admin-push.routes.ts | Admin API маршруты (21 endpoint) |
| Schemas | backend/src/domains/push/schemas/admin-push.schemas.ts | Fastify JSON Schema для валидации |
| Types | backend/src/domains/push/types/push.types.ts | TypeScript типы для аналитики |
5. Database Schema
Models
| Модель | Описание | Ключевые поля |
|---|---|---|
| PushNotification | Push-уведомление | 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 |
| PushRedirect | Tracking redirect | shortCode (unique), pushId, originalUrl, isActive, clicksCount |
| PushBatch | Батч массовой рассылки | pushId, batchNumber, totalUsers, processedUsers, successCount, failureCount, status |
Enums
| Enum | Значения | Описание |
|---|---|---|
| PushStatus | PENDING, SENDING, SENT, FAILED, CANCELLED | Жизненный цикл push |
| PushAction | SENT, DELIVERED, OPENED, CLICKED | Типы аналитических событий |
| BotRegistrationState | NEW_USER, ACTIVE, BLOCKED, REACTIVATED | Статус взаимодействия пользователя с ботом (используется в User модели) |
Relationships
6. API Endpoints
- Admin API
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-audience | Preview количества получателей по 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-уведомлений (для автокомплита, с иконками статуса и датой) |
7. Related
- Telegram Bot — отправка push через Telegram Bot API
- Cases — CasePreviewModal показывает hint про квизы при insufficient balance
- Banners — in-app рекламные баннеры (альтернативный канал)
- Feed — лента событий (in-app уведомления)