руководство · smtp python
Как продуктовой команде безопасно реализовать SMTP на Python?
Реализуйте SMTP на Python в авторизованном фоновом воркере, а не прямо в обработчике веб-запроса. Собирайте письмо с помощью EmailMessage, используйте SMTP_SSL для неявного TLS или явно повышайте соединение через STARTTLS, если этого требует текущий контракт провайдера, аутентифицируйтесь с помощью серверного секрета и вызывайте send_message с ограниченными тайм-аутами. Сохраняйте задачу до подключения, фиксируйте данные об отказах по каждому получателю, сверяйте неоднозначные обрывы соединения и отличайте приём по SMTP от последующей доставки и попадания во «Входящие».
Авторизуйте и сохраните отправку до SMTP
Начните с легитимного события приложения: чека, оповещения безопасности, запрошенного подтверждения или уведомления об аккаунте. Аутентифицируйте вызывающую сторону и авторизуйте тенанта, класс письма, видимый идентификатор From, получателя и ревизию шаблона. Запишите надёжную задачу исходящей отправки со стабильным ключом бизнес-события до открытия SMTP-соединения. Этот ключ должен не давать двум воркерам независимо создать одно и то же логическое письмо. Ввод из браузера не должен определять SMTP-хост, порт, имя пользователя, отправителя в конверте, произвольных получателей, заголовки или политику TLS. Храните эти значения в проверенной серверной конфигурации. Воркер очереди должен захватывать одну задачу, повторно проверять стоп-лист и авторизацию в момент отправки, записывать каждую попытку и освобождать или завершать задачу через явные состояния. SMTP-библиотека Python передаёт подготовленное письмо; авторизацию тенантов, согласие, идемпотентность и политику стоп-листа она не обеспечивает.
Собирайте письмо с помощью EmailMessage
Используйте email.message.EmailMessage вместо склеивания исходных заголовков и тела. Задайте From, To, Subject, Date и сгенерированный Message-ID согласно утверждённой в приложении модели, затем используйте set_content для текста и add_alternative для HTML, если он нужен. Проверяйте объекты адресов, ограничивайте число получателей и вложений, отклоняйте внедрение переводов строк в значениях и экранируйте данные шаблона для контекста вывода. Генерируйте текстовую и HTML-версию из одной неизменяемой ревизии шаблона. Не допускайте секретов и лишних персональных данных в темах, пользовательских заголовках, именах файлов, диагностических полях и логах. Осознанно разделяйте видимый заголовок From и отправителя в SMTP-конверте: аутентификация и обработка отказов могут зависеть от разных идентификаторов. Для аудита храните ревизию содержимого или хеш без персональных данных, а не полные тексты писем без определённой необходимости.
Явно выбирайте неявный TLS или STARTTLS
Python документирует SMTP_SSL для соединений, зашифрованных с самого начала, и SMTP.starttls для повышения защиты уже установленного соединения. Следуйте актуальным имени хоста, порту, сертификату и контракту отправки вашего провайдера, а не угадывайте по общему списку портов. Создайте проверяемый SSL-контекст по умолчанию и не отключайте проверку сертификата или имени хоста. Для STARTTLS подключитесь, при необходимости отправьте EHLO, вызовите starttls с контекстом и снова отправьте EHLO, потому что объявленные расширения могут измениться после повышения. Никогда не передавайте учётные данные или содержимое писем клиентов по незашифрованному соединению. RFC 8314 рекомендует отправку, защищённую TLS, и объявляет доступ открытым текстом устаревшим. Считайте ошибку сертификата, несовпадение имени хоста, отсутствие обязательного STARTTLS или неожиданные изменения возможностей жёсткими сбоями, требующими расследования, а не поводом для молчаливого отката.
Держите учётные данные SMTP в узкой границе секретов
Загружайте имя пользователя и пароль или токен во время работы из управляемого серверного хранилища секретов. Не помещайте учётные данные в исходный код, клиентские бандлы, дампы окружения, URL, трассировки исключений, аналитику, ноутбуки, скриншоты, промпты или закоммиченные фикстуры. Ограничивайте область действия каждых учётных данных до минимально необходимого окружения и нагрузки, которые поддерживает провайдер, и разделяйте разработку и продакшен. Аутентифицируйтесь только после установки требуемого состояния TLS. Отработайте ротацию на контролируемых получателях: выпустите замену через утверждённый процесс администрирования, обновите воркер, подтвердите аутентификацию и полный жизненный цикл событий, затем отзовите старое значение. Повторяющиеся ошибки аутентификации должны приостанавливать затронутый маршрут, а не запускать цикл быстрых повторов. Метод login в Python согласовывает механизм из объявленных сервером, но фактический механизм провайдера, политика аккаунта, права токена и поведение при ротации требуют актуальной проверки.
Используйте ограниченную функцию отправки на Python
Держите адаптер провайдера небольшим и возвращайте структурированные данные в конечный автомат задачи. Типичный сценарий создаёт SSL-контекст, открывает SMTP_SSL(host, port, timeout=10) as smtp для неявного TLS, вызывает smtp.login(username, secret), затем smtp.send_message(message, from_addr=envelope_from, to_addrs=recipients). Для провайдера, требующего явного повышения, используйте SMTP с тайм-аутом, ehlo, starttls(context=context), ehlo и затем login. Не выдавайте примеры имён хостов или портов за универсальные значения по умолчанию. Передавайте нормализованный список получателей, а не полагайтесь на разбор недоверенных заголовков. Сохраняйте класс исключения, код SMTP-ответа и ограниченный диагностический текст, когда они доступны, но скрывайте адреса, учётные данные и содержимое писем. Измеряйте этапы подключения, TLS, аутентификации, конверта, данных и завершения отдельно, чтобы операционные сбои оставались диагностируемыми.
Точно интерпретируйте результаты send_message по получателям
В документации Python сказано, что sendmail и send_message завершаются нормально, если письмо принято хотя бы для одного получателя, и возвращают словарь отклонённых получателей; пустой словарь означает, что на этом этапе не был отклонён ни один получатель. Сохраняйте этот результат по каждому получателю, а не помечайте всю задачу как доставленную. Если отклонены все получатели, библиотека выбрасывает исключение SMTPRecipientsRefused. Другие исключения различают отказ отправителю, отказ на этапе DATA, ошибки аутентификации, соединения, протокола и связанные с ними. Сопоставляйте точные данные с состояниями приложения: принято сервером отправки, отклонено постоянно, отклонено временно или неизвестно. Нормальное завершение доказывает лишь результат отправки по SMTP в этих рамках. Оно не подтверждает приём сервером назначения, итоговую папку в ящике, прочтение или вовлечённость. Последующие уведомления о статусе доставки или события провайдера нужно сопоставлять отдельно.
Повторяйте, только когда риск дублей под контролем
Классифицируйте сбои, прежде чем планировать новую попытку. Постоянные ошибки адреса, отправителя, аутентификации, политики или содержимого обычно требуют исправления или добавления в стоп-лист, а не автоматического повтора. Временные ответы 4xx можно повторять с экспоненциальной задержкой, джиттером (jitter), потолком попыток, сроком истечения и бюджетом на каждое направление. Сброс соединения или тайм-аут после передачи данных письма могут быть неоднозначными: сервер мог принять письмо, а клиент не получил итоговый ответ. Оставьте такую попытку в неизвестном состоянии, проверьте активность у провайдера или последующие события через корреляцию без персональных данных и избегайте немедленной слепой повторной отправки. В SMTP нет универсального ключа идемпотентности приложения. Надёжный ключ бизнес-события предотвращает параллельные попытки внутри приложения, но не может заставить удалённый SMTP-сервер дедуплицировать две принятые отправки. Эскалируйте повторяющиеся неоднозначные результаты и сохраняйте точные данные, на основании которых принималось решение.
Обрабатывайте частичный приём получателей и стоп-лист
Если у письма несколько получателей, SMTP может принять одних и отклонить других. Сохраняйте ответ для каждого получателя и переводите в следующее состояние только принятую часть. Не отправляйте заново весь исходный список только потому, что один адрес получил временный отказ. Применяйте стоп-листы по жёстким отказам, жалобам, отпискам, юридическим требованиям, тенантам и решениям администраторов перед каждой попыткой, включая повторы. Разделяйте классы писем только по явной задокументированной политике: пометка письма как транзакционного не отменяет безопасности получателей и ограничений провайдера. Для чувствительных сценариев предпочитайте задачи с одним получателем, когда приватность и индивидуальное состояние оправдывают затраты. Не раскрывайте списки получателей через To или Cc и никогда не используйте поведение Bcc как замену авторизации. Ограничивайте и маскируйте диагностический текст: SMTP-ответы могут содержать адреса получателей или специфичные для получателя подробности.
Тестируйте сценарии сбоев на контролируемых системах
Протестируйте сборку письма, Unicode, текстовую и HTML-альтернативы, вложения, отклонение заголовков, нормализацию получателей, проверку TLS, отсутствие STARTTLS, неверные учётные данные, отказ отправителю, отказ одному и всем получателям, отказ на этапе DATA, тайм-ауты до и после возможного приёма, обрывы соединения, ответы об ограничении частоты, истечение срока повторов, дублирующиеся воркеры, изменения стоп-листа и ротацию секретов. Для детерминированных модульных и интеграционных тестов используйте контролируемый тестовый SMTP-сервис или локальную заглушку; никогда не направляйте случайный трафик из тестовых окружений на адреса клиентов. В продакшен-канарейках используйте авторизованных получателей и изучайте исходные заголовки: видимый From, путь конверта, Message-ID, DKIM, SPF, выравнивание DMARC и данные провайдера. Убедитесь, что в логи и метрики не утекают учётные данные или тексты писем. Не допускайте запуска, если воркер может обойти авторизацию тенанта, понизить TLS, повторять без ограничений, игнорировать частичные отказы или не умеет приостанавливать маршрут отправки.
Как SendHQ вписывается в эту схему
SendHQ — email API в рамках рабочего пространства для ожидаемой продуктовой коммуникации. Его документация охватывает отправку, подтверждённые домены, события доставки и стоп-листы. При интеграции SendHQ с Python используйте его документированный HTTP API.
Частые вопросы
Что использовать в Python: SMTP_SSL или STARTTLS?
Используйте режим, который требует текущий контракт отправки вашего провайдера. SMTP_SSL шифрует соединение с самого начала; STARTTLS явно повышает защиту соединения и требует проверенного TLS и повторного EHLO.
Доказывает ли нормальное завершение send_message доставку?
Нет. Это означает, что на данном этапе отправки по SMTP принят хотя бы один получатель. Приём сервером назначения, попадание в папку ящика и вовлечённость требуют последующих данных в соответствующих рамках.
Что означает словарь, который возвращает send_message?
Он сопоставляет получателей, отклонённых SMTP-сервером, с данными ответа. Пустой словарь означает, что на этом этапе никто не был отклонён, а не что каждое письмо попало во «Входящие».
Можно ли сразу повторить запрос после тайм-аута?
Небезопасно, если тайм-аут произошёл после возможной отправки. Сохраните попытку как неоднозначную, сверьте данные провайдера или последующих событий и отправляйте повторно только в рамках ограниченной политики риска дублей.
Где хранить пароль SMTP?
Используйте управляемое серверное хранилище секретов с узким доступом по нагрузке и окружению, аудитом получения, проверенной ротацией и без доступа для клиентов, логов, промптов или фикстур.
Можно ли когда-либо отключать проверку сертификата в продакшене?
Нет. Ошибка сертификата или имени хоста свидетельствует о небезопасной или неверной конфигурации. Остановите маршрут и разберитесь в причине, а не ослабляйте молча проверку TLS.
Как обрабатывать частичный отказ получателям?
Сохраняйте результат по каждому получателю, переводите дальше принятую часть и повторяйте только допустимые временные отказы. Не отправляйте повторно уже принятым получателям вместе со всем исходным списком.
Доказывает ли эта страница, что SendHQ поддерживает SMTP?
Нет. Это руководство охватывает Python SMTP в целом; для его email API используйте актуальную документацию SendHQ.
Источники
- smtplib — клиент протокола SMTP — Python Software Foundation
- email.message: представление email-сообщения — Python Software Foundation
- Примеры работы с email в Python — Python Software Foundation
- RFC 5321: простой протокол передачи почты (SMTP) — RFC Editor
- RFC 4954: расширение SMTP для аутентификации — RFC Editor
- RFC 8314: незашифрованный текст признан устаревшим: использование Transport Layer Security (TLS) для отправки и доступа к почте — RFC Editor