smtplib — SMTP 协议客户端

源代码: Lib/smtplib.py


smtplib 模块定义了一个 SMTP 客户端会话对象,可用于将邮件发送到任何具有 SMTP 或 ESMTP 监听守护进程的互联网机器。有关 SMTP 和 ESMTP 操作的详细信息,请参阅 RFC 821(简单邮件传输协议)和 RFC 1869(SMTP 服务扩展)。

可用性: 不适用于 Emscripten,不适用于 WASI。

此模块在 WebAssembly 平台 wasm32-emscriptenwasm32-wasi 上不可用或无法工作。有关更多信息,请参阅 WebAssembly 平台

class smtplib.SMTP(host='', port=0, local_hostname=None, [timeout, ]source_address=None)

SMTP 实例封装了一个 SMTP 连接。它具有支持完整 SMTP 和 ESMTP 操作的方法。如果给出了可选的 hostport 参数,则在初始化期间使用这些参数调用 SMTP connect() 方法。如果指定,则 local_hostname 将在 HELO/EHLO 命令中用作本地主机的 FQDN。否则,将使用 socket.getfqdn() 查找本地主机名。如果 connect() 调用返回的不是成功代码,则会引发 SMTPConnectError。可选的 timeout 参数指定连接尝试等阻塞操作的超时时间(以秒为单位)(如果未指定,则将使用全局默认超时设置)。如果超时,则会引发 TimeoutError。可选的 source_address 参数允许绑定到具有多个网络接口的机器中的某个特定源地址,和/或绑定到某个特定的源 TCP 端口。它采用一个 2 元组 (host, port),用于在连接之前将套接字绑定到其源地址。如果省略(或者如果 hostport 分别为 '' 和/或 0),则将使用操作系统默认行为。

对于正常使用,您只需要初始化/连接、sendmail()SMTP.quit() 方法。下面包含一个示例。

SMTP 类支持 with 语句。像这样使用时,SMTP QUIT 命令会在 with 语句退出时自动发出。例如:

>>> from smtplib import SMTP
>>> with SMTP("domain.org") as smtp:
...     smtp.noop()
...
(250, b'Ok')
>>>

所有命令都会引发 审计事件 smtplib.SMTP.send,其参数为 selfdata,其中 data 是即将发送到远程主机的字节。

版本 3.3 中的变化: 添加了对 with 语句的支持。

版本 3.3 中的变化: 添加了 source_address 参数。

版本 3.5 中的新功能: 现在支持 SMTPUTF8 扩展(RFC 6531)。

版本 3.9 中的变化: 如果将 timeout 参数设置为零,则会引发 ValueError 以防止创建非阻塞套接字。

class smtplib.SMTP_SSL(host='', port=0, local_hostname=None, *, [timeout, ]context=None, source_address=None)

SMTP_SSL 实例的行为与 SMTP 的实例完全相同。SMTP_SSL 应该用于从连接开始就需要 SSL 并且使用 starttls() 不合适的情况。如果未指定 host,则使用本地主机。如果 port 为零,则使用标准的 SMTP-over-SSL 端口 (465)。可选参数 local_hostnametimeoutsource_address 的含义与它们在 SMTP 类中的含义相同。context 也是可选的,可以包含一个 SSLContext,并允许配置安全连接的各个方面。有关最佳实践,请阅读 安全注意事项

版本 3.3 中的变化: 添加了 context

版本 3.3 中的变化: 添加了 source_address 参数。

版本 3.4 中的变化: 该类现在支持使用 ssl.SSLContext.check_hostname服务器名称指示 进行主机名检查(请参阅 ssl.HAS_SNI)。

版本 3.9 中的变化: 如果将 timeout 参数设置为零,则会引发 ValueError 以防止创建非阻塞套接字。

版本 3.12 中的变化: 已删除已弃用的 keyfilecertfile 参数。

class smtplib.LMTP(host='', port=LMTP_PORT, local_hostname=None, source_address=None[, timeout])

