SMTP:电子邮件是如何发出去的
本文是《计算机网络学习笔记》系列的第十四篇。每天有数千亿封邮件在互联网上流转,但大多数人并不知道邮件是怎么从发件方的邮件客户端,一路送达收件方的邮箱的。本文拆解 SMTP 协议的对话格式,讲清楚一封邮件发出去的完整过程,以及为什么现在抓包已经看不到明文内容了。
一、邮件系统的三个角色
在深入 SMTP 之前,先理清邮件系统中的三个角色:
发件人 收件人
│ │
▼ ▼
邮件客户端 邮件客户端
(Outlook/Thunderbird) (Outlook/Thunderbird)
│ SMTP(发送) ▲
▼ │ IMAP/POP3(拉取)
发件方邮件服务器 ──── SMTP(中转)────► 收件方邮件服务器
(smtp.example.com) (smtp.test.com)- MUA(Mail User Agent,邮件用户代理):就是你用的邮件客户端,比如 Outlook、Thunderbird,负责撰写和接收邮件;
- MTA(Mail Transfer Agent,邮件传输代理):邮件服务器,负责在服务器之间转发邮件;
- SMTP:负责推送邮件,用于 MUA → MTA,以及 MTA → MTA 之间的传输;
- IMAP / POP3:负责拉取邮件,用于 MUA 从 MTA 收取邮件。
简单记:SMTP 管发,IMAP/POP3 管收。
二、SMTP 协议基础
SMTP(Simple Mail Transfer Protocol,简单邮件传输协议)是一个基于文本的协议,运行在 TCP 之上,使用 25 端口(加密版使用 587 或 465 端口)。
所谓"基于文本",意味着客户端和服务端之间交换的是人类可读的 ASCII 字符串,每条命令或响应以 \r\n 结尾——这和 HTTP/1.1 的设计思路完全一致。
SMTP 的每条响应都以一个三位数字状态码开头:
2xx:成功3xx:需要继续操作4xx:临时错误(可以重试)5xx:永久错误(不要重试)
三、完整的 SMTP 对话
一次完整的 SMTP 会话分为三个阶段:建立连接、传输信封、传输内容。
# ── 建立连接 ──────────────────────────────────────────────
S: 220 smtp.example.com Service ready # 服务器就绪
C: EHLO client.example.com # 客户端打招呼,声明自己身份
S: 250-smtp.example.com Hello # 服务器回应,250 表示成功
S: 250-SIZE 35882577 # 顺便告知最大邮件大小
S: 250 STARTTLS # 告知支持的扩展能力
# ── 传输信封(Envelope)──────────────────────────────────
C: MAIL FROM: <[email protected]> # 声明发件人
S: 250 OK
C: RCPT TO: <[email protected]> # 声明收件人(可多次,支持多个收件人)
S: 250 OK
# ── 传输内容(Message)──────────────────────────────────
C: DATA # 请求开始发送邮件内容
S: 354 Start mail input; end with <CRLF>.<CRLF> # 服务器同意,告知结束符
C: Subject: Hello
C: From: [email protected]
C: To: [email protected]
C:
C: This is the email body.
C: . # 关键:单独一行的句号,表示内容结束
S: 250 OK # 服务器确认接收完毕
# ── 断开连接 ──────────────────────────────────────────────
C: QUIT
S: 221 Service closing channel几个值得注意的细节
HELO vs EHLO
HELO 是原始 SMTP 的打招呼命令;EHLO(Extended HELO)是扩展版,服务器在回应中会列出自己支持的所有扩展能力(如 STARTTLS、AUTH、最大邮件大小等)。现代客户端都应该用 EHLO。
信封(Envelope)与邮件头(Header)是两回事
SMTP 的 MAIL FROM 和 RCPT TO 是信封,决定邮件实际送往哪里;DATA 里面的 From: 和 To: 是邮件头,是你在邮件客户端里看到的显示信息。两者可以不一样——这也是垃圾邮件伪造发件人的原理。
单独一行的句号是结束符
DATA 阶段,邮件正文以"单独一行,只有一个句号"(\r\n.\r\n)作为结束标志。如果正文本身某行恰好只有一个句号,协议规定必须转义成两个句号(.),接收方再还原回来。
四、邮件现状:为什么抓包看不到明文?
现代邮件传输几乎全部在 SMTP 之外套上了 TLS 加密层,有两种方式:
| 方式 | 端口 | 机制 |
|---|---|---|
| STARTTLS | 587 | 先建立普通 TCP 连接,然后通过 STARTTLS 命令升级为加密连接 |
| SMTPS | 465 | 从一开始就建立 TLS 连接,相当于 SMTP over TLS |
因为有了 TLS,即使用 Wireshark 抓包,看到的也是一堆密文乱码,无法直接读取邮件内容。
如果想看到明文的 SMTP 对话,可以在本地搭一个不加密的测试环境:
# 使用 Python 在本地启动一个 SMTP 调试服务器(接收到的邮件直接打印到终端)
python -m smtpd -n -c DebuggingServer localhost:1025
# 新开一个终端,用 telnet 模拟客户端
telnet localhost 1025
# 然后按照上面的对话格式,从 EHLO 开始逐行输入注意:Windows 下 telnet 的退格键行为异常,输错了无法删除,建议直接重新连接。
五、收邮件:IMAP vs POP3
SMTP 只负责发送,收件用的是另外两个协议:
| IMAP | POP3 | |
|---|---|---|
| 全称 | Internet Message Access Protocol | Post Office Protocol 3 |
| 端口 | 143(993 加密版) | 110(995 加密版) |
| 工作方式 | 邮件保留在服务器,客户端只是"查看" | 邮件下载到本地,默认从服务器删除 |
| 多设备支持 | ✅ 手机、电脑都能看到同一份邮件 | ❌ 在一台设备下载后,其他设备看不到 |
| 离线访问 | 需要同步,取决于客户端实现 | 下载后完全离线可用 |
| 现状 | 主流,Gmail/Outlook 均支持 | 已基本淘汰 |
POP3 之所以被淘汰,核心原因是:它把邮件下载到本地后就从服务器删除,换一台设备就看不到历史邮件了。在多设备成为常态的今天,这个设计无法接受。
六、DNS 与邮件:MX 记录
发件方的邮件服务器怎么知道,要把邮件发给哪台服务器?
答案是 DNS 的 MX 记录(Mail Exchanger)。
# 查询 gmail.com 的邮件服务器
nslookup -type=MX gmail.com
# 输出示例:
# gmail.com MX preference = 5, mail exchanger = gmail-smtp-in.l.google.com
# gmail.com MX preference = 10, mail exchanger = alt1.gmail-smtp-in.l.google.com当你发一封邮件到 [email protected],发件方服务器会先查询 gmail.com 的 MX 记录,拿到目标邮件服务器的域名,再解析其 IP,最后用 SMTP 把邮件投递过去。
MX 记录可以有多条,preference 值越小优先级越高,实现了负载均衡和故障转移。
小结
| 概念 | 一句话总结 |
|---|---|
| SMTP | 基于文本的邮件发送协议,TCP 25/587/465 端口 |
| EHLO | 扩展握手命令,服务器回应时列出支持的所有能力 |
| 信封 vs 邮件头 | MAIL FROM/RCPT TO 决定实际投递路径,From:/To: 只是显示信息 |
| STARTTLS / SMTPS | 现代 SMTP 的加密方式,抓包只能看到密文 |
| IMAP | 邮件留在服务器,多设备同步,已取代 POP3 成为主流 |
| MX 记录 | DNS 记录类型,指定某个域名的邮件应投递到哪台服务器 |
本系列前十三篇:
· 第1篇:《TCP 协议格式详解》
· 第2篇:《TCP 三次握手与四次挥手》
· 第3篇:《TCP 可靠数据传输》
· 第4篇:《TCP 流量控制与拥塞控制》
· 第5篇:《TLS:HTTPS 背后的加密握手》
· 第6篇:《HTTP 协议进化史》
· 第7篇:《DNS:互联网的电话簿》
· 第8篇:《IPv4 与 IPv6:网络层的寻址与路由》
· 第9篇:《NAT:家里的路由器如何帮你"上网"》
· 第10篇:《UDP 协议详解》
· 第11篇:《ICMP:ping 和 traceroute 背后的协议》
· 第12篇:《链路层:以太网帧与 ARP 协议》
· 第13篇:《从输入 URL 到页面显示:全流程串讲》
参考资料:《计算机网络:自顶向下方法》