作者:灰色头像6888 | 来源:互联网 | 2022-12-02 18:24
我们有一个Web应用程序,它向客户端发送电子邮件,并且该Web应用程序正在使用Flask邮件框架进行处理。大约2周前,我们的Web应用程序未能将电子邮件发送给客户和我们自己的团队。我们使用Office 365的Outlook作为发件人。
远程服务器返回'554 5.6.0损坏的消息内容;STOREDRV.Deliver.Exception:ConversionFailedException; 由于消息内容转换的永久异常而无法处理消息:TNEF摘要内容已损坏。ConversionFailedException:内容转换:概要TNEF内容已损坏。[阶段:PromoteCreateReplay]'原始邮件标题:
这是发件人被指示发送电子邮件后收到的错误消息。我们联系了Office 365管理员,Microsoft告诉他我们的Web应用程序具有的安全性不符合Microsoft的要求/协议。
问题是Flask邮件使用的旧版安全协议或配置无法与Microsoft Outlook很好地配合吗?
1> Martijn Piet..:
Outlook.com / Office365错误消息没有多大用处,因为它可以指示许多问题。这表明Microsoft邮件服务器对电子邮件包装的某些方面(标头,附件等)不满意,并且其解析器在某处出错。否则,它们的错误消息在提供的细节上几乎是无用的。我认为这是一个安全问题,这是胡说八道。Flask-Mail使用经过良好测试的Python标准库email
和smtplib
程序包,以通过TLS加密连接发送电子邮件。
对于Heroku上的Flask-Mail,我将问题追溯到在Heroku Dyno机器上生成的Message-ID标头。该问题不仅限于Heroku,但是,在主机名较长的任何主机上都会看到此问题。典型的Heroku dyno主机名以完整的UUID开头,外加另外5个组件,例如aaf39fce-569e-473a-9453-6862595bd8da.prvt.dyno.rt.heroku.com
。
该主机名用于为每封电子邮件生成的Message-ID标头中。Flask-Mail软件包使用标准email.utils.make_msgid()
函数生成标头,并且默认情况下使用当前主机名。然后,产生一个Message-ID标头,例如:
Message-ID: <154810422972.4.16142961424846318784@aaf39fce-569e-473a-9453-6862595bd8da.prvt.dyno.rt.heroku.com>
这是一个110个字符长的字符串。对于电子邮件标题,这是一个小问题,因为电子邮件RFC规定标题应限制为78个字符。但是,有多种方法可以解决此问题。为标题超过77个字符值越长,你可以使用在规定RFC 5322到折叠头。折叠可以在多行上使用多个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的邮件服务器没有此功能。他们真的不喜欢上面的RFC2047编码的Message-ID标头,并尝试将正文包装在TNEF winmail.dat附件中。这并不总是有效,因此最终会得到非常神秘的554 5.6.0损坏的消息内容错误消息。我认为这是Microsoft的错误;我不是100%肯定电子邮件RFC允许使用编码的单词折叠Message-ID标头,但是MS通过向收件人发送无意义的错误而不是在接收时拒绝消息来处理错误,这是很糟糕的。
您可以通过将模块设置为global 来设置供Flask-Mail使用的替代电子邮件策略flask_mail.message_policy
,或者我们可以生成其他message-ID。
仅当您使用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
在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令牌的处理,因为我怀疑这确实应该在那里解决。