LMTP 协议与 ESMTP 非常相似,它很大程度上基于标准 SMTP 客户端。LMTP 通常使用 Unix 套接字,因此我们的 connect() 方法必须支持该协议以及常规的 host:port 服务器。可选参数 *local_hostname* 和 *source_address* 的含义与在 SMTP 类中的含义相同。要指定 Unix 套接字,您必须为 *host* 使用以 '/' 开头的绝对路径。

支持身份验证,使用常规 SMTP 机制。使用 Unix 套接字时,LMTP 通常不支持或不需要任何身份验证,但您的情况可能会有所不同。

版本 3.9 中的变化: 添加了可选的 *timeout* 参数。

还定义了一系列不错的异常

exception smtplib.SMTPException

OSError 的子类,它是此模块提供的所有其他异常的基类。

版本 3.4 中的变化: SMTPException 成为 OSError 的子类

exception smtplib.SMTPServerDisconnected

当服务器意外断开连接时,或者在尝试使用 SMTP 实例连接到服务器之前,会引发此异常。

exception smtplib.SMTPResponseException

所有包含 SMTP 错误代码的异常的基类。在某些情况下,当 SMTP 服务器返回错误代码时,会生成这些异常。错误代码存储在错误的 smtp_code 属性中,并且 smtp_error 属性设置为错误消息。

exception smtplib.SMTPSenderRefused

发件人地址被拒绝。除了在所有 SMTPResponseException 异常上设置的属性之外,这还会将“sender”设置为 SMTP 服务器拒绝的字符串。

exception smtplib.SMTPRecipientsRefused

所有收件人地址都被拒绝。可以通过属性 recipients 访问每个收件人的错误,该属性是一个与 SMTP.sendmail() 返回的完全相同的字典。

exception smtplib.SMTPDataError

SMTP 服务器拒绝接受消息数据。

exception smtplib.SMTPConnectError

在与服务器建立连接期间发生错误。

exception smtplib.SMTPHeloError

服务器拒绝了我们的 HELO 消息。

exception smtplib.SMTPNotSupportedError

服务器不支持尝试的命令或选项。

3.5 版新增。

exception smtplib.SMTPAuthenticationError

SMTP 身份验证出错。最可能是服务器不接受提供的用户名/密码组合。

另请参阅

RFC 821 - 简单邮件传输协议

SMTP 的协议定义。本文档涵盖了 SMTP 的模型、操作过程和协议细节。

RFC 1869 - SMTP 服务扩展

SMTP 的 ESMTP 扩展的定义。这描述了一个使用新命令扩展 SMTP 的框架,支持动态发现服务器提供的命令,并定义了一些附加命令。

SMTP 对象

SMTP 实例具有以下方法

SMTP.set_debuglevel(level)

设置调试输出级别。*level* 的值为 1 或 True 会导致连接的调试消息以及发送到服务器和从服务器接收的所有消息。*level* 的值为 2 会导致这些消息被添加时间戳。

版本 3.5 中的变化: 添加了调试级别 2。

SMTP.docmd(cmd, args='')

向服务器发送命令 *cmd*。可选参数 *args* 只需连接到命令,并以空格分隔。

这将返回一个由数字响应代码和实际响应行组成的 2 元组(多行响应合并为一行。)

在正常操作中,应该不需要显式调用此方法。它用于实现其他方法,并且可能对测试私有扩展很有用。

如果在等待回复时与服务器的连接断开,则会引发 SMTPServerDisconnected

SMTP.connect(host='localhost', port=0)

连接到给定端口上的主机。默认情况下,连接到标准 SMTP 端口 (25) 上的本地主机。如果主机名以冒号 (':') 后跟一个数字结尾,则该后缀将被删除,并且该数字将被解释为要使用的端口号。如果在实例化期间指定了主机,则构造函数会自动调用此方法。返回一个 2 元组,其中包含服务器在其连接响应中发送的响应代码和消息。

引发带有参数 selfhostport审计事件 smtplib.connect

