当我有来自的命名附件时,Outlook 365 不接受 Python 电子邮件消息库输出

Posted

技术标签:

【中文标题】当我有来自的命名附件时,Outlook 365 不接受 Python 电子邮件消息库输出【英文标题】:Python's Email Message library output not getting accepted by Outlook 365 when i have a named attachments from 【发布时间】:2019-02-18 17:43:40 【问题描述】:

我创建了一个示例函数来测试发送带有附加 html 文件的电子邮件,我打算用它来报告未来的自动化测试运行(替换现有的外部 powershell 脚本)。请注意,我附加的是 html 文件,而不是使用 html 作为正文中的内联文本。我正在使用我们公司的 mailgun smtp 帐户服务发送电子邮件。

我似乎遇到了 Outlook 365(网络托管 - 使用 Outlook.office.com 域)拒绝或阻止发送的电子邮件的问题,但有趣的是,我的个人 hotmail 地址(outlook. live.com 域)。当我尝试在电子邮件对象中命名文件时,我发现 Outlook 365 阻止或不接受电子邮件。但如果我不命名它,它会通过(默认名称为 "ATT00001.htm" )。

我的代码如下,但它们的关键行似乎是

msg.add_attachment(open_file.read(), maintype='text', subtype='html', filename=filename)

如果我删除文件名键,它可以工作(但使用默认分配的文件名)例如

msg.add_attachment(open_file.read(), maintype='text', subtype='html')

我怀疑附件的标题或内容处置中存在 Outlook 365 不同意的内容,但我不确定它是什么或如何解决。

我正在使用以下(Python 3.6.5,在 Windows 10 机器上,似乎内置了 smtplib 和 email.message)

代码如下:

import smtplib
from email.message import EmailMessage
import os


def send_mail():
    MAILGUN_SMTP_LOGIN = "<my company's mailgun login>"
    MAILGUN_SMTP_PASSWORD = "<my company's mailgun password>"

    fromaddr = "muppet@sharklasers.com" # the from address seems to be inconsequential 
    toaddr = ['me@mycompanysdomainusingoffice365.com.au', 'me@hotmail.com']

    msg = EmailMessage()
    msg.preamble = 'This is preamble. Not sure where it should show in the email'

    msg['From'] = fromaddr
    msg['To'] = ', '.join(toaddr)
    msg['Subject'] = 'Testing attached html results send'
    msg.set_content(""" This is a test of attached html """)


    filename = 'api_automatedtests_20180903_1341.html'
    filepath = os.path.abspath('D:/work/temp/api_automatedtests_20180903_1341.html')
    open_file = open(filepath, "rb")
    # msg.make_mixed()
    msg.add_attachment(open_file.read(), maintype='text', subtype='html', filename=filename)
    # msg.add_attachment(open_file.read(), maintype='text', subtype='html')


    server = smtplib.SMTP(host="smtp.mailgun.org", port=587)
    server.ehlo()
    server.starttls()
    server.login(MAILGUN_SMTP_LOGIN, MAILGUN_SMTP_PASSWORD)
    server.set_debuglevel(1)
    server.send_message(msg)
    server.quit()


if __name__ == "__main__":
    send_mail()

我尝试过的

    尝试使用文本文件(具有适当的类型)使用相同的代码发送。例如msg.add_attachment(open_file.read(), maintype='text', subtype='plain', filename=filename) 结果:这按预期工作(通过给定名称出现 - 文件名是字符串变量,例如 testfile.txt)

    添加 msg.make_mixed() 以确保它被识别为多部分消息。结果:没有效果

    打开 smtp 调试级别 1,结果:Mailgun 说一切正常(并且消息确实按预期显示在我的 hotmail 帐户中)

    在 msg.add_attachment 调用中未使用文件名键。 结果:这个工作附件来自 ATT00001.htm 有趣的是,默认名称是 *.htm 而我尝试使用的文件名是 *.html

      尝试使用带有 *.htm 的文件名和“htm”子类型(而不是 html) 结果:与 html 相同(在 hotmail 上收到但在 Outlook 365 上没有收到)

      尝试使用泛型类型 maintype=''application', subtype='octet-stream'。 例如msg.add_attachment(open_file.read(), maintype='application', subtype='octet-stream', filename=filename) 结果:与 html 相同(在 hotmail 上收到但在 Outlook 365 上没有收到)

      尝试使用 mimetypes.guess,如此链接所示https://docs.python.org/3.6/library/email.examples.html

    ctype, encoding = mimetypes.guess_type(path) 如果 ctype 为 None 或 encoding 不是 None: # 无法猜测,或者文件被编码(压缩),所以 # 使用通用的 bag-of-bits 类型。 ctype = '应用程序/八位字节流' maintype, subtype = ctype.split('/', 1) 使用 open(path, 'rb') 作为 fp: msg.add_attachment(fp.read(), 主类型=主类型, 子类型=子类型, 文件名=文件名) 结果:确定为maintype='text', subtype='html',我得到的结果与我原来的代码相同(即到达hotmail但被365阻止)。

      检查我的垃圾邮件和杂乱文件夹 - 不在那里

