【问题标题】:How do I send email to addresses with non-ASCII characters in Python?如何在 Python 中向具有非 ASCII 字符的地址发送电子邮件?
【发布时间】:2019-02-07 13:34:16
【问题描述】:

使用 Python 3.x 中的 emailsmtplib 模块,经过大量研究,我可以发送带有 Unicode 主题、文本正文和名称(针对发件人和收件人)的电子邮件,这很棒,但它不允许我向本身包含 Unicode(或其他非 ASCII)字符的地址发送电子邮件。它似乎不受支持(如果您查看 email.utils 中的 cmets,它会这么说:即“地址必须(根据 RFC)是 ascii,因此如果不是,请引发 UnicodeError。”)任何尝试无论如何都要这样做(包括但不仅限于密件抄送收件人——以试图绕过任何邮件头限制)因一种或另一种形式的 Unicode 错误而失败。评论没有说明哪个 RFC(我不认为他们都指定电子邮件地址应仅使用 ASCII。)

有没有其他方法可以做到这一点,因为有传言说这样的地址可以存在于某些地方:úßerñame@dómain.com?我的意思是,还有其他支持它的电子邮件模块吗?

如果我的问题的前提不正确,电子邮件地址是否旨在成为全世界的 ASCII 格式(尽管有传言称其中一些使用其他字符)?

我看到这个问题是针对其他语言的,但不是针对 Python。

【问题讨论】:

  • 电子邮件地址不是“打算变成纯 ASCII”。它们最初仅支持 ASCII,旨在对世界其他地方更加友好,但这是一个缓慢的过渡。
  • 好吧,根据 RFC 3492,dómain.com 只是 xn--dmain-0ta.com
  • 我发现如果我将我的服务器对象的 command_encoding (smtplib.SMTP.command_encoding) 设置为“utf-8”(设置为“ascii”),它会像发送 BCC带有特殊字符的电子邮件。当然,我不知道它们是否会被接收,但它会发送。
  • 不知道是 smtplib 还是 Gmail 的 SMTP 服务器,但是在修改 email.utils.formataddr 以接受 UTF-8 电子邮件地址后,它通常说它发送电子邮件,但查看我发送的Gmail 上的文件夹,它似乎没有将电子邮件发送到 UTF-8 地址,在一种情况下,我得到以下异常:smtplib.SMTPRecipientsRefused(我不确定这是由于我的 SMTP 服务器还是因为 UTF smtplib 确实不支持 -8)。

标签: python python-3.x email unicode


【解决方案1】:

电子邮件地址是否打算成为全世界的 ASCII 格式?

没有;事实上,恰恰相反。电子邮件地址仅 ASCII。它们打算成为 Unicode,而我们正在努力实现;这只是一个缓慢的过渡。


在现代电子邮件中,电子邮件地址有两部分:1 DNS 主机名(@ 之后的部分)和该主机上的邮箱(@987654330 之前的部分@)。它们受完全不同的标准管理,因为 DNS 必须适用于 HTTP 和除了电子邮件之外的所有其他事物。


DNS 最后一次更新是在 1987 年 RFC 1035,它要求 ASCII 的一个受限子集(并且不区分大小写)。

但是,RFC 5890 中指定的 IDNA(应用程序的国际化域名)允许应用程序有选择地将 Unicode 字符集的大部分映射到 DNS 名称以呈现给用户。

因此,您不能拥有域名dómain.com。但是您可以拥有域名xn--dmain-0ta.com。而且很多应用会接受来自用户输入的dómain.com并自动翻译,接受来自网络的xn--dmain-0ta.com并显示给dómain.com2

在 Python 中,一些互联网协议库会自动为您对域名进行 IDNA 编码;否则不会。如果他们不这样做,您可以手动执行,如下所示:

>>> 'dómain.com'.encode('idna')
b'xn--dmain-0ta.com'

请注意,在 3.x 中,这是 bytes,而不是 str;如果您需要str,您可以随时这样做:

>>> 'dómain.com'.encode('idna').decode('ascii')
'xn--dmain-0ta.com'

邮箱名称由 SMTP 定义,最近在 RFC 5321RFC 5322 中定义,这清楚地表明,如何解释地址的“本地部分”完全取决于接收主机。例如,大多数电子邮件服务器使用不区分大小写的名称;许多允许“加标签”(例如,shule@gmail.comshule+so@gmail.com 是同一个邮箱);一些(如 gmail)忽略所有点;等等

问题在于 SMTP 从未指定用于标头的字符集。传统的 SMTP 服务器只有 7 位 ASCII,因此,实际上,直到最近,您只能在标头中使用 ASCII,因此在邮箱名称中也是如此。

RFC 6530 和相关提案中指定的 EAI(电子邮件地址国际化)允许在 SMTP 会话中协商 UTF-8。在 UTF-8 会话中,标头和这些标头中的地址被解释为 UTF-8。 (主机名的 IDNA 编码不是必需的,但仍然允许。)