SMTP.helo(name='')

使用 HELO 向 SMTP 服务器标识自己。主机名参数默认为本地主机的完全限定域名。服务器返回的消息存储为对象的 helo_resp 属性。

在正常操作中,应该不需要显式调用此方法。必要时,sendmail() 会隐式调用它。

SMTP.ehlo(name='')

使用 EHLO 向 ESMTP 服务器标识自己。主机名参数默认为本地主机的完全限定域名。检查响应中的 ESMTP 选项并存储它们以供 has_extn() 使用。还设置几个信息属性:服务器返回的消息存储为 ehlo_resp 属性,does_esmtp 根据服务器是否支持 ESMTP 设置为 TrueFalse,并且 esmtp_features 将是一个字典,其中包含此服务器支持的 SMTP 服务扩展的名称及其参数(如果有)。

除非您希望在发送邮件之前使用 has_extn(),否则应该不需要显式调用此方法。必要时,sendmail() 会隐式调用它。

SMTP.ehlo_or_helo_if_needed()

如果此会话之前没有 EHLOHELO 命令,则此方法会调用 ehlo() 和/或 helo()。它首先尝试 ESMTP EHLO

SMTPHeloError

服务器没有正确回复 HELO 问候语。

SMTP.has_extn(name)

如果 name 在服务器返回的 SMTP 服务扩展集中,则返回 True,否则返回 False。忽略大小写。

SMTP.verify(address)

使用 SMTP VRFY 检查此服务器上地址的有效性。如果用户地址有效,则返回一个元组,该元组由代码 250 和完整的 RFC 822 地址(包括人名)组成。否则,返回 400 或更大的 SMTP 错误代码和错误字符串。

注意

许多站点禁用 SMTP VRFY 以阻止垃圾邮件发送者。

SMTP.login(user, password, *, initial_response_ok=True)

登录到需要身份验证的 SMTP 服务器。参数是用于身份验证的用户名和密码。如果此会话之前没有 EHLOHELO 命令,则此方法首先尝试 ESMTP EHLO。如果身份验证成功,此方法将正常返回,或者可能引发以下异常

SMTPHeloError

服务器没有正确回复 HELO 问候语。

SMTPAuthenticationError

服务器不接受用户名/密码组合。

SMTPNotSupportedError

服务器不支持 AUTH 命令。

SMTPException

找不到合适的身份验证方法。

smtplib 支持的每种身份验证方法如果被通告为服务器支持,则会依次尝试。有关支持的身份验证方法列表,请参阅 auth()initial_response_ok 会传递给 auth()

可选关键字参数 initial_response_ok 指定对于支持它的身份验证方法,是否可以按照 RFC 4954 中的规定发送“初始响应”以及 AUTH 命令,而不是需要质询/响应。

在 3.5 版更改: 可能会引发 SMTPNotSupportedError,并添加了 initial_response_ok 参数。

SMTP.auth(mechanism, authobject, *, initial_response_ok=True)

为指定的身份验证 mechanism 发出 SMTP AUTH 命令,并通过 authobject 处理质询响应。

mechanism 指定了要用作 AUTH 命令参数的身份验证机制;有效值为 esmtp_featuresauth 元素中列出的那些值。

authobject 必须是一个可调用对象,它接受一个可选的单个参数

data = authobject(challenge=None)

如果可选关键字参数 initial_response_ok 为 true,则将首先调用不带参数的 authobject()。它可以返回 RFC 4954 “初始响应” ASCII str,该字符串将按照以下方式进行编码并与 AUTH 命令一起发送。如果 authobject() 不支持初始响应(例如,因为它需要质询),则当使用 challenge=None 调用时,它应该返回 None。如果 initial_response_ok 为 false,则不会首先使用 None 调用 authobject()

如果初始响应检查返回 None,或者 initial_response_ok 为 false,则将调用 authobject() 来处理服务器的质询响应;传递给它的 challenge 参数将是一个 bytes。它应该返回将进行 base64 编码并发送到服务器的 ASCII str data

