Как устроена отправка: один сервис с понятным контрактом, два режима
вызова и честная обработка сбоев доставки. Потребитель не знает про
SMTP — он просит «отправь приглашение», остальное берёт на себя
транспорт.
Интерфейс сервиса
Внутри — два низкоуровневых шага: compose (шаблон + данные +
язык → готовое письмо: тема, HTML и текстовая версия) и send
(доставка готового письма по SMTP).
-
send_invite (email, ссылка, роль, язык) — письмо
приглашения.
-
send_password_reset (email, ссылка, язык) —
письмо сброса пароля.
-
send_test (email, язык) — диагностическое письмо
для кнопки «Проверить соединение».
Inline или очередь
Одно ядро compose + send, два пути вызова.
Выбор — по природе сценария, а не по удобству.
Решение. Диагностика —
inline (синхронно):
тестовое письмо отправляется прямо в запросе с таймаутом, админ сразу
видит вердикт. Всё остальное —
через очередь: приглашения (в
т.ч. массовые), сброс пароля и
алерты
ставятся задачей в SAQ, воркер шлёт фоном.
Inline
синхронно
Отправка в запросе с коротким таймаутом. Исход доступен сразу:
результат пишется в last_test_ok /
last_test_at и отражается чипом статуса на экране
настроек.
Кто: только тестовое письмо.
Очередь · SAQ
асинхронно
Задача в SAQ поверх Redis (брокер уже есть). Письма шлёт фоном
общий воркер платформы
— с ретраями, тротлингом и сохранностью при перезапуске.
Потребитель отвечает мгновенно, не дожидаясь доставки.
Кто: invite · reset · bulk · alerts.
Темпом отправки управляет воркер очереди — не потребитель. Это его
ответственность, в одном месте: положили задачи — воркер разгребает их
в заданном темпе.
-
Зачем темп. Залп писем разом упёрся бы в таймауты, а
провайдер счёл бы это рассылкой и зарезал или пометил спамом. Воркер
держит безопасный темп: ограничение скорости отправки и разумная пачка
за раз.
-
Потребитель не пакетит сам. Сколько бы писем ни
потребовалось — это просто столько же задач в очереди. Потребитель
кладёт их и сразу отвечает, не дожидаясь доставки; копить и дробить на
пачки — забота воркера, в одном месте.
-
Параметры — конфиг, не хардкод. Параллельность,
скорость отправки и размер пачки задаются настройками воркера.
-
Без дублей — идемпотентность. Задача ставится со
стабильным
job_id: повторная постановка той же отправки
(ретрай запроса, дабл-клик, повторный enqueue) отбрасывается, пока
задача стоит в очереди или выполняется. Ретрай при сбое доставки —
это та же задача с тем же id, а не второе письмо.
Ошибки доставки
SMTP может лечь, ответить таймаутом или отказать. Что происходит —
зависит от режима и от типа ошибки.
По режиму.
inline
Вердикт сразу
Сбой → типизированная ошибка наверх; экран проверки показывает
fail, в last_test_ok пишется false.
очередь
Ретраи, затем не-доставлено
Временный сбой → ретраи с экспоненциальной задержкой;
исчерпаны → задача в «не доставлено», статус виден потребителю
(для инвайта — на
модели admission).
По типу ошибки.
5xx
Постоянный отказ
Неверный адрес, ящик отклонил — ретраи бессмысленны, сразу в
«не доставлено».
4xx · сеть
Временный сбой
Сервер недоступен, таймаут, throttle провайдера — ретраим с
backoff.
-
Сброс пароля — особый случай. Сбой доставки наружу
не раскрывается: ответ на «забыли пароль?» всегда единый
(анти-энумерация —
Auth / Security),
провал уходит только в лог и аудит.