关于为什么使用文件名会破坏它的任何建议?

更新 在通过不同的提供商发送到其他电子邮件地址后,我发现:

1) muppet@sharklasers.com 不是受信任的发件人(可以更改)

2) 我发现附件被标记为不安全。 html 文件来自 pytest 的带有单个文件选项的 html 报告。它包含用于行扩展器的 javascript。 Gmail 警告附件可能不安全(office 365 直接完全屏蔽了电子邮件)。

不确定如何解决 2)。我可以在 Outlook 365 和 gmail 之间通过电子邮件将相同的文件发送给自己,反之亦然,并且文件不会被阻止。只有当我使用 python 库和 Mailgun SMTP 使用上述脚本时,它才会被阻止。我怀疑我需要在电子邮件标题中更改某些内容才能解决此问题。但我不知道是什么。

在尝试添加文件名和附件被标记为不安全之间似乎存在某种联系

【问题讨论】:

【参考方案1】:

好的,我想通了。问题是需要在其值中包含“name=filename”的内容类型。 我还需要使用 maintype='multipart', subtype='mixed'。

我有 2 个解决方案。

解决方案 1

import smtplib
from email.message import EmailMessage
import os

def send_mail(body_text, fromaddr, recipient_list, smtp_login, smtp_pass, file_path):
    msg = EmailMessage()
    msg.preamble = 'This is preamble. Not sure where it should show'

    msg['From'] = fromaddr
    msg['To'] = ', '.join(recipient_list)
    msg['Subject'] = 'API Testing results'
    msg.set_content(body_text)

    filename = os.path.basename(file_path)
    open_file = open(file_path, "rb")
    msg.add_attachment(open_file.read(), maintype='multipart', subtype='mixed; name=%s' % filename, filename=filename)

    server = smtplib.SMTP(host="smtp.mailgun.org", port=587)
    server.ehlo()
    server.starttls()
    server.login(smtp_login, smtp_pass)
    server.send_message(msg)
    server.quit()


if __name__ == "__main__":
    smtp_login = "<my smtp login>"
    smtp_pass = "<my smtp password>"
    recipient_list = ['user1@mycompany.com.au', 'user2@mycompany.com.au']
    file_path = os.path.abspath('D:/work/temp/api_automatedtests_20180903_1341.html')
    body_text = "test results for 03/09/2018 "
    fromaddr = 'autotesting@mycompany.com.au'
    send_mail(body_text=body_text, recipient_list=recipient_list, smtp_login=smtp_login, smtp_pass=smtp_pass,
              file_path=file_path)

解决方案 2(根据使用 email.mime 库的文档,这是一个遗留解决方案,应该优先使用 EmailMessage 方法。

import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
import os

def send_mail(body_text, fromaddr, recipient_list, smtp_login, smtp_pass, file_path):

    msg = MIMEMultipart()
    msg['From'] = fromaddr
    msg['To'] = ', '.join(recipient_list)
    msg['Subject'] = "Sending API test results"
    msg.attach(MIMEText(body_text, 'plain'))

    filename = os.path.basename(file_path)
    attachment = open(file_path, "rb")

    part = MIMEBase('multipart', 'mixed; name=%s' % filename)
    part.set_payload(attachment.read())
    encoders.encode_base64(part)
    part.add_header('Content-Disposition', "attachment; filename= %s" % filename)

    msg.attach(part)

    server = smtplib.SMTP(host="smtp.mailgun.org", port=587)
    server.starttls()
    server.login(smtp_login, smtp_pass)
    text = msg.as_string()
    server.set_debuglevel(1)
    server.sendmail(fromaddr, recipient_list, text)
    server.quit()

if __name__ == '__main__':
    smtp_login = "<my smtp login>"
    smtp_pass = "<my smtp password>"
    recipient_list = ['user1@mycompany.com.au', 'user2@mycompany.com.au']
    file_path = os.path.abspath('D:/work/temp/api_automatedtests_20180903_1341.html')
    body_text = " Api test results for 03/09/2018 "
    fromaddr = "autotest@mycompany.com.au"
    send_mail(body_text=body_text, fromaddr=fromaddr, recipient_list=recipient_list, smtp_login=smtp_login, smtp_pass=smtp_pass,
              file_path=file_path)

【讨论】:

以上是关于当我有来自的命名附件时,Outlook 365 不接受 Python 电子邮件消息库输出的主要内容,如果未能解决你的问题,请参考以下文章

逻辑应用和 Outlook 嵌入图像

Android后台服务无法解析/访问outlook.office365.com,除非手机负责?

在文件名上使用 ReceivedTime 保存来自 Outlook 的附件

通过 python 发送时附件不显示在 Outlook 中

通过 Outlook 中的宏写入电子邮件添加默认电子邮件签名

将电子邮件附件移动到 Outlook 中的子文件夹