SMTP 类为 CRAM-MD5PLAINLOGIN 机制提供了 authobjects;它们分别命名为 SMTP.auth_cram_md5SMTP.auth_plainSMTP.auth_login。它们都需要将 SMTP 实例的 userpassword 属性设置为适当的值。

用户代码通常不需要直接调用 auth,而是可以调用 login() 方法,该方法将依次尝试上述每种机制,顺序如上所列。auth 的公开是为了方便实现 smtplib 不支持(或尚不支持)的身份验证方法。

3.5 版新增。

SMTP.starttls(*, context=None)

将 SMTP 连接置于 TLS(传输层安全)模式。之后的所有 SMTP 命令都将被加密。然后,您应该再次调用 ehlo()

如果提供了 keyfilecertfile,则使用它们来创建 ssl.SSLContext

可选的 context 参数是一个 ssl.SSLContext 对象;这是使用密钥文件和证书文件的替代方法,如果指定了该参数,则 keyfilecertfile 都应该是 None

如果此会话之前没有 EHLOHELO 命令,则此方法首先尝试 ESMTP EHLO

版本 3.12 中的变化: 已删除已弃用的 keyfilecertfile 参数。

SMTPHeloError

服务器没有正确回复 HELO 问候语。

SMTPNotSupportedError

服务器不支持 STARTTLS 扩展。

RuntimeError

您的 Python 解释器不支持 SSL/TLS。

版本 3.3 中的变化: 添加了 context

在 3.4 版更改: 该方法现在支持使用 SSLContext.check_hostname 和 *服务器名称指示* 进行主机名检查(请参阅 HAS_SNI)。

在 3.5 版更改: 现在,由于缺少 STARTTLS 支持而引发的错误是 SMTPNotSupportedError 子类,而不是基本 SMTPException

SMTP.sendmail(from_addr, to_addrs, msg, mail_options=(), rcpt_options=())

发送邮件。必需的参数是 RFC 822 发件人地址字符串、RFC 822 收件人地址字符串列表(一个裸字符串将被视为包含 1 个地址的列表)和一个消息字符串。调用者可以传递一个 ESMTP 选项列表(例如 8bitmime),以便在 MAIL FROM 命令中用作 mail_options。应该与所有 RCPT 命令一起使用的 ESMTP 选项(例如 DSN 命令)可以作为 rcpt_options 传递。(如果需要对不同的收件人使用不同的 ESMTP 选项,则必须使用低级方法(例如 mail()rcpt()data())来发送消息。)

注意

from_addrto_addrs 参数用于构造传输代理使用的消息信封。sendmail 不会以任何方式修改消息头。

msg 可以是包含 ASCII 范围内字符的字符串,也可以是字节字符串。使用 ascii 编解码器将字符串编码为字节,并将单独的 \r\n 字符转换为 \r\n 字符。字节字符串不会被修改。

如果此会话之前没有 EHLOHELO 命令,则此方法首先尝试 ESMTP EHLO。如果服务器执行 ESMTP,则消息大小和每个指定的选项都将传递给它(如果该选项在服务器公布的功能集中)。如果 EHLO 失败,则将尝试 HELO 并禁止 ESMTP 选项。

如果邮件至少被一个收件人接受,此方法将正常返回。否则,它将引发异常。也就是说,如果此方法没有引发异常,那么应该有人会收到您的邮件。如果此方法没有引发异常,它将返回一个字典,其中包含每个被拒绝的收件人的一个条目。每个条目都包含一个由 SMTP 错误代码和服务器发送的相应错误消息组成的元组。

如果 *mail_options* 中包含 SMTPUTF8,并且服务器支持它,则 *from_addr* 和 *to_addrs* 可能包含非 ASCII 字符。

此方法可能会引发以下异常

SMTPRecipientsRefused

所有收件人均被拒绝。没有人收到邮件。异常对象的 recipients 属性是一个包含被拒绝收件人信息的字典(类似于至少有一个收件人被接受时返回的字典)。

