Сообщение об ошибке Outlook.com / Office365 не очень полезно, поскольку может указывать на любое количество проблем.Это указывает на то, что почтовые серверы Microsoft были недовольны каким-либо аспектом упаковки электронной почты (заголовки, вложения и т. Д.), И их анализаторы где-то ошиблись.В противном случае их сообщение об ошибке почти бесполезно в том, что оно предоставляет.Я считаю, что утверждение о том, что это проблема безопасности , является бессмысленным;Flask-Mail использует проверенные пакеты стандартной библиотеки Python email
и smtplib
для отправки электронной почты через зашифрованное соединение TLS.
Для Flask-Mail в Heroku я вместо этого отследил проблему до заголовка Message-ID, который генерируется на машинах Heroku Dyno.Проблема не ограничивается Heroku, однако вы можете увидеть это на любом хосте с длинным именем хоста .Типичное имя хоста Heroku dyno начинается с полного UUID, плюс еще 5 компонентов или около того, например, aaf39fce-569e-473a-9453-6862595bd8da.prvt.dyno.rt.heroku.com
.
Это имя хоста используется в заголовке идентификатора сообщения, который создается для каждого электронного письма.Пакет Flask-Mail использует стандартную email.utils.make_msgid()
функцию для генерации заголовка, которая по умолчанию использует текущее имя хоста.В результате получается заголовок идентификатора сообщения, например:
Message-ID: <154810422972.4.16142961424846318784@aaf39fce-569e-473a-9453-6862595bd8da.prvt.dyno.rt.heroku.com>
Это строка длиной 110 символов.Это небольшая проблема для заголовков электронной почты, потому что в RFC для электронной почты указано, что заголовки должны ограничиваться 78 символами.Однако есть способы обойти это;для значений заголовков длиннее 77 символов можно использовать положения в заголовках от RFC 5322 до fold .При свертывании можно использовать несколько RFC 2047 кодированных слов в нескольких строках.Это то, что происходит здесь, заголовок электронного письма выше становится
Message-ID: =?utf-8?q?=3C154810422972=2E4=2E16142961424846318784=40aaf39fce-?=
=?utf-8?q?569e-473a-9453-6862595bd8da=2Eprvt=2Edyno=2Ert=2Eheroku=2Ecom=3E?=
, который, будучи 78 и 77 символами, теперь соответствует стандарту MIME электронной почты.
Все это мне кажетсябыть совместимым со стандартами и действительным методом обработки почтовых заголовков.Или, по крайней мере, то, что другие почтовые провайдеры переносят и обрабатывают должным образом, но почтовые серверы Microsoft не имеют этого.Им действительно не нравится вышеуказанный заголовок Message-ID, закодированный в RFC2047, и они пытаются обернуть тело в прикрепленный файл TNEF winmail.dat.Это не всегда работает, поэтому вы получите очень загадочное 554 5.6.0 Поврежденное сообщение Сообщение об ошибке.Я считаю это ошибкой Microsoft;Я не уверен на 100%, что RFC электронной почты позволяют складывать заголовок Message-ID с использованием закодированных слов, но MS обрабатывает ошибку, отправляя получателю бессмысленную ошибку, а не отклоняет сообщение, когда получение просто ужасно.
Вы можете установить альтернативную политику электронной почты для использования Flask-Mail, задав глобальный модуль flask_mail.message_policy
, или мы можем сгенерировать другой идентификатор сообщения.
Политики электронной почты доступны только в том случае, если вы используете Python 3.3 или более поздней версии, но именно объект политики обрабатывает свертывание, что позволяет нам изменять способ обработки Message-ID и других заголовков идентификатора RFC 5322.Вот подкласс, который не свернет заголовок Message-ID;на самом деле стандарт позволяет использовать до 998 символов в одной строке, и этот подкласс использует это ограничение только для этого заголовка:
import flask_mail
from email.policy import EmailPolicy, SMTP
# Headers that contain msg-id values, RFC5322
MSG_ID_HEADERS = {'message-id', 'in-reply-to', 'references', 'resent-msg-id'}
class MsgIdExcemptPolicy(EmailPolicy):
def _fold(self, name, value, *args, **kwargs):
if (name.lower() in MSG_ID_HEADERS and
self.max_line_length < 998 and
self.max_line_length - len(name) - 2 < len(value)
):
# RFC 5322, section 2.1.1: "Each line of characters MUST be no
# more than 998 characters, and SHOULD be no more than 78
# characters, excluding the CRLF.". To avoid msg-id tokens from being folded
# by means of RFC2047, fold identifier lines to the max length instead.
return self.clone(max_line_length=998)._fold(name, value, *args, **kwargs)
return super()._fold(name, value, *args, **kwargs)
flask_mail.message_policy = MsgIdExcemptPolicy() + SMTP
В Python 2.7 или Python 3.2 или более поздней версии вам придется прибегнуть к заменезаголовок Message-Id, просто заново сгенерируйте заголовок с жестко закодированным доменным именем:
from flask import current_app
from flask_mail import Message as _Message
# set this to your actual domain name
DOMAIN_NAME = 'example.com'
class Message(_Message):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# work around issues with Microsoft Office365 / Outlook.com email servers
# and their inability to handle RFC2047 encoded Message-Id headers. The
# Python email package only uses RFC2047 when encoding *long* message ids,
# and those happen all the time on Heroku, where the hostname includes a
# full UUID as well as 5 more components, e.g.
# aaf39fce-569e-473a-9453-6862595bd8da.prvt.dyno.rt.heroku.com
# The work-around is to just use our own domain name, hard-coded, but only
# when the message-id length exceeds 77 characters (MIME allows 78, but one
# is used for a leading space)
if len(self.msgId) > 77:
domain = current_app.config.get('MESSAGE_ID_DOMAIN', DOMAIN_NAME)
self.msgId = make_msgid(domain=domain)
Затем вы использовали бы вышеупомянутый класс Message
вместо класса flask_mail.Message()
, и онсоздаст более короткий заголовок Message-ID, который не будет конфликтовать с проблемными парсерами заголовков Microsoft.
Я подал отчет об ошибке в проекте Python для отслеживания обработки токенов msg-id, как я подозреваю, это действительно должно быть решено там.