news 2026/11/4 8:43:10

Python3 SMTP发送邮件(以163邮箱为例)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python3 SMTP发送邮件(以163邮箱为例)

大家平时在邮箱网页里填写收件人、主题和正文,然后点击发送。今天我们用 Python 完成相同的事情。网页里的“发送”按钮背后,也需要把邮件交给邮件服务器。Python 程序相当于我们自己编写的一个简易邮件客户端。

邮件中的概念寄信类比示例
邮件正文信纸上的内容“你好,这是课堂实验邮件”
邮件头信件上的信息发件人、收件人、主题
邮件地址寄件人或收件人地址你的账号@163.com
SMTP 服务器接收寄件的邮局smtp.163.com
端口邮局办理某项业务的窗口465
身份认证证明自己有权使用这个寄件账号邮箱地址和授权码
SMTP寄送邮件时共同遵守的规则简单邮件传输协议

Python 程序
│
│ 登录并提交邮件
▼
163 的 SMTP 服务器
│
│ 转交邮件
▼
收件方邮件服务器,例如 QQ 邮箱服务器
│
▼
收件人通过网页或客户端查看邮件

问:使用 163 邮箱给 QQ 邮箱发送邮件,Python 应该连接 163 还是 QQ 的 SMTP 服务器?

答:连接发件方的 163 SMTP 服务器。收件人的邮箱可以是 QQ、Outlook 或其他邮箱,跨邮箱投递由邮件服务器处理。

注:

  • SMTP 负责发送邮件。
  • POP3、IMAP 主要用于读取或同步邮箱邮件。
  • 本节课实现发送功能,不需要编写收信程序。

使用python发送邮件,程序只需要做两件事:1:把邮件写好。2:把邮件交给服务器发送。

python发送邮件需要的库

工具负责什么
email包构造邮件内容和邮件头
smtplib模块连接 SMTP 服务器、登录、发送
ssl模块配置加密连接
getpass模块在终端输入授权码时隐藏输入内容

这些都是python标准库,不必执行pip install 。本节课使用MIMEText。Python 也提供了较新的EmailMessage接口,后续课程介绍。

第一步:现在代码中生成一份邮件,不用连接服务器,也不用发送。

from email.mime.text import MIMEText from email.header import Header from email.utils import formataddr sender = "your_account@163.com" receiver = "receiver@example.com" message = MIMEText( "你好,这是我使用 Python 编写的第一封邮件。", "plain", "utf-8" ) message["From"] = formataddr(("Python课堂", sender)) message["To"] = formataddr(("收件同学", receiver)) message["Subject"] = Header("Python邮件发送实验", "utf-8") print(message.as_string())

代码介绍:

1.MIMEText():构造文本邮件

常用形式:MIMEText(_text, _subtype="plain", _charset=None)

参数含义本课填写
_text正文内容,通常是字符串"你好……"
_subtype文本类型"plain"
_charset文本采用的字符编码"utf-8"
message = MIMEText("你好", "plain", "utf-8")

创建一份邮件正文,内容是“你好”,按纯文本处理,采用 UTF-8 编码。

邮件有两个常见类型

# 纯文本 MIMEText("你好,同学!", "plain", "utf-8") # HTML MIMEText("<h1>你好,同学!</h1>", "html", "utf-8")

plain会把正文当普通文字;html允许邮件客户端按 HTML 展示。

函数返回的是一个邮件对象,因此message并不是普通字符串。参数名开头的下划线属于库的命名。

2.message["From"]等:填写邮件头

message["From"] = ... message["To"] = ... message["Subject"] = ...
字段含义
From邮件中显示的发件人
To邮件中显示的收件人
Subject邮件主题

message像一张已经准备好的邮件表单,我们现在填写发件人、收件人和主题。这是一种类似字典的操作方式,但message本身是邮件对象。

3.formataddr():把昵称与邮箱地址组合起来

常用形式:

