smtplib — SMTP 协议客户端

源代码: Lib/smtplib.py


smtplib 模块定义了一个 SMTP 客户端会话对象,该对象可用于将邮件发送到任何具有 SMTP 或 ESMTP 监听守护进程的互联网机器。有关 SMTP 和 ESMTP 操作的详细信息,请参阅 RFC 821 (Simple Mail Transfer Protocol) 和 RFC 1869 (SMTP Service Extensions)。

可用性:非 WASI。

此模块在 WebAssembly 上不起作用或不可用。有关更多信息,请参阅 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 语句。这样使用时,当 with 语句退出时,会自动发出 SMTP QUIT 命令。例如:

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

所有命令都将引发一个 审计事件 `smtplib.SMTP.send`,其参数为 `self` 和 `data`,其中 `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(也是可选的)可以包含一个 ssl.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 客户端。通常使用 Unix 套接字进行 LMTP,因此我们的 connect() 方法必须同时支持 Unix 套接字和常规的 host:port 服务器。可选参数 local_hostnamesource_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 - Simple Mail Transfer Protocol

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

RFC 1869 - SMTP Service Extensions

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

SMTP 对象

SMTP 实例具有以下方法

SMTP.set_debuglevel(level)

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

版本 3.5 中已更改: 添加了调试级别 2。

SMTP.docmd(cmd, args='')

向服务器发送命令 cmd。可选参数 args 仅用空格将其与命令连接起来。

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

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

如果在等待回复时与服务器的连接丢失,将引发 SMTPServerDisconnected

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

连接到给定端口上的主机。默认值是连接到本地主机上的标准 SMTP 端口 (25)。如果主机名以冒号 (`:`) 加上数字结尾,则会剥离该后缀,并将该数字解释为要使用的端口号。如果在实例化时指定了主机,则构造函数会自动调用此方法。返回服务器在其连接响应中发送的响应代码和消息组成的 2 元组。

引发一个 审计事件 `smtplib.connect`,其参数为 `self`、`host` 和 `port`。

SMTP.helo(name='')

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

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

SMTP.ehlo(name='')

使用 EHLO 向 ESMTP 服务器标识自己。主机名参数默认为本地主机的完全限定域名。检查响应以获取 ESMTP 选项,并为 has_extn() 存储它们。此外,还设置了几个信息属性:服务器返回的消息存储在对象的 ehlo_resp 属性中,does_esmtp 设置为 TrueFalse(取决于服务器是否支持 ESMTP),并且 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)

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

mechanism 指定用作 AUTH 命令参数的身份验证机制;有效值是 esmtp_features 中 `auth` 元素列出的值。

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

data = authobject(challenge=None)

如果可选关键字参数 initial_response_ok 为 true,则会先用无参数调用 `authobject()`。它可以返回 RFC 4954 的“初始响应”ASCII str,它将作为下面的 AUTH 命令进行编码和发送。如果 `authobject()` 不支持初始响应(例如,因为它需要质询),则在调用时应返回 None(当 `challenge=None` 时)。如果 initial_response_ok 为 false,则不会先用 `None` 调用 `authobject()`。

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

SMTP 类为 `CRAM-MD5`、`PLAIN` 和 `LOGIN` 机制提供了 `authobjects`;它们分别命名为 `SMTP.auth_cram_md5`、`SMTP.auth_plain` 和 `SMTP.auth_login`。它们都需要将 `SMTP` 实例的 `user` 和 `password` 属性设置为适当的值。

用户代码通常不需要直接调用 `auth`,而是可以调用 `login()` 方法,该方法将按列出的顺序尝试上述每个机制。`auth` 是公开的,以便于实现 `smtplib` 未(或尚未)直接支持的身份验证方法的实现。

在 3.5 版本加入。

SMTP.starttls(*, context=None)

将 SMTP 连接置于 TLS (Transport Layer Security) 模式。之后的所有 SMTP 命令都将进行加密。之后应再次调用 ehlo()

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

可选的 context 参数是一个 ssl.SSLContext 对象;这是使用 keyfile 和 certfile 的替代方法,如果指定了它,则 keyfilecertfile 都应为 None

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

在 3.12 版更改: 已移除已弃用的 keyfilecertfile 参数。

SMTPHeloError

服务器对 HELO 问候语没有正确响应。

SMTPNotSupportedError

服务器不支持 STARTTLS 扩展。

RuntimeError

您的 Python 解释器没有可用的 SSL/TLS 支持。

版本 3.3 中已更改: 添加了 context 参数。

版本 3.4 中已更改: 该方法现在支持使用 ssl.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 个地址的列表)以及一个消息字符串。调用者可以为用于 `MAIL FROM` 命令的邮件信封传递一个 ESMTP 选项列表(例如 `8bitmime`),作为 mail_options。可作为 rcpt_options 传递给所有 `RCPT` 命令的 ESMTP 选项(例如 `DSN` 命令)。(如果需要对不同收件人使用不同的 ESMTP 选项,则必须使用低级方法,如 `mail()`、`rcpt()` 和 `data()` 来发送消息。)

备注

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

msg 可以是一个包含 ASCII 范围字符的字符串,或者是一个字节字符串。字符串使用 ascii 编码器编码为字节,并且单个 `\r` 和 `\n` 字符将被转换为 `\r\n` 字符。字节字符串不会被修改。

如果本会话中没有先前的 EHLOHELO 命令,则此方法会先尝试 ESMTP EHLO。如果服务器支持 ESMTP,则消息大小和指定的每个选项都将传递给它(如果该选项在服务器声明的功能集中)。如果 `EHLO` 失败,则会尝试 `HELO` 并抑制 ESMTP 选项。

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

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

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

SMTPRecipientsRefused

所有收件人都被拒绝。没有人收到邮件。

SMTPHeloError

服务器对 HELO 问候语没有正确响应。

SMTPSenderRefused

服务器未接受 from_addr

SMTPDataError

服务器返回了意外错误代码(除了拒绝收件人之外)。

SMTPNotSupportedError

`SMTPUTF8` 已在 mail_options 中给出,但服务器不支持。

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

版本 3.2 中已更改: msg 可以是字节字符串。

版本 3.5 中已更改: 添加了 `SMTPUTF8` 支持,并且如果指定了 `SMTPUTF8` 但服务器不支持,则可能引发 SMTPNotSupportedError

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

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

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

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

在 3.2 版本加入。

版本 3.5 中已添加: 支持国际化地址(`SMTPUTF8`)。

SMTP.quit()

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

还支持与标准 SMTP/ESMTP 命令 `HELP`、`RSET`、`NOOP`、`MAIL`、`RCPT` 和 `DATA` 对应的低级方法。通常不需要直接调用它们,因此此处不予记录。有关详细信息,请参阅模块代码。

此外,SMTP 实例具有以下属性

SMTP.helo_resp

HELO 命令的响应,参见 helo()

SMTP.ehlo_resp

EHLO 命令的响应,参见 ehlo()

SMTP.does_esmtp

一个布尔值,指示服务器是否支持 ESMTP,参见 ehlo()

SMTP.esmtp_features

一个字典,包含服务器支持的 SMTP 服务扩展名称,参见 ehlo()

SMTP 示例

此示例提示用户输入邮件信封所需的地址(“To”和“From”地址)以及要传递的消息。请注意,要包含在消息中的头部必须作为输入包含在消息中;此示例不处理 RFC 822 头部。特别是,“To”和“From”地址必须在消息头部中显式包含

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()` 进行发送;参见 email: 示例