资讯动态

CPython smtplib 模块全解析:SMTP/ESMTP 客户端会话与邮件发送实战

发布时间:2026/9/8 16:19:18 来源:尧图企业网站定制
CPython smtplib 模块全解析SMTP/ESMTP 客户端会话与邮件发送实战【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpythonLib/smtplib.py文档位于 Doc/library/smtplib.rst实现了完整的 SMTP/ESMTP 客户端会话对象用于向任何运行 SMTP 或 ESMTP 守护进程的互联网主机发送邮件。它覆盖了从最基础的“连接—发信—退出”三件套到SMTP_SSL隐式 TLS、STARTTLS显式升级、AUTH多机制认证、SMTPUTF8国际化地址以及 LMTP 本地投递等完整能力。读完本文你将掌握SMTP、SMTP_SSL、LMTP三类客户端对象的全部构造参数与核心方法理解其异常体系与源码级实现细节并能直接写出可运行的邮件发送代码。模块定位纯标准库的 SMTP 协议客户端smtplib定义了一个 SMTP 客户端会话对象可以向任意提供 SMTP 或 ESMTP 监听服务的互联网主机发送邮件。协议行为遵循RFC 821Simple Mail Transfer Protocol定义 SMTP 的模型、操作流程与协议细节RFC 1869SMTP Service Extensions定义 ESMTP 扩展框架、服务端能力的动态发现EHLO并规范了一批扩展命令。该模块依赖 sockets。仓库文档通过 Doc/includes/wasm-notavail.rst 明确标注模块在 WebAssemblyWASI平台上不可用或不工作因此涉及 WASM 部署环境时需要留意这一约束。从源码结构看模块只依赖标准库中的socket、io、re、email.*、base64、hmac等模块顶层导出了异常类、quoteaddr/quotedata工具函数与SMTP等名称Lib/smtplib.py#L55-L58。三个客户端类SMTP、SMTP_SSL 与 LMTPSMTP通用 SMTP/ESMTP 会话class smtplib.SMTP(host, port0, local_hostnameNone, timeout..., source_addressNone)一个SMTP实例封装一条到服务器的连接其方法覆盖完整的 SMTP 与 ESMTP 操作集。要点如下参数含义默认/说明host服务器主机名空串时不自动连接需手动调用connect()为真值时构造时自动调用connect(host, port)port服务器端口0表示使用类属性default_portSMTP 为 25local_hostnameHELO/EHLO 中本地主机的 FQDN缺省时通过socket.getfqdn()计算若无 FQDN不含点号源码会退化为形如[127.0.0.1]的域字面量Lib/smtplib.py#L265-L281timeout阻塞操作如连接尝试的超时秒数缺省使用全局默认超时设置超时抛TimeoutError设为 0 会抛ValueError防止创建非阻塞 socket自 3.9 起source_address绑定源地址形如(host, port)的二元组供多网卡主机选择出口 IP/端口省略或传入/0时采用 OS 默认行为自 3.3 起正常使用只需三组方法初始化/连接、sendmail与quit。SMTP还支持with语句自 3.3 起退出代码块时自动发出QUIT from smtplib import SMTP with SMTP(domain.org) as smtp: ... smtp.noop() ... (250, bOk)对应源码中__enter__/__exit__的实现Lib/smtplib.py#L283-L294退出时执行docmd(QUIT)若响应码不是 221 则抛SMTPResponseException若服务器已经断开则静默忽略最后一律关闭 socket。SMTP_SSL从连接开始就加密的隐式 TLSclass smtplib.SMTP_SSL(host, port0, local_hostnameNone, *, timeout..., contextNone, source_addressNone)SMTP_SSL行为与SMTP完全一致但适用于从连接建立开始就需要 SSL、不适合用starttls()事后升级的场景典型如 465 端口。其default_port为 465。local_hostname、timeout、source_address语义与SMTP相同context自 3.3 起接收一个ssl.SSLContext用于配置安全连接的各项参数SSL 最佳实践可参考 Doc/library/ssl.rst 的安全章节自 3.4 起支持基于ssl.SSLContext.check_hostname的主机名校验与 SNIServer Name Indication自 3.12 起已移除废弃的keyfile/certfile参数。从源码看SMTP_SSL复用了SMTP.connect的连接逻辑只在_get_socket中用context.wrap_socket(new_socket, server_hostnameself._host)包一层 TLSLib/smtplib.py#L1037-L1043未提供context时会先创建标准库默认上下文ssl._create_stdlib_context()。LMTP面向本地投递的协议变体class smtplib.LMTP(host, portLMTP_PORT, local_hostnameNone, source_addressNone, timeout...)LMTPLocal Mail Transfer Protocol与 ESMTP 非常相似常用于本地邮件投递如投递给 dovecot-lda 之类的投递代理因此经常使用Unix socket。两种用法走 TCP与普通 SMTP 相同走 Unix sockethost传入以/开头的绝对路径源码中LMTP.connect检测到首字符为/即改用socket.AF_UNIX建立连接Lib/smtplib.py#L1074-L1099。LMTP同样支持标准 SMTP 认证机制通过 Unix socket 时通常不需要认证但视服务器配置而定。细节握手命令为lhloehlo_msg lhlo见 Lib/smtplib.py#L1066端口常量LMTP_PORT 2003Lib/smtplib.py#L1050timeout参数自 3.9 起支持同样禁止传入 0。一套完整的异常体系模块从SMTPException自 3.4 起为OSError子类派生出全套异常便于精确区分失败环节异常语义附加属性/说明SMTPException所有 smtplib 异常的基类继承OSErrorSMTPServerDisconnected服务器意外断开或连接前就使用实例—SMTPResponseException携带 SMTP 错误码的异常基类smtp_code错误码、smtp_error错误消息SMTPSenderRefused发件地址被拒额外含sender被拒的发件地址字符串SMTPRecipientsRefused全部收件人被拒recipients与sendmail()返回值同构的字典逐收件人记录错误SMTPDataError服务器拒绝消息数据—SMTPConnectError与服务器建立连接出错—SMTPHeloError服务器拒绝HELO问候—SMTPNotSupportedError服务器不支持尝试的命令或选项自 3.5 起例如服务器不支持SMTPUTF8时SMTPAuthenticationError认证失败通常是用户名/密码组合不被接受源码定义见 Lib/smtplib.py#L69-L142。其中SMTPResponseException在__init__中把(code, msg)同时写入smtp_code/smtp_error与args方便解包与日志输出。SMTP 对象方法全解连接与会话管理set_debuglevel(level)设置调试输出级别。1或True打印连接过程及收发双方全部消息2额外为每条消息加上时间戳自 3.5 起。源码中_print_debug在debuglevel 1时前缀datetime.datetime.now().time()后写入sys.stderrLib/smtplib.py#L305-L309。docmd(cmd, args)发送单条命令args以空格拼接在命令后。返回二元组数字响应码、拼接后的响应行多行响应会被合并。仅在调用私有扩展或测试时需要直接使用等待响应期间连接丢失会抛SMTPServerDisconnected。connect(hostlocalhost, port0)连接指定主机端口默认连本地 25 端口。若主机名以“: 数字”结尾会自动剥离该后缀并把数字解析为端口源码见 Lib/smtplib.py#L336-L347host.find(:) host.rfind(:)保证只有单个冒号时才解析避免破坏 IPv6 地址。port保持 0 时使用default_port。构造时指定host会触发自动调用。该方法还会触发审计事件smtplib.connect。helo(name)用HELO向服务器表明身份默认主机名为本地 FQDN服务器应答存入helo_resp属性。正常情况下无需显式调用sendmail需要时会隐式调用。ehlo(name)用EHLO表明身份并解析 ESMTP 能力。服务器应答存入ehlo_respdoes_esmtp被置为True/False表示是否支持 ESMTPesmtp_features字典记录服务器通告的扩展名及参数若有。源码解析逻辑见 Lib/smtplib.py#L452-L501除标准EHLO关键字行外还兼容旧式auth...通告写法。ehlo_or_helo_if_needed()若本会话尚无EHLO/HELO先尝试 ESMTPEHLO失败则回退HELO。问候应答非法时抛SMTPHeloError。has_extn(name)判断name是否在服务器通告的扩展集合中大小写不敏感。mail()发送SMTPUTF8、starttls()升级 TLS 前都用它探测能力。verify(address)/vrfy(address)用VRFY命令校验地址。地址有效时返回(250, 完整 RFC 822 地址)否则返回 400 及以上错误码与错误串。注意许多站点为防垃圾邮件禁用VRFY。quit()结束会话并关闭连接返回QUIT命令结果。源码中同时复位ehlo_resp/helo_resp/esmtp_features/does_esmtp以支持重新connect()后再次握手Lib/smtplib.py#L1003-L1011。认证login与底层authlogin(user, password, *, initial_response_okTrue)完成认证登录若本会话尚无握手先执行EHLO必要时回退HELO若服务器未通告AUTH扩展抛SMTPNotSupportedError在服务器通告的机制中按客户端偏好顺序[CRAM-MD5, PLAIN, LOGIN]无 CRAM-MD5 支持时剔除前者见 Lib/smtplib.py#L733-L740逐个尝试全部失败时抛出最后一次的SMTPAuthenticationError。可能的异常SMTPHeloError问候应答错误、SMTPAuthenticationError用户名/密码不被接受、SMTPNotSupportedError服务器不支持AUTH、SMTPException无合适认证方法。auth(mechanism, authobject, *, initial_response_okTrue)是更底层的钩子用于实现 smtplib 尚未内置的认证机制。authobject需为可调用对象签名形如data authobject(challengeNone)若initial_response_ok为真先以无参调用authobject()返回的“初始响应”字符串按 RFC 4954 与AUTH命令一同发出若返回None或initial_response_ok为假则由服务器挑战驱动challenge以bytes传入回调返回 ASCIIstr数据经 base64 编码后发回服务器。类内置的三个 authobject 为auth_cram_md5、auth_plain、auth_login分别对应CRAM-MD5、PLAIN、LOGIN依赖实例的user/password属性。源码同时用_MAXCHALLENGE 5限制挑战轮数防止服务器让认证陷入无限循环Lib/smtplib.py#L655-L666。加密升级starttls(contextNone)starttls()把连接切入 TLS 模式后续命令全部加密随后应再次ehlo()。行为要点无前置握手时先EHLO服务器未通告starttls扩展时抛SMTPNotSupportedError3.5 起为子类而非基类context为ssl.SSLContext自 3.3 起不传则用标准库默认上下文自 3.12 起已移除keyfile/certfile解释器未编译 SSL 支持时抛RuntimeError成功响应 220后按 RFC 3207 要求丢弃升级前获得的全部服务端知识helo_resp、ehlo_resp、esmtp_features清空does_esmtp置回FalseLib/smtplib.py#L794-L801非 220 响应抛SMTPResponseException自 3.4 起支持主机名校验与 SNI。发信核心sendmailsendmail(from_addr, to_addrs, msg, mail_options(), rcpt_options())完整执行一次邮件事务参数含义参数说明from_addrRFC 822 发件地址字符串to_addrsRFC 822 收件地址字符串列表传裸字符串等价于单元素列表msg消息内容。字符串须为 ASCII按 ascii 编解码器编码并把孤立的\r/\n归一化为\r\nbytes 则原样传输、不加修改mail_options用于MAIL FROM命令的 ESMTP 选项如8bitmimercpt_options用于所有RCPT命令的 ESMTP 选项如 DSN 命令选项须为完整文本如NOTIFYSUCCESS,FAILURE需要对不同收件人使用不同选项时须改用mail/rcpt/data等底层方法完整流程对应 Lib/smtplib.py#L872-L911先隐式EHLO若服务器支持 ESMTP 则自动附加sizelen(msg)并携带各选项EHLO失败则回退HELO并抑制 ESMTP 选项依次执行mail、逐收件人rcpt、data。判断规则至少一个收件人被接受即正常返回返回值为被拒收件人字典{收件人: (错误码, 错误消息)}全部接受时为空字典全部收件人被拒抛SMTPRecipientsRefused发件地址被拒抛SMTPSenderRefusedDATA响应异常抛SMTPDataErrorSMTPUTF8传入mail_options但服务器不支持时抛SMTPNotSupportedError收到 421 时连接会被关闭其他异常下连接通常保持打开便于调用方RSET后继续使用。注意from_addr/to_addrs只用于构造传输信封envelopesendmail不修改消息头。若SMTPUTF8出现在mail_options且服务器支持from_addr/to_addrs可含非 ASCII 字符。源码中的消息编码/点号转义实现在data()Lib/smtplib.py#L563-L590字符串先归一化行尾再 ascii 编码对行首.做 RFC 821 点填充_quote_periods并确保以\r\n.\r\n结尾。面向email的便捷方法send_messagesend_message(msg, from_addrNone, to_addrsNone, mail_options(), rcpt_options())send_message是sendmail针对email.message.Message对象的便捷封装收/发件地址缺省时按 RFC 5322 从消息头提取from_addr优先取Sender字段否则取Fromto_addrs合并To、Cc、Bcc三字段若消息含恰好一组Resent-*头则改用它们含多组Resent-*头时抛ValueError无法判定哪组最新序列化使用email.generator.BytesGenerator、行尾\r\n且绝不传输消息中可能存在的Bcc或Resent-Bcc头先copy.copy再删除见 Lib/smtplib.py#L964-L967若收/发件地址含非 ASCII服务器未通告SMTPUTF8则抛SMTPNotSupportedError否则以utf8True克隆消息策略重新序列化并自动在mail_options中加入SMTPUTF8与BODY8BITMIME。实例属性与低层命令helo_resp最近一次HELO的服务器应答ehlo_resp最近一次EHLO的服务器应答通常是多行does_esmtp布尔值指示服务器是否支持 ESMTP执行ehlo后生效esmtp_features服务器通告的 SMTP 服务扩展名与参数字典键一律小写。此外对应标准命令HELP、RSET、NOOP、MAIL、RCPT、DATA的底层方法help()、rset()、noop()、mail()、rcpt()、data()也已实现通常无需直接调用详细实现可查阅 Lib/smtplib.py#L507-L590。审计事件smtplib参与 CPython 的审计钩子机制敏感网络操作均可被sys.addaudithook观察smtplib.SMTP.send所有命令在发送前触发参数为self与即将发出的databytes见构造器文档中事件声明smtplib.connectconnect()触发参数为self、host、port源码调用点 Lib/smtplib.py#L347。对安全要求高的运行环境可通过审计事件感知“谁在何时向哪个 SMTP 主机发起连接、发送了哪些命令字节”事件总表见 Doc/library/audit_events.rst。实战示例基础交互式示例沿用文档示例逻辑交互输入信封地址与正文注意发件/收件地址必须显式写入消息头再建立连接发送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):) # 先把 From: 与 To: 头加在消息开头 lines [fFrom: {from_addr}, fTo: {, .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()set_debuglevel(1)会把与服务器交互的每个命令与响应打印到 stderr便于联调。用email构造并发送推荐一般实践是用email包构造消息对象再交给send_message发送这样消息头、MIME 结构与多收件人地址都由标准库统一处理。更多示例见 Doc/library/email.examples.rst源码中该模块对应的Message/BytesGenerator配合逻辑可对照 Lib/smtplib.py#L913-L988。一个简洁框架如下import smtplib from email.message import EmailMessage msg EmailMessage() msg[From] senderexample.com msg[To] aexample.com, bexample.com msg[Subject] test msg.set_content(hello from smtplib) with smtplib.SMTP(localhost) as server: # 退出时自动 QUIT server.send_message(msg) # 地址自动从消息头提取加密连接的典型姿势需要全程加密的端口465使用SMTP_SSL普通端口25/587则先SMTP再starttls()import smtplib, ssl # 方式一隐式 TLS with smtplib.SMTP_SSL(mail.example.com, 465, timeout10, contextssl.create_default_context()) as server: server.login(user, password) server.send_message(msg) # 方式二显式 STARTTLS升级后务必重新 EHLO server smtplib.SMTP(mail.example.com, 587, timeout10) server.starttls(contextssl.create_default_context()) server.ehlo() server.login(user, password) server.send_message(msg) server.quit()需要说明create_default_context()会启用证书与主机名校验若服务器证书不可信应修复证书信任链而非关闭校验。login内部会按CRAM-MD5/PLAIN/LOGIN的偏好顺序自动选择服务器支持的机制。仓库中的测试验证标准库自带 Lib/test/test_smtplib.py基于模拟 socket 与本地测试服务器线程可用作行为契约其中若干用例直接印证上文要点testTimeoutZero/LMTP 的 testTimeoutZerotimeout0抛ValueErrorLib/test/test_smtplib.py#L130-L133test_debuglevel与test_debuglevel_2分别断言 stderr 出现connect:前缀与HH:MM:SS.ffffff时间戳前缀Lib/test/test_smtplib.py#L141-L160NonConnectingTests.testNotConnected未连接即ehlo()/send()抛SMTPServerDisconnectedLib/test/test_smtplib.py#L699-L709BadHELOServerTests握手响应码非 220 时构造抛SMTPConnectErrorLib/test/test_smtplib.py#L754-L770TooLongLineTests响应行超过_MAXLINE8192 字节抛SMTPResponseExceptionLib/test/test_smtplib.py#L800-L802test_quit_resets_greeting、test_with_statement与test_with_statement_QUIT_failure验证quit()复位握手状态、with语句自动QUIT及 QUIT 失败时的异常行为Lib/test/test_smtplib.py#L1253-L1287SMTPUTF8SimTests基于SimSMTPUTF8Server覆盖SMTPUTF8通告下sendmail/底层mail/send_message对非 ASCII 地址的处理以及服务器不支持时抛SMTPNotSupportedError的分支Lib/test/test_smtplib.py#L1403-L1536。版本演进一览3.2msg可传字节串新增send_message3.3SMTP支持with语句新增source_address、starttls的context、SMTP_SSL的context参数3.4SMTPException改为OSError子类SMTP_SSL与starttls支持主机名校验与 SNI3.5SMTPUTF8RFC 6531支持新增SMTPNotSupportedError与auth()set_debuglevel支持级别 2带时间戳login()新增initial_response_ok3.9timeout0抛ValueErrorLMTP新增timeout参数3.12移除废弃的keyfile/certfileSMTP_SSL与starttls。这些变更在 Doc/library/smtplib.rst 中均有对应versionchanged/versionadded记录可为升级项目提供迁移参考。【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价