formataddr(pair, charset="utf-8")
参数含义
pair一个二元组,依次放显示名称和邮箱地址
charset显示名称使用的编码,默认 UTF-8
formataddr(("张老师", "your_account@163.com")) # 这里有两层括号: formataddr( ("张老师", "your_account@163.com") ) # └──────── 一个元组参数 ──────────┘

可以理解为:

昵称:张老师 地址:your_account@163.com

组合成符合邮件头格式的字符串。中文昵称可能被编码,邮箱客户端会负责解码显示。

4.Header():处理中文主题

Header(s, charset)
参数含义示例
s邮件头中的文字"Python邮件发送实验"
charset文字编码"utf-8"

例如:

message["Subject"] = Header("Python邮件发送实验", "utf-8")

正文有正文的编码,主题也需要正确处理中文。Header帮我们构造邮件头中的文字。

5.as_string():把邮件对象转换成可传输的文本

mail_text = message.as_string()

邮件对象---- as_string()------>包含邮件头、正文及编码信息的完整邮件字符串

观察print(message.as_string())的结果。看到编码后的字符时,这不一定是乱码,而可能是邮件传输格式;邮箱客户端会解码展示。

这个方法只负责转换格式,不会发送邮件。

二.连接、登录和发送

执行顺序:建立连接 → 验证身份 → 提交邮件 → 关闭连接

1.SMTP_SSL():建立加密连接

server = smtplib.SMTP_SSL( host="smtp.163.com", port=465, timeout=20, context=ssl.create_default_context() )
参数含义本课取值
host发件服务器地址"smtp.163.com"
port服务器端口,使用整数465
timeout阻塞网络操作的超时时间,单位秒20
contextTLS 配置对象ssl.create_default_context()
local_hostname向服务器介绍本机时使用的名称省略
source_address指定本机连接使用的地址和端口省略

timeout=20不是“20 秒后发送”,也不是整个程序必须在 20 秒内结束。Python 等待服务器响应的时候,如果连续 20 秒没有收到任何数据,就直接抛出超时异常,不再死等。

服务器地址不要写成:"https://smtp.163.com" 。这里不需要网页协议前缀

2.ssl.create_default_context():创建加密配置

返回一个 TLS 配置对象,用于采用合理的默认设置并验证服务器证书。

这个对象告诉 Python,建立连接时怎样加密,以及怎样确认连接的是可信服务器。

3.login():验证发件人身份

server.login(user, password)
参数本课填写
user完整的 163 邮箱地址
password163 邮箱生成的客户端授权码

4.sendmail():提交邮件

server.sendmail( from_addr, to_addrs, msg, mail_options=(), rcpt_options=() )
参数含义示例
from_addr实际投递使用的发件地址sender
to_addrs实际收件地址,课堂统一用列表[receiver]
msg完整邮件字符串或字节串message.as_string()
mail_options扩展发送选项省略
rcpt_options扩展收件选项省略

示例:

server.sendmail(sender, [receiver], message.as_string())

receiver # 一个字符串
[receiver] # 包含一个字符串的列表

该方法返回“被拒绝收件人”的字典。返回{}表示本次提交的收件地址全部被服务器接受;全部收件人被拒绝时会抛出异常。

5.quit():结束连接

server.quit()

无需传参数,用于结束 SMTP 会话。

最终示例使用:

with smtplib.SMTP_SSL(...) as server: ...

退出with代码块时会自动执行退出和连接清理,因此不必再手动调用quit()。

重点区分两点:邮件头与投递地址

# 这是邮件里显示的收件人信息。 message["To"] = formataddr(("收件同学", receiver)) # 这里的 [receiver] 才是本次实际投递的收件地址。 server.sendmail(sender, [receiver], message.as_string())

课堂实验保持两者一致。可以用“信纸上写的收件人”和“投递时使用的信封地址”帮助区分。

三.实验前准备:开启 163 的 SMTP 服务

准备:

  • 一个能正常登录的普通 163.com 邮箱。
  • 一个真实的收件邮箱,例如自己的 QQ 邮箱。
  • Python 3 和可联网的电脑。

