Инженерия · 21 сентября 2026 г.
Проектирование email-вебхуков с доставкой «хотя бы один раз»
Как построить устойчивых потребителей вебхуков для email-событий с помощью повторных попыток, ключей идемпотентности и проверки подписи, чтобы не пропустить ни одного события доставки.
Проблема надёжной доставки событий
Чтобы добиться доставки email-вебхуков «хотя бы один раз» (at-least-once), нужна система, в которой отправитель повторяет неудачные запросы с экспоненциальной задержкой (exponential backoff), а получатель обеспечивает идемпотентность. Сети ненадёжны, серверы падают, поэтому нельзя считать, что один ответ HTTP 200 OK гарантирует обработку события. Надёжность складывается из постоянной очереди повторов на стороне отправителя и слоя дедупликации на стороне получателя.
Когда вы подключаете email API вроде SendHQ, приложению нужно знать, было ли письмо доставлено, получило ли отказ (bounce) или было помечено как спам. Эти события асинхронны. Если ваш эндпоинт вебхуков пролежит пять минут во время пика трафика, вы можете потерять тысячи критически важных сигналов доставки. В аналитике появится пробел, а система не сможет реагировать на отказы (что критично для сохранения репутации отправителя).
Анатомия надёжного вебхука
Надёжная архитектура вебхуков держится на трёх основных опорах: проверке подписи, идемпотентной обработке и стратегии повторных попыток.
1. Проверка подписи
Никогда не доверяйте POST-запросу к эндпоинту вебхуков только на основании IP-адреса или наличия API-ключа в теле. Злоумышленники могут их подделать. Вместо этого используйте подпись HMAC (Hash-based Message Authentication Code — код аутентификации сообщения на основе хеша).
Отправитель подписывает полезную нагрузку (payload) общим секретом и передаёт подпись в заголовке (например, X-SendHQ-Signature). Получатель заново вычисляет хеш с тем же секретом и сравнивает его со значением заголовка.
const crypto = require('crypto');
function verifySignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
// Use timingSafeEqual to prevent timing attacks
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature));
}
2. Идемпотентность и дедупликация
Доставка «хотя бы один раз» означает, что отправитель будет пересылать событие, пока не получит успешный ответ. Если сервер обработал событие, но упал до отправки 200 OK, отправитель пришлёт событие ещё раз. Без идемпотентности одна доставка может быть учтена в базе данных как две.
У каждого события должен быть уникальный event_id. Для учёта обработанных событий используйте паттерн с ключом идемпотентности.
Порядок работы:
- Получите полезную нагрузку вебхука.
- Проверьте, есть ли
event_idв таблицеprocessed_events. - Если есть, сразу верните 200 OK и проигнорируйте тело.
- Если нет, обработайте событие и запишите
event_idв одной транзакции.
3. Стратегия повторных попыток
Для отправителя политика повторов обязательна. Стандартный паттерн — экспоненциальная задержка со случайным разбросом (jitter). Например: повтор через 1 минуту, 5 минут, 30 минут, 2 часа и 12 часов.
Если получатель возвращает ошибку 4xx (кроме 429), обычно это ошибка клиента (например, неверная подпись), и повтор не поможет. Ошибка 5xx или тайм-аут означают временный сбой, при котором повторы необходимы.
Пример полезной нагрузки
Так выглядит типичная полезная нагрузка события доставки, которую вы можете получить от SendHQ:
{
"event_id": "evt_12345abcde",
"event_type": "delivered",
"timestamp": "2026-09-15T10:00:00Z",
"message_id": "msg_98765xyz",
"recipient": "user@example.com",
"metadata": {
"order_id": "ord_5544"
}
}
Обработка сбоев и пограничных случаев
Проблема «медленного потребителя»
Если обработчик вебхука синхронно выполняет тяжёлые записи в базу данных или обращается к другим внешним API, эндпоинт будет отваливаться по тайм-ауту. Это запускает логику повторов у отправителя и приводит к «шторму повторов», способному положить ваш сервер.
Решение: разделите приём и обработку.
- Получите вебхук.
- Проверьте подпись.
- Поместите исходную полезную нагрузку в очередь сообщений (например, RabbitMQ, SQS или Redis).
- Сразу верните 200 OK.
- Отдельный воркер читает очередь и обновляет базу данных.
Проблема готовности агентов
Когда ИИ-агенты запускаются вебхуками, растёт риск бесконечных циклов. Если агент получает событие «delivered» и в ответ отправляет ещё одно письмо, которое порождает новое событие «delivered», вы получаете цикл.
Относитесь к отправке письма как к внешнему побочному эффекту. Агенты никогда не должны отправлять письма автоматически по вебхуку без подтверждения человеком (human-in-the-loop) или строгой проверки через конечный автомат, подтверждающей, что действие действительно нужно.
Сравнение экосистемы
При выборе провайдера надёжность часто связана с тем, как он обрабатывает эти события и сколько берёт за объём почты, который их порождает.
Для больших объёмов транзакционной почты разница в стоимости очень заметна. Согласно ценам Amazon SES, отправка a la carte стоит 0.10 USD за 1 000 писем. Для сравнения, цены Postmark начинаются от 15 USD в месяц за 10 000 писем, превышение — от 1.20 до 1.80 USD за 1 000. При объёме 50 000 писем SES a la carte обойдётся примерно в 5 USD, а тарифы Postmark — около 66 USD.
Среди других вариантов — Resend с бесплатным тарифом на 3 000 писем в месяц (не более 100 в день) и тарифом Pro за 20 USD в месяц на 50 000 писем. Mailgun стоит от 15 USD в месяц за 10 000 писем. SendGrid превратил бесплатный тариф в 60-дневный пробный период, а Essentials начинается от 19.95 USD в месяц.
Независимо от провайдера целостность ваших данных определяется надёжностью потребления этих событий.
Чек-лист внедрения для инженеров
- Проверка подписи: проверяется ли полезная нагрузка (payload) с помощью общего секрета и функции сравнения за константное время?
- Асинхронная обработка: возвращает ли эндпоинт 200 OK до выполнения тяжёлой бизнес-логики?
- Идемпотентность: есть ли уникальное ограничение для
event_id, предотвращающее повторную обработку? - Управление тайм-аутами: установлен ли тайм-аут меньше тайм-аута провайдера, чтобы избежать перекрывающихся повторных попыток?
- Мониторинг: есть ли у вас оповещения о всплеске ответов 5xx на эндпоинте вебхуков?
- Состояние DNS: корректно ли настроены ваши принимающие серверы? Используйте такие инструменты, как SendHQ Email DNS Checker, чтобы убедиться, что ваша инфраструктура доступна и настроена правильно.
- Стандарты аутентификации: настроили ли вы DKIM, SPF и DMARC, чтобы исходящие письма принимались и вам приходилось обрабатывать меньше вебхуков «bounce»?
Сводка компромиссов
Подход | Плюсы | Минусы
Синхронная обработка | Простота реализации, немедленная согласованность | Высокий риск тайм-аутов, склонность к штормам повторов
Обработка через очередь | Хорошо масштабируется, устойчива к пикам | Сложнее инфраструктура, согласованность в конечном счёте
Простое логирование | Низкие накладные расходы | Пропущенные события не восстановить без ручного разбора логов
Таблица идемпотентности | Гарантированная целостность данных | Дополнительная запись в базу на каждое событие
Выводы
Надёжность email-вебхуков — это не предотвращение сбоев, а проектирование с расчётом на них. Исходя из того, что сеть будет падать, а события будут доставляться больше одного раза, вы строите по-настоящему устойчивую систему. Настраиваете ли вы записи SPF для небольшого проекта или масштабируете огромную транзакционную систему, паттерны проверки подписи и идемпотентности остаются золотым стандартом.
Стройте свою почтовую инфраструктуру с SendHQ.