Многоканальная система уведомлений: шаблоны, повторы, предпочтения
Спроектируйте многоканальную систему уведомлений для email, SMS и Telegram с версионированными шаблонами, отслеживанием статусов доставки, логикой повторов и уважением пользовательских предпочтений.

Что решает единая система уведомлений
Когда электронная почта, SMS и Telegram реализованы как отдельные фичи, разрывы проявляются быстро. «Оповещение» в итоге получается с разными формулировками, разным временем и разными правилами — кто и что получает. Службы поддержки вынуждены разбираться с тремя версиями правды: у почтового провайдера, у SMS-шлюза и в логах бота.
Многоканальная система уведомлений решает это, рассматривая уведомления как один продукт, а не три отдельные интеграции. Происходит одно событие (сброс пароля, оплата счета, падение сервера), а система решает, как доставить его по каналам на основе шаблонов, предпочтений пользователя и правил доставки. Сообщение всё ещё может быть отформатировано по-разному для каждого канала, но остаётся единым по смыслу, данным и учёту.
Большинству команд нужна одна и та же основа, независимо от того, с какого канала они начали: версии шаблонов с переменными, отслеживание статуса доставки («отправлено, доставлено, не удалось, почему»), разумные повторы и резервные сценарии, пользовательские предпочтения с согласием и тишиной, и аудиторский след, чтобы поддержка могла увидеть, что произошло, не догадываясь.
Успех выглядит скучно — и в хорошем смысле. Сообщения предсказуемы: нужный человек получает нужный контент в нужное время через разрешённые каналы. Когда что-то идёт не так, устранение неполадок становится простым, потому что каждая попытка записана с понятным статусом и кодом причины.
Оповещение «новый вход» — хороший пример. Вы создаёте его один раз, наполняете одинаковыми данными о пользователе, устройстве и местоположении, а затем доставляете как email для деталей, SMS для срочности и Telegram для быстрой подтверждающей заметки. Если SMS-провайдер завершается по таймауту, система планирует повторную попытку по расписанию, логирует таймаут и может переключиться на другой канал вместо того, чтобы просто потерять оповещение.
Основные концепции и простая модель данных
Многоканальная система уведомлений остаётся управляемой, когда вы отделяете «почему мы уведомляем» от «как мы доставляем». Это значит небольшой набор общих объектов и только канал-специфичные поля там, где они действительно отличаются.
Начните с события. Событие — это именованный триггер, например order_shipped или password_reset. Держите имена последовательными: строчные буквы, подчёркивания и прошедшее время, когда это уместно. Рассматривайте событие как стабильный контракт, от которого зависят шаблоны и правила предпочтений.
Из одного события создаётся запись уведомления. Это намерение, ориентированное на пользователя: для кого, что произошло и какие данные нужны для рендеринга (номер заказа, дата доставки, код сброса). Храните здесь общие поля: user_id, event_name, locale, priority и scheduled_at.
Далее разделяйте на сообщения по каналам. Уведомление может породить от 0 до 3 сообщений (email, SMS, Telegram). В сообщениях находятся канал-специфичные поля: адрес назначения (email, телефон, Telegram chat_id), template_id и отрендерённый контент (subject/body для email, короткий текст для SMS).
Наконец, отслеживайте каждую отправку как попытку доставки. Попытки содержат provider request_id, временные метки, коды ответов и нормализованный статус. Именно это вы проверяете, когда пользователь говорит: «Я не получил(а) это.»
Простая модель часто укладывается в четыре таблицы или коллекции:
- Event (каталог допустимых имён событий и настроек по умолчанию)
- Notification (одна запись на пользовательское намерение)
- Message (одна запись на канал)
- DeliveryAttempt (одна запись на попытку)
Планируйте идемпотентность заранее. Дайте каждому уведомлению детерминированный ключ, например (event_name, user_id, external_ref), чтобы повторы из внешних систем не создавали дубликатов. Если этап рабочего процесса перезапускается, ключ идемпотентности не даст пользователю получить два SMS.
Храните в долгосрочной перспективе только то, что нужно для аудита (событие, уведомление, финальный статус, временные метки). Краткосрочные очереди доставки и сырые полезные нагрузки провайдера держите лишь столько, сколько нужно для работы и отладки.
Практический сквозной поток (шаг за шагом)
Многоканальная система уведомлений работает лучше, когда «решение, что отправлять» отделено от «процесса отправки». Это делает приложение быстрым и упрощает обработку сбоев.
Практический поток выглядит так:
-
Производитель событий создаёт запрос на уведомление. Это может быть «сброс пароля», «оплата счета» или «обновление тикета». Запрос включает user ID, тип сообщения и контекстные данные (номер заказа, сумма, имя сотрудника поддержки). Сохраните запрос сразу, чтобы был аудиторский след.
-
Роутер загружает правила пользователя и сообщения. Он проверяет предпочтения пользователя (разрешённые каналы, подписки, тихие часы) и правила сообщений (например: для уведомлений безопасности сначала попробовать email). Роутер формирует план каналов, например: Telegram, затем SMS, затем email.
-
Система помещает задачи отправки в очередь по каналам. Каждая задача содержит ключ шаблона, канал и переменные. Задачи идут в очередь, чтобы пользовательское действие не блокировалось отправкой.
-
Воркеры каналов доставляют через провайдеров. Email отправляется через SMTP или API почты, SMS — через SMS-шлюз, Telegram — через вашего бота. Воркеры должны быть идемпотентными, чтобы повторный запуск той же задачи не вызывал дубликатов.
-
Обновления статусов стекаются в одно место. Воркеры фиксируют queued, sent, failed и, когда доступно, delivered. Если провайдер подтверждает только «accepted», запишите и это и трактуйте отдельно от delivered.
-
Резервные сценарии и повторы работают из того же состояния. Если Telegram падает, роутер (или воркер повторов) может запланировать SMS следующим, не потеряв контекст.
Пример: пользователь меняет пароль. Бэкенд генерирует один запрос с user и IP. Роутер видит, что пользователь предпочитает Telegram, но тёмные часы блокируют Telegram ночью, поэтому он отправляет email сейчас и запланирует Telegram утром, отслеживая оба варианта в одной записи уведомления.
Если вы реализуете это в AppMaster, храните запросы, задачи и таблицы статусов в Data Designer и выражайте логику маршрутизации и повторов в Business Process Editor, выполняя отправку асинхронно, чтобы UI оставался отзывчивым.
Структура шаблонов, работающая для всех каналов
Хорошая система шаблонов исходит из одной идеи: вы уведомляете о событии, а не «отправляете email» или «отправляете SMS». Создавайте один шаблон на событие (сброс пароля, заказ отправлен, оплата не прошла), а затем храните варианты по каналам под тем же событием.
Сохраняйте одинаковые имена переменных во всех вариантах канала. Если в email используется first_name и order_id, SMS и Telegram должны использовать те же имена. Это предотвращает тонкие ошибки, когда один канал рендерится нормально, а в другом появляются пустые места.
Простая и повторяемая форма шаблона
Для каждого события определяйте небольшой набор полей по каналу:
- Email: subject, preheader (по желанию), HTML-тело, текстовый fallback
- SMS: простой текст в теле
- Telegram: простой текст в теле, плюс опциональные кнопки или краткие метаданные
Единственное, что меняется между каналами — форматирование, а не смысл.
SMS требует особых правил из‑за краткости. Решите заранее, что делать, если контент слишком длинный, и сделайте это единообразно: задайте лимит символов, выберите правило усечения (отрезать и добавить ... или сначала убрать опциональные строки), избегайте длинных URL и лишних знаков препинания, и ставьте ключевое действие в начало (код, дедлайн, следующий шаг).
Локализация без копирования бизнес-логики
Рассматривайте язык как параметр, а не отдельный рабочий поток. Храните переводы по событию и каналу, затем рендерьте с теми же переменными. Логика «Заказ отправлен» остаётся прежней, пока тема и тело меняются в зависимости от локали.
Режим предпросмотра окупается: рендерьте шаблоны с примерными данными (включая крайние случаи, например длинное имя), чтобы поддержка могла проверить варианты email, SMS и Telegram до публикации.
Надёжный статус доставки и отладка
Уведомление полезно лишь тогда, когда можно позже ответить на вопрос: что с ним случилось? Хорошая система отделяет намерение сообщения от каждой попытки его доставки.
Начните с небольшого общего набора статусов, которые значат одно и то же для email, SMS и Telegram:
- queued: принято вашей системой, ждёт воркера
- sending: попытка доставки в процессе
- sent: успешно передано провайдеру API
- failed: попытка завершилась ошибкой, требующей действий
- delivered: есть доказательство доставки пользователю (когда возможно)
Держите эти статусы на основной записи сообщения, но записывайте каждую попытку в историю. Именно история облегчает отладку: попытка №1 упала (таймаут), попытка №2 удалась, или SMS прошёл, а email всё время отскакивает.
Что хранить для каждой попытки
Нормализуйте ответы провайдеров, чтобы можно было искать и группировать проблемы, даже когда провайдеры используют разные формулировки.
- provider_name и provider_message_id
- response_code (нормализованный код, например TIMEOUT, INVALID_NUMBER, BOUNCED)
- raw_provider_code и raw_error_text (для обращения в поддержку)
- started_at, finished_at, duration_ms
- channel (email, sms, telegram) и destination (замаскировано)
Планируйте частичную успешность. Одно уведомление может породить три сообщения по каналам, которые разделяют один parent_id и бизнес-контекст (order_id, ticket_id, alert_type). Если SMS отправился, а email упал, вы всё ещё хотите полную картину в одном месте, а не три несвязанных инцидента.
Что значит «доставлено» на самом деле
«Sent» — это не «delivered». Для Telegram вы можете знать лишь, что API принял сообщение. Для SMS и email доставка часто зависит от вебхуков или обратных вызовов провайдера, и не все провайдеры одинаково надёжны.
Определите delivered по каналу заранее. Используйте подтверждённую вебхуком доставку, когда она доступна; иначе считайте delivered неизвестным и продолжайте отмечать sent. Так ваши отчёты честны, а ответы поддержки последовательны.
Повторы, резервные сценарии и когда прекращать попытки
Именно на повторах системы часто совершают ошибки. Повторять слишком быстро — вы создаёте штормы. Повторять вечно — дубли и головную боль для поддержки. Цель проста: пробовать снова, когда есть реальный шанс на успех, и останавливаться, когда его нет.
Начните с классификации ошибок. Таймаут у почтового провайдера, 502 у SMS-шлюза или временная ошибка Telegram — обычно подлежат повторам. Неправильный email, номер телефона, не прошедший валидацию, или блокировка бота в Telegram — не подлежат. Относиться к ним одинаково — тратить деньги и захламлять логи.
Практический план повторов ограничен и использует backoff:
- Попытка 1: отправить сейчас
- Попытка 2: через 30 секунд
- Попытка 3: через 2 минуты
- Попытка 4: через 10 минут
- Остановить после максимального возраста (например, 30–60 минут для оповещений)
Остановка должна быть явно представлена в модели данных. Отмечайте сообщение как dead-letter (или failed-permanently), когда оно превышает лимиты повторов. Храните последний код ошибки и короткое сообщение, чтобы поддержка могла действовать без догадок.
Предотвращайте повторные отправки после успеха с помощью идемпотентности. Создавайте ключ идемпотентности для логического сообщения (часто notification_id + user_id + channel). Если провайдер ответил с задержкой и вы снова пытаетесь, вторая попытка должна распознаваться как дубликат и пропускаться.
Резервные сценарии должны быть продуманными, а не панической автоматикой. Определяйте правила эскалации по степени важности и времени. Пример: сброс пароля не должен автоматически падать на другой канал (риск приватности), но оповещение о продакшен-инциденте может попробовать SMS после двух неудачных попыток в Telegram, а затем email через 10 минут.
Предпочтения пользователей, согласие и тихие часы
Система уведомлений кажется «умной», когда уважает людей. Проще всего — дать пользователям выбирать каналы для каждого типа уведомлений. Многие команды делят типы на корзины: безопасность, аккаунт, продукт и маркетинг, потому что правила и юридические требования различаются.
Начните с модели предпочтений, которая работает даже если канал недоступен. У пользователя может быть email, но нет телефона, или он ещё не подключил Telegram. Ваша система должна рассматривать это как норму, а не ошибку.
Чаще всего нужен компактный набор полей: тип уведомления (security, marketing, billing), разрешённые каналы по типу (email, SMS, Telegram), согласие по каналу (дата/время, источник и доказательство, если нужно), причина отписки по каналу (выбор пользователя, отскок email, ответ "STOP"), и правило тихих часов (начало/конец плюс часовой пояс пользователя).
Тихие часы — частая причина ошибок. Храните часовой пояс пользователя (не только смещение), чтобы переходы на летнее/зимнее время не удивляли. Когда сообщение попадает в тихие часы, не помечайте его как ошибку. Отложите его и выберите следующее разрешённое время отправки.
По умолчанию важно учитывать критичность. Частый подход: уведомления безопасности игнорируют тихие часы (но всё ещё уважают твёрдые отказы, если это требуется), а неважные обновления следуют тихим часам и выбору каналов.
Пример: сброс пароля должен уходить немедленно в самый быстрый разрешённый канал. Недельная сводка подождёт утра и пропустит SMS, если пользователь явно его не включал.
Операции: мониторинг, логи и рабочие процессы поддержки
Когда уведомления задействуют email, SMS и Telegram, команде поддержки нужны быстрые ответы: отправляли ли мы, дошло ли это и что пошло не так? Многоканальная система уведомлений должна ощущаться как единый инструмент расследования, даже если за кулисами работают разные провайдеры.
Начните с простого админ‑вида, которым может пользоваться любой сотрудник. Сделайте поиск по пользователю, типу события, статусу и временному окну, показывайте последние попытки первыми. Каждая строка должна раскрывать канал, ответ провайдера и следующую запланированную операцию (повтор, резервный канал или остановка).
Метрики, которые ловят проблемы рано
Аварии редко проявляются одним чистым ошибочным кодом. Отслеживайте небольшой набор показателей и просматривайте их регулярно:
- Скорость отправки по каналам (сообщений в минуту)
- Процент ошибок по провайдерам и кодам ошибок
- Частота повторов (сколько сообщений потребовало вторую попытку)
- Время до доставки (queued → delivered, p50 и p95)
- Процент отбрасываний (остановлено из-за настроек пользователя, согласия или лимитов повторов)
Коррелируйте всё. Генерируйте correlation ID при возникновении события (например, "invoice overdue") и прокатывайте его через шаблоны, очереди, вызовы провайдеров и обновления статусов. В логах этот ID станет ниткой, по которой следовать, когда одно событие расходится по каналам.
Воспроизведение для поддержки без сюрпризов
Воспроизведения должны быть безопасными, чтобы вы не заспамили людей и не начислили лишние расходы. Безопасный поток обычно выглядит так: повторно отправляйте только конкретное message ID (не всю пачку); показывайте точную версию шаблона и отрендерённый контент перед отправкой; требуйте причину и сохраняйте, кто инициировал повтор; блокируйте повтор, если сообщение уже доставлено, если только не выполнено явное принудительное действие; и применяйте лимиты скорости по пользователю и по каналу.
Базовые требования по безопасности и приватности
Многоканальная система уведомлений работает с персональными данными (email, номера телефонов, chat ID) и часто сопровождает чувствительные моменты (входы, платежи, запросы в поддержку). Предполагайте, что тело сообщения и каждая строка лога могут быть видны позже, и проектируйте систему так, чтобы ограничивать хранение и доступ.
По возможности держите чувствительные данные вне шаблонов. Шаблон должен быть многократно используемым и «скучным»: «Ваш код {{code}}» — нормально, а вот встраивание полных данных аккаунта или длинных токенов — нет. Если сообщение обязательно должно содержать одноразовый код или ссылку для сброса, храните только то, что нужно для проверки (например, хэш и срок действия), а не открытое значение.
При хранении и логировании событий уведомлений маскируйте данные. Агенту поддержки обычно нужно знать, что код был отправлен, а не сам код. То же относится к номерам телефонов и email: храните полное значение для доставки, но показывайте замаскированную версию в большинстве интерфейсов.
Минимальные контролы для предотвращения инцидентов
- Ролевой доступ: только узкий круг ролей может видеть тела сообщений и полные данные получателя.
- Отдельный доступ для отладки и для поддержки, чтобы отладка не превращалась в утечку приватных данных.
- Защитите webhook‑эндпойнты: используйте подписанные колбэки или shared secrets, проверяйте метки времени и отклоняйте неизвестные источники.
- Шифруйте чувствительные поля в покое и используйте TLS в транспорте.
- Определите правила хранения: детальные логи храните коротко, затем оставляйте только агрегаты или хэшированные идентификаторы.
Практический пример: если SMS для сброса пароля не прошёл и вы переключились на Telegram, сохраните попытку, статус провайдера и замаскированного получателя, но избегайте хранения самой ссылки для сброса в базе или логах.
Пример сценария: одно оповещение, три канала, реальные исходы
Клиент — Майя — включила два типа уведомлений: Сброс пароля и Новый вход. Она предпочитает Telegram первым, затем email. SMS она разрешила только как запасной канал для сброса пароля.
Однажды вечером Майя запрашивает сброс пароля. Система создаёт одну запись уведомления с устойчивым ID и разворачивает её в попытки по каналам в соответствии с её текущими предпочтениями.
То, что видит Майя, просто: через секунды приходит сообщение в Telegram с коротким кодом и временем истечения, больше ничего не приходит, потому что Telegram сработал и резерв не понадобился.
То, что фиксирует система, более подробно:
- Notification: type=PASSWORD_RESET, user_id=Maya, template_version=v4
- Attempt #1: channel=TELEGRAM, status=SENT затем DELIVERED
- Email и SMS попытки не создавались (политика: остановиться после первого успеха)
На следующей неделе сработало оповещение Новый вход с нового устройства. Предпочтение Майи для входов — только Telegram. Система отправляет Telegram, но провайдер возвращает временную ошибку. Система делает два повтора с backoff, затем помечает попытку как FAILED и останавливается (для этого типа оповещения резерв не разрешён).
Реальная неудача: Майя запрашивает ещё один сброс пароля в путешествии. Telegram отправлен, но для сброса пароля настроен SMS‑фоллбэк, если Telegram не доставит в течение 60 секунд. SMS‑провайдер упирается в таймаут. Система фиксирует таймаут, выполняет ещё одну попытку, и вторая попытка проходит успешно. Майя получает SMS‑код через минуту.
Когда Майя обращается в поддержку, агент ищет по пользователю и временному окну и сразу видит историю попыток: временные метки, коды ответов провайдера, число повторов и финальный исход.
Быстрый чек‑лист, типичные ошибки и дальнейшие шаги
Многоканальная система уведомлений проще в эксплуатации, когда вы можете быстро ответить на два вопроса: «Что именно мы пытались отправить?» и «Что с этим дальше произошло?». Используйте этот чек‑лист, прежде чем добавлять новые каналы или события.
Быстрый чек‑лист
- Чёткие имена событий и ответственность (например,
invoice.overdue, отвечают за billing) - Переменные шаблонов определены один раз (обязательные vs опциональные, значения по умолчанию, правила форматирования)
- Статусы согласованы заранее (created, queued, sent, delivered, failed, suppressed) и что каждый из них значит
- Лимиты повторов и backoff (макс. попыток, интервалы, правило остановки)
- Правила хранения (как долго держать тела сообщений, ответы провайдеров и историю статусов)
Если вы сделаете только одно — запишите разницу между sent и delivered простыми словами. Sent — что ваша система сделала. Delivered — что сообщил провайдер (и это может быть с задержкой или отсутствовать). Их смешивание запутает поддержку и бизнес‑стейкхолдеров.
Частые ошибки, которых стоит избегать
- Приравнивать sent к успеху и получать завышенные показатели доставки
- Позволять шаблонам по каналам расходиться до тех пор, пока email, SMS и Telegram не начнут противоречить друг другу
- Делать повторы без идемпотентности, что приводит к дубликатам при таймаутах, после которых провайдер всё же принимает сообщение
- Повторять бесконечно, превращая временную проблему в шумную аварию
- Хранить слишком много персональных данных в логах и статусах «на всякий случай»
Начните с одного события и одного основного канала, затем добавляйте второй канал как резерв (не как параллельную рассылку). Когда поток стабилен, расширяйте события по одному, удерживая шаблоны и переменные общими, чтобы сообщения оставались согласованными.
Если вы хотите построить это без ручного написания каждого фрагмента кода, AppMaster (appmaster.io) — практичный вариант: моделируйте события, шаблоны и попытки доставки в Data Designer, реализуйте маршрутизацию и повторы в Business Process Editor и подключайте email, SMS и Telegram как интеграции, сохраняя отслеживание статусов в едином месте.