在 163 邮箱网页版中,从“设置”进入“POP3/SMTP/IMAP”的页面,开启包含 SMTP 的服务,并按照页面要求完成身份验证、生成客户端授权码。

配置:

项目填写内容
发件服务器smtp.163.com
端口465
连接方式SSL/TLS
登录账号完整的 163 邮箱地址
登录凭证客户端授权码

强调三点:

  1. 授权码由发件人的 163 邮箱生成。
  2. 收件人不需要提供密码或授权码,也不必为了收这封邮件开启 SMTP。
  3. 授权码不要写进课件、作业截图或提交的源码中。授权码不要让别人知道。

四、最终实验:163 邮箱给其他邮箱发送邮件

import smtplib import ssl from getpass import getpass from email.mime.text import MIMEText from email.header import Header from email.utils import formataddr # 1. 获取发件人、收件人和授权码 sender = input("请输入发件人的163邮箱:").strip() receiver = input("请输入收件邮箱,例如你的QQ邮箱:").strip() auth_code = getpass("请输入163邮箱客户端授权码:").strip() # 2. 编写正文 body = """你好! 这是我在 Python 课堂上发送的一封实验邮件。 本次实验中,我学习了: 1. 构造邮件正文和邮件头。 2. 连接163邮箱的SMTP服务器。 3. 使用授权码登录并发送邮件。 祝学习顺利! """ # 3. 构造邮件对象 message = MIMEText(body, "plain", "utf-8") message["From"] = formataddr(("Python课堂", sender)) message["To"] = formataddr(("收件同学", receiver)) message["Subject"] = Header("Python课堂邮件实验", "utf-8") # 4. 创建TLS配置 context = ssl.create_default_context() # 5. 连接、登录、发送 try: with smtplib.SMTP_SSL( host="smtp.163.com", port=465, timeout=20, context=context ) as server: server.login(sender, auth_code) refused = server.sendmail( sender, [receiver], message.as_string() ) if not refused: print("163服务器已接受邮件,请到收件邮箱检查。") else: print("以下收件地址被拒绝:", refused) except smtplib.SMTPAuthenticationError as error: print("登录失败:请检查163邮箱、授权码及SMTP服务状态。") print("详细信息:", error) except smtplib.SMTPRecipientsRefused as error: print("收件地址被拒绝:请检查收件邮箱是否填写正确。") print("详细信息:", error) except smtplib.SMTPException as error: print("SMTP通信发生异常:", error) except OSError as error: print("网络连接或TLS连接发生异常:", error)

getpass()在普通终端中输入时通常不会显示字符或星号,这是正常现象。输入完毕按回车;建议在终端运行,部分 IDE 输出窗口不支持隐藏输入。

写法解释
input("提示文字")显示提示,读取一行输入,返回字符串
.strip()本课不传参数,去掉输入首尾的空白
getpass("提示文字")提示输入授权码,通常隐藏输入,返回字符串
"""多行文字"""定义跨越多行的字符串
print(...)把一个或多个值显示到终端
try尝试执行可能失败的代码
except ... as error捕获指定类型的异常,用error保存异常信息
with ... as server创建并管理连接,将连接对象命名为server
if not refused当返回的字典为空时进入该分支

这里的try、except、with、if是语法关键字,不是函数,不需要寻找它们的“函数参数”。

实验结果要检查两层:

  • 程序层面:163 服务器是否接受邮件。
  • 收件层面:收件邮箱是否实际收到,必要时查看垃圾邮件文件夹。

服务器接受邮件,不等于保证立即进入对方收件箱。如果程序已经显示“已接受”,随后退出连接时报错,也应先检查收件情况,避免重复发送。