这很好,但是如果您的客户端、服务器、收件人的服务器或沿途的任何中继服务器不使用 SMTPUTF8 怎么办?为了处理这种情况,每个拥有 UTF-8 邮箱的人也都有该邮箱的 ASCII 名称。理想情况下,它与消息一起发送,并且链上的最后一个 SMTPUTF8 程序在遇到第一个非 SMTPUTF8 程序时切换到 ASCII 替代程序。更常见的是,它只是收到一条错误消息并将其传播回用户以手动处理。3

我们的想法是,最终,互联网上的大多数主机都会使用 SMTPUTF8,因此您可以是 úßerñame@dómain.com — 但与此同时,您在 dómain.com 上的服务器将 úßerñameussernyame 作为同一个邮箱的别名。任何不能处理 SMTPUTF8 的人都会看到您(并且必须引用您)为ussernyame。 (事实上​​,他们的邮件客户端会将您视为ussernyame@xn--dmain-0ta.com,但它可以修复最后一部分;如果第一部分在传输过程中丢失,它无能为力。)

截至 2018 年年中,大多数主机不支持 SMTPUTF8,许多客户端库也不支持。

从 Python 3.5 开始,4 标准库的 smtplib 支持 SMTPUTF8。如果您使用的是高级sendmail 函数:

如果SMTPUTF8 包含在 mail_options 中,并且服务器支持它,from_addrto_addrs 可能包含非 ASCII 字符。

所以,你所做的是这样的:

try:
    server.sendmail([fromaddr], [toaddr], msg, mail_options=['SMTPUTF8'])
except SMTPNotSupportedError:
    server.sendmail([fromaddr_ascii], [toaddr_ascii], msg)

(理论上最好用has_extn检查EHLO响应,但在实践中,尝试它似乎更值得顺利。这可能会随着服务器生态系统和/或smptlib的未来改进而改变。)

你从哪里得到fromaddr_asciitoaddr_ascii?这取决于你的程序。 DNS部分,你只使用IDNA,但对于邮箱部分,没有这样的规则;您必须知道邮箱的备用 ASCII 邮箱名称。也许你问用户。也许您有一个数据库,其中存储了 EAI 和传统地址的联系人。也许您只担心一个特定的域,并且您知道它使用了一些您可以实施的规则。


1。实际上,addr-spec 有两个部分; address 是一个 addr-spec 加上可选的显示名称和注释。不过没关系。

2。有几个例外。例如,如果您键入http://staсkoverflow.com,您的浏览器可能会警告您使用西里尔字母小写字母 Es 代替拉丁字母小写字母 Cee 可能是一种劫持尝试。或者,如果您尝试导航到http://dómain.com,则告诉您该域不存在的错误页面可能会显示xn--dmain-0ta.com,因为这对于调试更有用。

3。这是希望随着时间的推移会变得更好的事情之一,但在它不再重要之前可能不会变得足够好......

4.如果您使用的是 Python 3.4 或 2.7 怎么办?那么你没有 SMTPUTF8 支持。升级,找第三方库代替smtplib,或者自己写SMTP代码。

【讨论】:

  • 邮件标头和 toaddr/fromaddr 中的域是否需要是 idna 样式,还是只是标头?你还在支持 SMTPUTF8 的域上使用 idna 样式的文本吗(如果你不需要,会不会很痛苦)?
  • @Shule 相关规范是section 3.2 of RFC 6531:如果您的软件充当客户端,它“可能会在 SMTP 命令或邮件头中将邮箱名称的域部分作为 A 标签或 U -标签”。换句话说,您可以在命令和标头中使用 Unicode 域,而无需执行 IDNA。但是,您可能还是想这样做,因为某些服务器在实践中可能会更好地处理它。 (例如,exim 会处理它们,如果它配置为,但 IIRC,Ubuntu 16.04 LTS 中的版本不是。)
  • @Shule 最后一点:即使您不需要,它也不应该损害向 IDNA 交付域名。但是,这确实意味着如果收件人的客户端软件不知道 IDNA,它就不会很好地显示地址。 (不知道 IDNA 的传统客户端软件可能也不知道 UTF-8,所以 IDNA 比 mojibake 更好……但网络邮件客户端可能是另一回事。)
  • 嗯,现在好像正在发送。不过,我仍然需要尝试格式化收件人,看看这是否能解决我的 Gmail 发送文件夹中的邮件标题问题(这似乎只是密件抄送的问题,如果我同时有 Unicode 收件人姓名和 Unicode电子邮件地址——而不是一个或另一个)。我确实必须使用 SMTPUTF8 和我修改后的 email.utils.formataddr 版本(但在使用 SMTPUTF8 时似乎不需要设置 command_encoding)。在发送到我的 Yandex 电子邮件时,我必须使用 IDNA 域来回复电子邮件(以便能够从 Yandex 回复它们)。
  • 哦,我还为所有电子邮件字段使用了 msg.add_header,而不是分配诸如 msg["To"]=addresses 之类的值。默认情况下支持 UTF-8,尽管我不记得在这种情况下使用 formataddr 时有多么必要。
猜你喜欢
  • 2017-03-01
  • 2013-01-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-06
  • 2013-12-28
  • 2012-08-17
  • 2016-03-06
相关资源
最近更新 更多