Перейти к основному содержимому

Исходящая почта на бэкенде

Разработчику

Play ITЗагрузка интерактивного демо…

Регистрация, сброс пароля, счета, уведомления — email остаётся надёжным каналом, когда push и мессенджеры недоступны. Бэкенд не "рисует письмо в браузере", а формирует сообщение и передаёт его почтовой инфраструктуре.

Для продукта почтовый контур одновременно технический и репутационный. Пользователь оценивает сервис по тому, дошло ли письмо вовремя, открылась ли ссылка восстановления, совпал ли контент с ожидаемым действием.


Когда email становится критичным

В большинстве продуктов почта отвечает за операции с высокой ценой ошибки:

  • подтверждение владения аккаунтом;
  • восстановление доступа;
  • финансовые документы и юридические уведомления;
  • служебные уведомления для команды и клиентов.

Поэтому для почтового контура важны наблюдаемость, повторяемость и контроль доставки.


Цепочка доставки

На практике приложение редко говорит напрямую с MX Gmail: чаще используют транзакционный SMTP (облачный релей) с API или SMTP-auth.

Эта модель отделяет бизнес-логику приложения от сложностей доставки — репутации домена, повторных попыток, обработки bounce и политики почтовых провайдеров.


Структура письма (MIME)

Письмо — текст с заголовками (RFC 5322 + MIME):

ЗаголовокНазначение
FromОтправитель (должен быть разрешён SPF/DKIM домена)
To / Cc / BccПолучатели
SubjectТема (кодировка UTF-8 → base64 в заголовке)
Message-IDУникальный идентификатор (полезен для поддержки)
Content-Typetext/plain, text/html, multipart/alternative

Multipart — одно письмо с несколькими частями: plain + HTML + вложения. Клиент выбирает подходящую часть.

Такая структура повышает совместимость. Клиенты с ограниченным HTML-рендерингом читают текстовую часть, а современные интерфейсы показывают HTML-версию без потери смысла сообщения.

Упрощённый каркас multipart/alternative (как формирует библиотека или MTA):

From: Shop <noreply@shop.example>
To: user@example.com
Subject: =?UTF-8?B?0J7Qv9C40YHQutC+0L3RgdC60Lg=?=
MIME-Version: 1.0
Content-Type: multipart/alternative; boundary="boundary42"

--boundary42
Content-Type: text/plain; charset=utf-8

Сброс пароля: https://shop.example/reset?token=…

--boundary42
Content-Type: text/html; charset=utf-8

<p>Сброс пароля: <a href="https://shop.example/reset?token=…">перейти</a></p>

--boundary42--

На DNS домена отправителя настраивают SPF (кто может слать от имени домена), DKIM (подпись письма) и DMARC (политика при несовпадении) — без этого транзакционные письма часто попадают в спам.

Кодировки тела:

  • base64 — бинарные вложения, нелатиница в старых клиентах;
  • quoted-printable — текст с редкими не-ASCII символами.

Что реализует бэкенд

Почтовая подсистема на бэкенде устроена как отдельный процесс с очередью и подтверждением статусов. Такой подход сохраняет отзывчивость HTTP-слоя и делает отправку управляемой под нагрузкой.

  1. Шаблоны — отделение текста от кода (HTML + подстановка переменных).
  2. Очередь — отправка асинхронно: HTTP-запрос не ждёт SMTP 30 секунд.
# После успешной регистрации — не блокировать ответ клиенту
def register_user(email: str, password: str) -> User:
user = create_user(email, password)
queue.enqueue("send_email", template="welcome", user_id=user.id)
return user
  1. Идемпотентность — повтор задачи не должен слать десятое "сбрось пароль"; ключ: userId + template + window.
  2. Логирование — статус queued / sent / bounced, без паролей в логах.
  3. Unsubscribe — для маркетинга обязателен; для транзакционных писем — другие правила.

Типичные ошибки:

  • синхронная отправка в обработчике HTTP → таймауты;
  • нет retry с экспоненциальной паузой при 4xx/5xx релея;
  • From-домен без SPF, DKIM, DMARC → письма в спаме.

Статусы доставки и реакция системы

СтатусСмыслДействие на бэкенде
queuedПисьмо принято в очередьсохранить message_id и срок SLA
sentРелей принял письмозакрыть задачу отправки
deferredВременная проблема доставкиповтор по backoff
bouncedПостоянная ошибка адреса или политикипометить адрес, остановить дальнейшие отправки
complaintПолучатель пожаловался на спаммгновенно исключить адрес из маркетинговых рассылок

Практика: храните события доставки отдельно от бизнес-таблиц, чтобы поддержка могла быстро ответить "что именно случилось с письмом".

Эта детализация превращает отправку писем из "чёрного ящика" в наблюдаемую систему, где каждое сообщение имеет историю и понятный операционный статус.


Шаблоны и контент без хаоса

Рекомендуемая структура шаблонизации:

  1. Базовый layout (шапка, подвал, юридический блок).
  2. Контентные шаблоны по типам писем.
  3. Версионирование шаблонов в репозитории.
  4. Предпросмотр HTML и plain-варианта в CI.

Такой подход помогает безопасно менять тексты и снижает риск поломки важной рассылки.


Тестирование без риска

СредаПодход
ЛокальноПоднять песочницу SMTP: все письма перехватываются, в веб-UI видно HTML
CIMock SMTP или заглушка API провайдера
StagingОтдельный домен, whitelist получателей

Никогда не гоняйте автотесты на реальные ящики пользователей.

Безопасная тестовая среда защищает репутацию домена отправителя и снимает риски случайной рассылки. Для энциклопедического подхода это ключевой принцип: проверка механики доставки должна быть полной, а воздействие на внешних получателей — нулевым.

Проверка порта с сервера: openssl s_client -connect smtp.example:587 -starttls smtp (или аналог в терминале).


Безопасность

  • Не вставляйте в HTML неэкранированные пользовательские данные (XSS в почтовике).
  • Ссылки сброса пароля — одноразовые токены с коротким TTL.
  • Rate limit на "отправить письмо" — защита от спама и перебора email.

Подробнее об общей модели: безопасность приложений.


Мини-checklist перед запуском в прод

  • Настроены SPF, DKIM, DMARC и проверены внешним валидатором.
  • Все письма отправляются асинхронно через очередь.
  • Реализованы retry/backoff и дедупликация по ключу идемпотентности.
  • Логи и события доставки связаны через message_id/request_id.
  • Есть отдельные домены для staging и production.

Связанные темы