在使用 python 的 email 和 smtplib 包发送的电子邮件中,EmailMessage 无法正确显示

Posted

技术标签:

【中文标题】在使用 python 的 email 和 smtplib 包发送的电子邮件中,EmailMessage 无法正确显示【英文标题】:EmailMessage doesn't show correctly in the sent emails with python's email and smtplib packages 【发布时间】:2021-12-10 05:02:55 【问题描述】:

我的邮件可以正确发送,但在收件人邮件中显示不正确。它看起来像这样:

收件人:=?utf-8?b?..?= <..com> MIME 版本:1.0 内容类型:多部分/混合; 边界="===============5404281335870522242=="

--===============5404281335870522242== 内容类型:文本/纯文本; charset="utf-8" 内容传输编码:base64

5bCK5pWs55qE5a2U6LaF5YW...

--================5404281335870522242== 内容类型:image/png 内容传输编码:base64 内容处置:附件; filename="user.png" MIME 版本:1.0

iVBORw0KGgo...

除了SubjectFrom 行(显示在To 之后)以及所有纯文本正文之外,MIME 字符串直接显示。

这是我的代码:

import smtplib
import ssl
import mimetypes
from pathlib import Path
from email.message import EmailMessage
from email.utils import formataddr
import time

class EmailSender:
    PORT = 465
    CONTEXT = ssl.create_default_context()

    def __init__(
        self,
        username,
        password,
        host,
    ):
        self.username = username
        self.password = password
        self.host = host
        self.mails = []

    def _add_name_header(self, name="", mail_addr=""):
        if name:
            return formataddr((name, mail_addr))
        else:
            return mail_addr

    def add_mail(
        self,
        from_email="",
        from_name="",
        to_email="",
        to_name="",
        subject="",
        message_txt="",
        files=None,
    ):
        msg = EmailMessage()
        msg["Subject"] = subject
        msg["From"] = self._add_name_header(from_name, from_email)
        msg["To"] = self._add_name_header(to_name, to_email)
        msg.set_content(message_txt)

        if not files is None:
            for file_obj in files:
                if file_obj.exists():
                    file = str(file_obj)
                    ctype, encoding = mimetypes.guess_type(file)
                    if ctype is None or encoding is not None:
                        # No guess could be made, or the file is encoded (compressed), so use a generic bag-of-bits type.
                        ctype = "application/octet-stream"
                    maintype, subtype = ctype.split("/", 1)
                    with file_obj.open("rb") as fp:
                        msg.add_attachment(
                            fp.read(),
                            maintype=maintype,
                            subtype=subtype,
                            filename=file_obj.name,
                        )

        self.mails.append(msg)

    def send(self, time_interval=1):
        with smtplib.SMTP_SSL(
            host=self.host, port=self.PORT, context=self.CONTEXT
        ) as server:
            try:
                server.login(user=self.username, password=self.password)
            except Exception as e:
                # Need process errors
                raise e
            for msg in self.mails:
                server.send_message(msg)
                time.sleep(time_interval)

我只是这样做:

sender = EmailSender(
        username, password, host="smtp.163.com"
)

files = list(Path("D:/").glob("*.pdf"))

sender.add_mail(
        from_email, from_name, to_email, to_name, subject, message_txt, files=None
)
sender.send(time_interval=10)

【问题讨论】:

【参考方案1】:

我是问题的 OP。我自己解决了这个问题,我会分享解决方案。

TLNR:我的邮件中使用了非 Ascii 字符,因此请使用 msg = EmailMessage(EmailPolicy(utf8=True)) 而不是 msg = EmailMessage()

我在SMTP.send_message的文档中误解了这些句子

如果 from_addr 和 to_addrs 中的任何地址包含非 ASCII 字符和服务器不宣传 SMTPUTF8 支持,一个 引发 SMTPNotSupported 错误。否则消息被序列化 其策略的克隆将 utf8 属性设置为 True,并且 将 SMTPUTF8 和 BODY=8BITMIME 添加到 mail_options。

由于我在地址中添加了非 ASCII 标头,我相信 smtplib 会自动为我使用 utf8 策略。但是在smtplib.py文件中我看到了这个:

if from_addr is None:
    # Some code
    from_addr = email.utils.getaddresses([from_addr])[0][1]
if to_addrs is None:
    # Some code
    to_addrs = [a[1] for a in email.utils.getaddresses(addr_fields)]
# Some code
international = False
try:
    "".join([from_addr, *to_addrs]).encode("ascii")
except UnicodeEncodeError:
    # Some code
    international = True

也就是说,该函数仅检查地址部分是否包含非 ASCII 字符,但不检查标头名称。

之后,消息被处理为纯ASCII内容,这可能没问题,我不知道为什么,但是somohow,To:xxx行之前和之后插入了许多额外的/r字符,这使得 smtp 服务器可能认为这是一个分隔符?最终导致了问题。

【讨论】:

以上是关于在使用 python 的 email 和 smtplib 包发送的电子邮件中,EmailMessage 无法正确显示的主要内容,如果未能解决你的问题,请参考以下文章

Python使用SMTP模块email模块发送邮件

Python使用SMTP模块email模块发送邮件

python发送email邮件

python发送email邮件

Python使用SMTP模块email模块发送邮件

Python SMTP邮件模块