import smtplib import ssl from getpass import getpass from email.mime.text import MIMEText from email.header import Header from email.utils import formataddr # 1. 获取发件人、收件人和授权码 sender = "发送邮箱" receiver = ["接收邮箱"] auth_code = "授权码" # 2. 编写正文 body = """你好! 这是我在 Python 课堂上发送的一封实验邮件。 本次实验中,我学习了: 1. 构造邮件正文和邮件头。 2. 连接163邮箱的SMTP服务器。 3. 使用授权码登录并发送邮件。 祝学习顺利! """ # 3. 构造邮件对象 message = MIMEText(body, "plain", "utf-8") message["From"] = formataddr(("Python课堂", sender)) message["To"] = formataddr(("收件同学", receiver[0])) message["Subject"] = Header("Python课堂邮件实验", "utf-8") # 4. 创建TLS配置 context = ssl.create_default_context() # 5. 连接、登录、发送 try: with smtplib.SMTP_SSL( host="smtp.163.com", port=465, timeout=20, context=context ) as server: server.login(sender, auth_code) refused = server.sendmail( sender, receiver, message.as_string() ) if not refused: print("163服务器已接受邮件,请到收件邮箱检查。") else: print("以下收件地址被拒绝:", refused) except smtplib.SMTPAuthenticationError as error: print("登录失败:请检查163邮箱、授权码及SMTP服务状态。") print("详细信息:", error) except smtplib.SMTPRecipientsRefused as error: print("收件地址被拒绝:请检查收件邮箱是否填写正确。") print("详细信息:", error) except smtplib.SMTPException as error: print("SMTP通信发生异常:", error) except OSError as error: print("网络连接或TLS连接发生异常:", error)
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/4 8:43:10

PyTorch深度学习实战手册:364页代码驱动学习指南

1. 为什么一本364页的深度学习实战手册值得熬夜啃第一次看到这个标题的时候&#xff0c;我脑子里冒出来的第一个念头是&#xff1a;又是一本“速成”书吧。市面上打着“动手学”旗号的资料太多了&#xff0c;翻开来要么是公式堆砌&#xff0c;要么是代码跑不通&#xff0c;最后…

作者头像 李华
网站建设 2026/10/30 5:03:42

基于Matlab的电动汽车削峰填谷多目标优化调度实现

电动汽车的调度问题&#xff0c;很多人刚接触时会下意识觉得“不就是控制一下充电时间吗”。但真正上手做下去&#xff0c;你会发现事情远没有这么简单&#xff1a;电网有基础负荷&#xff0c;电动汽车有出行需求&#xff0c;用户有充电成本敏感度&#xff0c;电池有寿命损耗&a…

作者头像 李华
网站建设 2026/10/30 8:50:36

IntelliJ IDEA Java开发必备12个生产力插件实战指南

1. 这不是“插件清单”&#xff0c;而是一份IDEA生产力实战地图你打开IntelliJ IDEA&#xff0c;新建一个Spring Boot项目&#xff0c;刚敲完RestController&#xff0c;光标停在类名上——这时候&#xff0c;你真正需要的不是“又一个插件”&#xff0c;而是能立刻缩短从‘想到…

作者头像 李华
网站建设 2026/10/31 6:47:52

C语言指针入门:给内存换名字的艺术与实战

这一讲是《C语言对话》系列的第五篇&#xff0c;标题我琢磨了好一阵子&#xff0c;最后是从一句大家耳熟的西方谚语里找到的灵感&#xff1a;A rose by any other name would smell as sweet——玫瑰就算换个名字&#xff0c;开出来照样香。乍一听和C语言没什么关系&#xff0c…

作者头像 李华
网站建设 2026/10/30 23:28:24

计算机毕设怎么做?基于Hadoop的网络安全入侵流量特征分析与可视化系统源码 毕业设计 选题推荐 毕设选题 数据分析 机器学习

> ✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡如果你遇到…

作者头像 李华
网站建设 2026/11/2 2:19:12

PE启动盘装Win10全指南:UEFI与GPT分区引导避坑手册

1. 为什么我一直推荐用PE装Windows 101.1 直接跑ISO镜像安装的痛点很多人觉得装系统就是把ISO镜像解压到U盘&#xff0c;然后从U盘启动开装。这个操作在全新硬盘、引导干净的情况下确实能跑通&#xff0c;但只要稍微遇到点"特殊情况"就非常难受&#xff1a;比如你的硬…

作者头像 李华