SMTPHeloError

服务器没有正确回复 HELO 问候语。

SMTPSenderRefused

服务器不接受 *from_addr*。

SMTPDataError

服务器返回了意外的错误代码(而不是拒绝收件人)。

SMTPNotSupportedError

在 *mail_options* 中给出了 SMTPUTF8,但服务器不支持。

除非另有说明,否则即使在引发异常后连接也将保持打开状态。

版本 3.2 中的变化: *msg* 可以是字节字符串。

版本 3.5 中的变化: 添加了 SMTPUTF8 支持,如果指定了 SMTPUTF8 但服务器不支持,则可能会引发 SMTPNotSupportedError

SMTP.send_message(msg, from_addr=None, to_addrs=None, mail_options=(), rcpt_options=())

这是一个方便的方法,用于在消息由 email.message.Message 对象表示时调用 sendmail()。参数的含义与 sendmail() 相同,只是 *msg* 是一个 Message 对象。

如果 *from_addr* 是 None 或 *to_addrs* 是 None,则 send_message 会根据 RFC 5322 中的规定,使用从 *msg* 的标头中提取的地址填充这些参数:如果存在 *Sender* 字段,则将 *from_addr* 设置为该字段,否则设置为 *From* 字段。 *to_addrs* 组合了 *msg* 中 *To*、*Cc* 和 *Bcc* 字段的值(如果有)。如果消息中只出现了一组 *Resent-* 标头,则忽略常规标头,而使用 *Resent-* 标头。如果消息包含多组 *Resent-* 标头,则会引发 ValueError,因为无法明确检测到最新的一组 *Resent-* 标头。

send_message 使用 BytesGenerator 将 *msg* 序列化,其中 *linesep* 为 \r\n,并调用 sendmail() 来传输生成的邮件。无论 *from_addr* 和 *to_addrs* 的值是什么,send_message 都不会传输 *msg* 中可能出现的任何 *Bcc* 或 *Resent-Bcc* 标头。如果 *from_addr* 和 *to_addrs* 中的任何地址包含非 ASCII 字符,并且服务器未声明支持 SMTPUTF8,则会引发 SMTPNotSupported 错误。否则,将使用其 policy 的克隆来序列化 Message,其中 utf8 属性设置为 True,并将 SMTPUTF8BODY=8BITMIME 添加到 *mail_options* 中。

3.2 版新增。

版本 3.5 中的新增功能: 支持国际化地址(SMTPUTF8)。

SMTP.quit()

终止 SMTP 会话并关闭连接。返回 SMTP QUIT 命令的结果。

还支持与标准 SMTP/ESMTP 命令 HELPRSETNOOPMAILRCPTDATA 对应的低级方法。通常不需要直接调用这些方法,因此这里不作介绍。有关详细信息,请参阅模块代码。

SMTP 示例

此示例提示用户输入邮件信封中所需的地址(“收件人”和“发件人”地址)以及要传递的邮件。请注意,要包含在邮件中的标头必须包含在输入的邮件中;此示例不执行对 RFC 822 标头的任何处理。特别是,“收件人”和“发件人”地址必须明确包含在邮件标头中

import smtplib

def prompt(title):
    return input(title).strip()

from_addr = prompt("From: ")
to_addrs  = prompt("To: ").split()
print("Enter message, end with ^D (Unix) or ^Z (Windows):")

# Add the From: and To: headers at the start!
lines = [f"From: {from_addr}", f"To: {', '.join(to_addrs)}", ""]
while True:
    try:
        line = input()
    except EOFError:
        break
    else:
        lines.append(line)

msg = "\r\n".join(lines)
print("Message length is", len(msg))

server = smtplib.SMTP("localhost")
server.set_debuglevel(1)
server.sendmail(from_addr, to_addrs, msg)
server.quit()

注意

通常,您需要使用 email 包的功能来构建电子邮件,然后可以通过 send_message() 发送;请参阅 电子邮件:示例