Уведомления в продакшене: чек-лист надёжной доставки
Прямой вызов email или push-сервиса из HTTP-запроса работает в демо, но ломается при таймаутах, повторах и всплесках нагрузки. Чек-лист поможет проверить схему уведомлений до релиза и не потерять сообщения.

Прямой вызов сервиса email, push или SMS из обработчика заказа почти всегда выглядит рабочим до первого сбоя: пользователь повторяет запрос, внешний провайдер отвечает через 20 секунд, а уведомление уходит дважды или не уходит вовсе. Перед продакшеном проверьте не только шаблоны, но и очередь, повторы, идемпотентность, согласие пользователя и наблюдаемость. Ниже — практический чек-лист для системы, которая должна пережить временно недоступный провайдер и не превратить один заказ в пять одинаковых сообщений.
Сначала разделите событие и доставку
Уведомление начинается не с вызова API почтового сервиса. Сначала приложение фиксирует бизнес-событие: заказ оплачен, пароль изменён, отчёт готов. Затем отдельный обработчик решает, кому, по какому каналу и с каким шаблоном отправить сообщение.
Это разделение важно по двум причинам. Сбой провайдера не должен откатывать оплату или заставлять пользователя ждать ответа HTTP. А повторная обработка события не должна создавать дубликаты, если один и тот же запрос пришёл дважды.
Для HTTP-части ориентиром здесь служит RFC 9110, HTTP Semantics. Материал не привязан к конкретной очереди, языку или библиотеке, поэтому названия функций и конфигурационных ключей для RabbitMQ, Kafka, SQS или конкретного push-провайдера намеренно не приводятся.
Надёжное уведомление — это не обещание «отправить письмо». Это управляемый процесс со статусом, сроком жизни, повтором и понятным финалом: доставлено, отклонено или окончательно не удалось.
Чек-лист перед релизом
1. Зафиксируйте контракт события
У каждого события должны быть стабильный идентификатор, тип, время возникновения, идентификатор получателя и минимальный набор данных для построения сообщения. Не передавайте в очередь случайный объект из базы целиком: схема изменится, а старое сообщение останется в очереди и перестанет обрабатываться.
- Определён уникальный event_id, который не меняется при повторной доставке.
- Указан тип события: например, payment_succeeded, а не расплывчатое send_notification.
- Есть версия схемы события или совместимое правило её изменения.
- Понятно, какие поля обязательны, а какие можно восстановить из базы.
- В событие не попадают пароли, токены, полные данные банковской карты и лишние персональные данные.
Проверка: возьмите сохранённое событие из тестовой среды и обработайте его спустя неделю. Оно должно либо успешно построить сообщение, либо завершиться понятной ошибкой совместимости.
2. Уберите доставку из пользовательского запроса
HTTP-обработчик должен записать бизнес-результат и поставить задачу на доставку, а не ждать ответа внешнего сервиса. Если очередь недоступна, система обязана явно зафиксировать этот сбой: запись в журнал, транзакционная таблица событий или другой согласованный механизм должны позволить восстановить задачу.
Проверьте границу транзакции. Ситуация, когда заказ уже закоммичен, а событие потерялось между записью в базу и публикацией в очередь, называется проблемой двойной записи. Для критичных уведомлений обычно применяют outbox-подход: событие записывают в ту же транзакцию, что и бизнес-изменение, а отдельный процесс публикует его дальше.
Проверка: принудительно остановите процесс между сохранением заказа и отправкой события. После восстановления уведомление должно найтись в хранилище непубликованных событий и уйти без ручного редактирования заказа.
3. Определите идемпотентность
Очередь или провайдер могут повторить доставку. Это нормальный сценарий, а не исключение. Обработчик должен уметь увидеть, что событие с таким идентификатором уже обработано, и не создавать второй email, push или SMS.
Не путайте идемпотентность с дедупликацией по тексту. Два разных заказа могут иметь одинаковую тему письма, а один заказ может законно породить разные уведомления. Ключом обычно служит комбинация бизнес-события, получателя, канала и назначения сообщения.
Проверка: отправьте один и тот же объект в обработчик несколько раз параллельно. В базе и у провайдера должен появиться один результат либо повтор должен быть явно помечен как пропущенный.
4. Настройте повторы без лавины
Повторяйте только временные ошибки: сетевой таймаут, временная недоступность провайдера, ответ о перегрузке. Неверный адрес, отозванное согласие или ошибка шаблона не исправятся через минуту и не должны бесконечно возвращаться в очередь.
- Разделите временные и постоянные ошибки.
- Используйте экспоненциальную задержку между попытками, а не мгновенный цикл.
- Ограничьте число попыток и общий срок жизни задачи.
- Добавьте случайное смещение задержки, чтобы тысячи задач не повторялись одновременно.
- После исчерпания лимита отправляйте задачу в отдельное хранилище для разбора, а не удаляйте молча.
Проверка: отключите провайдера на десять минут и убедитесь, что очередь не растёт с ускорением, а после восстановления задачи постепенно догоняют отставание.
5. Разведите каналы и приоритеты
Код подтверждения входа и рекламная подборка не должны конкурировать за один лимит и один рабочий поток. Разделите хотя бы критичные транзакционные сообщения и низкоприоритетные кампании. Для SMS и push проверьте отдельные ограничения провайдера, а для email — обработку отказов и жалоб.
Канал должен выбираться по назначению, а не по принципу «если email не сработал, отправим SMS». Резервный канал может стоить денег, нарушить ожидания пользователя или отправить чувствительную информацию в неподходящее место.
Проверка: создайте очередь рекламных задач, затем инициируйте сброс пароля. Сервис должен обработать критичное сообщение в установленный срок, даже если рекламная очередь заполнена.
6. Проверьте пользовательские настройки
Уведомление должно учитывать согласие на канал, отписку от категории, часовой пояс и доступность контакта. Служебные сообщения, которые нужны для исполнения операции или безопасности, могут иметь другие правила, но это должно быть явно определено продуктом и юристами, а не зашито случайным условием в обработчике.
Сохраняйте снимок решения о доставке: какой канал был выбран, какая версия шаблона применялась и почему отправка состоялась или была пропущена. Это понадобится при обращении пользователя и при расследовании инцидента.
7. Защитите содержимое и секреты
- Секреты провайдеров храните в менеджере секретов или защищённом хранилище, а не в репозитории и логах.
- В логах маскируйте адреса, номера телефонов, токены и ссылки с одноразовыми параметрами.
- Ссылки в письмах делайте короткоживущими и одноразовыми, если они открывают чувствительные данные.
- Проверяйте HTML-шаблоны на вставку пользовательского содержимого без экранирования.
- Не позволяйте получателю изменить адрес доставки через ссылку без отдельной проверки прав.
Проверка: отправьте тестовое сообщение с пользовательским именем, кавычками, HTML-фрагментом и длинным текстом. В письме или push не должно появиться исполняемого содержимого, а в логах — полного персонального значения.
Какой подход выбрать
Архитектура зависит от цены ошибки и допустимой задержки. Для прототипа достаточно фоновой задачи, но критичный платёжный сценарий требует сохранения события и контроля повторов.
| Подход | Когда подходит | Что выигрываете | Чем платите |
|---|---|---|---|
| Прямой вызов провайдера из запроса | Локальный прототип, некритичное тестовое окружение | Минимум компонентов и быстрая реализация | Таймауты пользователя, потеря события, дубликаты |
| Фоновая задача после записи в базу | Большинство обычных email и push-уведомлений | Короткий HTTP-ответ, независимая обработка | Нужно решить проблему потери события между базой и очередью |
| Outbox и отдельный воркер | Платежи, безопасность, юридически значимые сообщения | Событие сохраняется вместе с бизнес-изменением | Дополнительная таблица, воркер, очистка и мониторинг |
| Раздельные очереди по приоритетам | Большой поток и несколько классов сообщений | Критичные уведомления не блокируются рекламой | Сложнее лимиты, маршрутизация и контроль нагрузки |
Критерий выбора простой: если потеря одного сообщения требует ручного обзвона, возврата денег или расследования безопасности, прямой вызов из HTTP-запроса уже недостаточен.
Что ломается чаще всего
Дубликаты после таймаута
Приложение не получило ответ провайдера и повторило запрос, хотя провайдер уже принял первый. Нужны внешний ключ идемпотентности, если его поддерживает провайдер, и собственная запись попытки доставки. Нельзя считать отсутствие ответа доказательством неудачи.
Очередь растёт, но никто не замечает
Одной метрики «воркер жив» мало. Нужны возраст самой старой задачи, размер очереди, доля окончательных ошибок, время доставки и число повторов. Для трассировки полезно передавать идентификатор события через HTTP-вызов и обработчик; OpenTelemetry описывает общие правила для таких телеметрических данных.
Повтор отправляет уже неактуальный текст
Заказ отменили, а отложенное сообщение всё ещё сообщает об оплате. Перед финальной отправкой проверьте срок жизни события и состояние объекта, если бизнес-логика допускает такую проверку. Для юридически или финансово значимых сообщений, наоборот, может потребоваться отправить именно зафиксированную версию факта, а не текущее состояние.
Провайдер принимает запрос, но письмо не доходит
Принятие API-запроса — не то же самое, что доставка в почтовый ящик или показ push. Сохраняйте идентификатор сообщения у провайдера, обрабатывайте статусы отказа и сверяйте их с внутренним состоянием. Отдельно проверьте SPF, DKIM и DMARC для email-домена; это уже инфраструктурная часть, а не логика очереди.
Сноска. Идемпотентность — свойство операции, при котором повтор одного и того же запроса не меняет итог после первого успешного выполнения. Для уведомлений это означает, что повтор обработки события не создаёт второе сообщение.
Что измерять перед продакшеном
- долю уведомлений, доставленных с первой попытки;
- время от события до постановки в очередь и от очереди до результата;
- возраст самой старой необработанной задачи;
- число повторов на одно событие;
- долю постоянных ошибок по каналам и провайдерам;
- число дубликатов и ручных повторных отправок;
- стоимость сообщений по каналам, особенно для SMS и резервных маршрутов.
Пороговые значения задайте до релиза. Например, команда должна заранее знать, что задержка критичного push в пять минут — это инцидент, а задержка рекламного email на час — нет. Числа зависят от продукта, поэтому их нельзя заменить универсальным SLA.
Что делать дальше
Перед релизом проведите четыре отказоустойчивых теста: остановите воркер, отключите провайдера, отправьте одинаковое событие параллельно и измените схему шаблона после появления задач в очереди. Затем проверьте восстановление, дубликаты и ручной разбор окончательных ошибок.
После запуска смотрите не только на процент успешных API-вызовов, но и на фактический путь события: оно создано, поставлено в очередь, принято провайдером, доставлено или отклонено. Инвалидация старых шаблонов и управление массовыми рассылками — отдельные темы; в этом чек-листе важнее сначала доказать, что одна критичная операция не превращается в потерянное или повторное уведомление.
Тендерный специалист 44-ФЗ / 223-ФЗ: Разбор PDF-документации закупки, поиск «заточек» под поставщика, чек-лист заявки.
Спросить за 20 ₽Источники: RFC 9110: HTTP Semantics, OpenTelemetry Specification
Частые вопросы
Нужна ли очередь, если уведомлений мало?
Можно ли гарантировать доставку уведомления?
Что делать, если пользователь получил уведомление дважды?
Что будет, если провайдер недоступен несколько часов?
Как понять, что уведомление действительно доставлено?
Нужно ли повторять отправку при любом HTTP-коде ошибки?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.