SMTP 真正如何工作
完整的SMTP事务处理流程演示——从DNS查询到最终投递。如果你以编程方式发送电子邮件,这是你需要的心智模型。
整体图景
SMTP——简单邮件传输协议——是在互联网上传输电子邮件的协议。根据RFC 5321定义,自1982年以来一直是电子邮件的骨干(原始RFC 821)。尽管经历了数十年的扩展,核心事务仍然是两个服务器之间通过TCP 25端口的简短同步文本对话。
每次电子邮件投递都遵循相同的序列:
- DNS查询——查找收件人的邮件服务器
- TCP连接——连接到25端口(或465/587提交端口)
- 横幅和EHLO——交换身份和功能
- STARTTLS——升级到加密连接(通常)
- AUTH——认证(如果使用587端口提交)
- 信封——MAIL FROM和RCPT TO
- DATA——传输消息头和正文
- QUIT——关闭连接
第1步:DNS——查找邮件服务器
在任何SMTP对话开始之前,发送服务器必须发现将消息投递到何处。它查询收件人域的MX记录DNS。
10 mx1.example.com.
20 mx2.example.com.
数字是优先级(越小越优先)。发送者首先尝试mx1.example.com。如果无法访问或返回临时错误,发送者回退到mx2.example.com。
如果不存在MX记录,发送者回退到域的A或AAAA记录——但这是最后的手段。如果存在显式Null MX记录(0 .),域不接受邮件。有关完整算法,请参见DNS和邮件路由。
第2步:TCP连接和横幅
发送者打开MX主机25端口的TCP连接。接收服务器先以220横幅响应:
220 mx1.example.com ESMTP ready
220状态代码表示"服务就绪"。如果服务器超负荷,可能会响应421(服务不可用)并关闭连接。发送者应稍后重试。
对于消息提交(用户的邮件客户端通过其提供商发送),连接改为去往587端口(RFC 6409)或465端口(隐式TLS,RFC 8314)而不是25端口。
第3步:EHLO——宣布功能
发送者使用EHLO(扩展HELO)引入自己,提供自己的主机名。服务器响应其支持的扩展:
250-mx1.example.com Hello sender.mailertogo.com
250-SIZE 52428800
250-8BITMIME
250-STARTTLS
250-AUTH PLAIN LOGIN
250-PIPELINING
250-ENHANCEDSTATUSCODES
250 SMTPUTF8
每条250-行公布一个扩展。最后一行使用250 (空格,不是破折号)表示结束。关键扩展:
- SIZE——最大消息大小(字节)
- STARTTLS——服务器支持升级到TLS(RFC 3207)
- AUTH——支持的认证机制(RFC 4954)
- PIPELINING——发送者可以批量发送命令而无需等待(RFC 2920)
- ENHANCEDSTATUSCODES——扩展状态代码如5.1.1(RFC 3463)
- 8BITMIME——支持8位内容传输
- SMTPUTF8——国际化电子邮件地址(RFC 6531)
旧版HELO命令(没有"E")完全跳过扩展。在当今实践中几乎从不使用。
第4步:STARTTLS——升级到加密
如果服务器公布了STARTTLS,发送者发出命令以升级连接:
220 2.0.0 Ready to start TLS
# TLS握手发生在此处
# 连接现已加密
# 发送者必须在加密通道上再次EHLO
EHLO sender.mailertogo.com
250-mx1.example.com Hello sender.mailertogo.com
250-SIZE 52428800
250 ENHANCEDSTATUSCODES
STARTTLS后,发送者必须再次发送EHLO。服务器可能在加密连接上公布不同的扩展(例如,AUTH通常仅在建立TLS后才公布)。
在465端口,TLS是隐式的——连接从第一个字节开始加密,无需STARTTLS步骤。
没有TLS,整个SMTP对话——包括密码和消息内容——以明文发送。现代最佳实践是始终使用TLS。有关强制TLS的机制,请参见MTA-STS和DANE。
第5步:AUTH——认证(仅提交)
当邮件客户端通过587端口提交消息时,它使用SMTP AUTH进行认证(RFC 4954):
235 2.7.0 Authentication successful
Base64字符串编码\0username\0password。这就是为什么TLS至关重要——没有它,凭据以明文传输。
服务器间在25端口的投递不使用AUTH。相反,接收服务器依靠发送者的IP地址、SPF记录、DKIM签名和其他认证机制来验证消息。请参见电子邮件认证说明。
第6步:信封——MAIL FROM和RCPT TO
信封与消息头分开。它告诉接收服务器谁在发送消息以及谁应接收消息:
250 2.1.0 OK
RCPT TO:<bob@example.net>
250 2.1.5 OK
RCPT TO:<carol@example.net>
250 2.1.5 OK
关键细节:
-
MAIL FROM指定返回路径(也称为信封发送者或退回地址)。这是DSN(退回消息)的目标地址。它可以与
From:头不同。 - RCPT TO指定每个收件人。你可以为一条消息有多个RCPT TO命令。
- 可选的
SIZE=参数让服务器能够及早拒绝超大消息。 MAIL FROM:<>(空反向路径)用于退回消息本身,以防止无限退回循环。
每条命令获得个别响应。250表示已接受。RCPT TO上的550表示邮箱不存在。452表示服务器暂时无法接受该收件人。发送者必须单独处理每个响应——某些收件人可能被接受,而其他的被拒绝。
第7步:DATA——发送消息
DATA命令开始消息传输:
354 Start mail input; end with <CRLF>.<CRLF>
From: Alice <alice@example.com>
To: Bob <bob@example.net>
Subject: Meeting tomorrow
Date: Tue, 11 Mar 2026 10:30:00 -0400
Message-ID: <abc123@example.com>
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
Hi Bob,
Are we still on for 2pm?
- Alice
.
250 2.0.0 OK: queued as 4F3A21C8
消息包括头和正文,由空行分隔。消息由只包含单个句点(.)的行终止——"点转义"约定。消息正文中任何以句点开头的行在传输时前置一个额外的句点,在接收时被剥离。
DATA后的250响应意味着消息已被接受投递。它不意味着消息已被投递到收件人的邮箱。接收服务器可能在内容过滤期间仍会拒绝它,或稍后生成退回。
队列ID(例如4F3A21C8)对调试非常宝贵。记录它。
第8步:QUIT
221 2.0.0 Bye
发送者关闭会话。TCP连接被拆除。如果发送者有更多消息要发往同一服务器,它可以发出另一个MAIL FROM而不是QUIT来重用连接。
SMTP响应代码
每个SMTP响应以三位数代码开头。第一位数字告诉你类别:
| 代码 | 含义 | 操作 |
|---|---|---|
| 2xx | 成功 | 命令完成;继续 |
| 3xx | 中间 | 服务器等待更多数据(例如DATA后) |
| 4xx | 临时失败 | 稍后重试。服务器在后续尝试中可能接受消息 |
| 5xx | 永久失败 | 不重试。消息将永远不会被原样接受 |
你会遇到的常见代码:
-
220——服务就绪(横幅) -
250——请求的操作已完成 -
354——开始消息输入 -
421——服务不可用,关闭连接(稍后重试) -
450——邮箱暂时不可用(灰名单、速率限制) -
451——请求的操作因本地错误中止 -
452——存储空间不足 -
550——邮箱不存在/操作未执行 -
551——用户不在本地;请尝试转发地址 -
552——消息大小超过限制 -
553——邮箱名不允许 -
554——事务失败(通常用于策略拒绝)
使用ENHANCEDSTATUSCODES,服务器附加更具体的代码如5.1.1(坏目标邮箱)。有关完整分解,请参见理解电子邮件退回。
完整事务
以下是从连接到关闭的整个SMTP会话,附有注释:
220 mx1.example.net ESMTP Postfix
# 2. 客户端介绍自己
EHLO sender.mailertogo.com
250-mx1.example.net
250-STARTTLS
250-SIZE 26214400
250-PIPELINING
250-ENHANCEDSTATUSCODES
250 8BITMIME
# 3. 升级到TLS
STARTTLS
220 2.0.0 Ready to start TLS
# (TLS握手完成)
# 4. 在加密通道上重新介绍
EHLO sender.mailertogo.com
250-mx1.example.net
250-SIZE 26214400
250-PIPELINING
250-ENHANCEDSTATUSCODES
250 8BITMIME
# 5. 信封
MAIL FROM:<notifications@app.example.com>
250 2.1.0 OK
RCPT TO:<bob@example.net>
250 2.1.5 OK
# 6. 消息内容
DATA
354 End data with <CR><LF>.<CR><LF>
From: App Notifications <notifications@app.example.com>
To: bob@example.net
Subject: Your invoice is ready
Date: Tue, 11 Mar 2026 14:00:00 +0000
Message-ID: <inv-7842@app.example.com>
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
Your March invoice is ready. Log in to view it.
.
250 2.0.0 OK: queued as B9C4E1A3F
# 7. 完成
QUIT
221 2.0.0 Bye
提交与中继
SMTP担当两个不同的角色:
- 提交(587或465端口):邮件客户端向其邮件服务器发送新消息。需要认证。服务器负责投递。
- 中继(25端口):一个邮件服务器向另一个邮件服务器投递消息。不需要认证——授权来自DNS(MX记录)和发送者验证(SPF、DKIM)。
开放中继——一个接受任何人向任何人发送邮件的服务器——是电子邮件配置的首要禁忌。垃圾邮件发送者立即利用开放中继。每个现代邮件服务器必须将中继限制在认证用户或已知发送者。
可能出错的事情
连接被拒绝或超时
MX主机已关闭或受防火墙保护。发送者的队列以指数退避重试,循环通过较低优先级的MX记录。重试期后(通常为4-5天),消息退回。
灰名单
接收服务器为未知发送者的第一次尝试返回450。合法服务器重试;垃圾邮件发送者通常不会。你的消息会延迟几分钟,但最终会投递。
在RCPT TO处被拒绝
550 5.1.1 User unknown响应意味着邮箱不存在。这是一个硬退回。立即从列表中删除该地址。
在DATA处被拒绝
服务器可能接受信封但拒绝内容。常见原因:消息太大、内容被标记为垃圾邮件或认证检查(SPF/DKIM/DMARC)失败。响应在终止句点之后出现。
已接受然后退回
服务器为DATA返回250,但后来生成DSN(退回消息),因为邮箱已满、内容过滤拒绝了它或发生了内部错误。这是最难处理的情况——你只能通过发送到MAIL FROM地址的退回消息才能了解。
TLS失败
如果STARTTLS协商失败,大多数服务器回退到明文。这是安全风险——活跃攻击者可以剥离TLS。MTA-STS和DANE防止此降级。
关键要点
- SMTP是同步的、基于文本的协议。每个命令都获得一个响应代码。
- 信封(MAIL FROM / RCPT TO)与消息头(From: / To:)分开。它们可以不同。
- DATA后的
250意味着"已接受投递",而不是"已投递到收件箱"。 - 4xx错误是临时的——重试。5xx错误是永久的——不重试。
- 始终使用TLS。用MTA-STS或DANE强制执行。
- 从服务器响应记录队列ID以调试。