SMTP 原理与报文 — 命令、响应码与 MIME 结构

SMTP 会话状态机、命令集与三位响应码、信封与报文头的区别、MIME 多部分结构与完整投递时序

先建立直觉

SMTP 干的事情非常朴素:把一封写好的信,从你手上一站一站送到收信人的邮局

它像两个人在电话里对暗号——你说一句"我要寄信",对方回一句"可以";你说"寄给张三",对方回"收到";你说"内容开始",对方回"你讲吧"。全程都是人能读懂的大白话,一问一答,谁也不抢话。

送到收信人的邮局就算完事了,至于收信人什么时候去开信箱,SMTP 一概不管

它解决什么问题

没有 SMTP 会怎样?每家公司的邮件系统各说各话,A 公司的信根本递不进 B 公司的门。而且就算递进去了,也说不清"这封信到底该退给谁"“中途经过了哪几个邮局”。

SMTP 用一套纯文本、可人工朗读的命令/响应对话解决了三件事:

  1. 谁发、发给谁:通过 MAIL FROM:RCPT TO: 建立"信封(Envelope)",与邮件正文里的 From: / To: 头部(Header)相互独立。
  2. 内容怎么传DATA 命令后以 <CRLF>.<CRLF> 作为结束标记传输报文;二进制附件由 MIME 编码成 7bit ASCII。
  3. 跨域怎么走:查目标域的 MX 记录找到对方 MTA,逐跳中继,每跳追加一条 Received: 头,形成可追溯的投递路径。

工作流程(简化版)

用普通人的语言,一封邮件的投递就 5 步:

  1. 敲门:客户端连上服务器,服务器先说一句"我准备好了"。
  2. 自报家门:客户端说"我是谁",服务器顺便报出"我支持哪些本事"(能不能加密、能不能认证、单封信最大多大)。
  3. 写信封:客户端说明"这封信谁发的"“要给谁”,可以一次写多个收件人。
  4. 塞信纸:客户端说"我要开始给内容了",然后把标题、正文、附件一股脑发过去,最后用一个约定的结束标记告诉服务器"讲完了"。
  5. 道别:服务器说"这封信我接手了",客户端说再见,连接关闭。

对应到真实命令,一次完整投递分三阶段:

阶段动作关键点
连接与握手TCP 握手 → 服务端发 220 → 客户端 EHLO 域名EHLO 应答里公布扩展能力清单
邮件事务MAIL FROM: → 多个 RCPT TO:DATA → 内容 → <CRLF>.<CRLF>一条连接可做多个事务;RSET 中止
释放QUIT221服务端收到最终 250 后才接管投递

信封(Envelope)与报文(Message)分离是 SMTP 最关键的设计:

  • 信封MAIL FROM:(退信地址)+ RCPT TO:(投递地址),只有 MTA 可见,决定实际投递。
  • 报文DATA 后的「头部 + 空行 + 正文」,From:/To: 仅用于显示,可与信封不同。

Bcc 只出现在 RCPT TO: 信封里、不写入头部,故他人看不到;钓鱼邮件伪造 From: 头部而信封发件人是攻击者本人,正是同一机制的误用。

报文 / 头部长什么样

核心命令集(RFC 5321)

看这张表前先记住:SMTP 的命令全是 4 个字母,一条命令干一件事,顺序不能乱——先握手、再写信封、最后才给内容。

命令含义
HELO / EHLO握手(EHLO 为 ESMTP,回多行 250 能力清单)
MAIL FROM:<addr>声明信封发件人,可带 SIZE= 等扩展参数
RCPT TO:<addr>声明一个信封收件人,多个收件人重复发送
DATA服务端回 354 后传输报文,以单独一行的 . 结束
RSET / NOOP / QUIT重置事务 / 保活 / 关闭连接(回 221
VRFY / EXPN验证用户 / 展开列表(现代服务器普遍禁用,防枚举)
STARTTLS明文升级为 TLS(RFC 3207),升级后须重发 EHLO
AUTH身份认证(RFC 4954),凭据 Base64 传输

命令英文全称对照:HELO = Hello(问候);EHLO = Extended Hello(扩展问候);MAIL FROM = 信封发件人;RCPT TO = Recipient To(信封收件人);DATA = 报文数据;RSET = Reset(重置);NOOP = No Operation(空操作/保活);VRFY = Verify(验证用户);EXPN = Expand(展开邮件列表);AUTH = Authentication(认证)。

响应码

看这张表前先记住:只看第一位数字就能决定下一步怎么办——2 是成了、3 是"你继续"、4 是先等等、5 是没戏了。

响应格式 <3位数字>[空格|-]<文本>,连字符表示后续还有行。首位决定客户端行为:

首位类别客户端行为
2xx成功完成继续下一步
3xx中间响应(如 354 等待正文)发送后续数据
4xx暂时性否定入队稍后重试
5xx永久性否定放弃并生成退信(NDR)

高频码:220 就绪 / 221 关闭 / 235 认证成功 / 250 完成 / 334 认证质询 / 354 输入正文 / 421 服务不可用 / 450 灰名单临时拒 / 535 认证失败 / 550 邮箱不存在或中继被拒 / 552 超配额 / 554 策略拒绝(反垃圾)。

报文头部关键字段(RFC 5322)

看这张表前先记住:这些字段是"信纸上印的内容",收件人能看到,但它们不决定信真正送到哪里——决定投递的是信封。

字段含义
Received:每跳追加一条,倒序阅读,最上面是最后一跳,排查路径核心依据
From: / To: / Cc:显示用地址;Bcc 不出现在最终报文
Reply-To:回复地址,可与 From 不同
Subject:非 ASCII 须 RFC 2047 编码 =?UTF-8?B?...?=
Message-ID:全局唯一标识,用于去重与会话串联
Return-Path:最终 MTA 写入的信封发件人(退信地址)
MIME-Version:固定 1.0
Content-Type:text/plain; charset=UTF-8multipart/mixed; boundary="B1"
Content-Transfer-Encoding:7bit/8bit/base64/quoted-printable
Authentication-Results:接收方写入的 SPF/DKIM/DMARC 校验结果

字段中文全称对照:Received(投递轨迹)、From(显示发件人)、To(显示收件人)、Cc = Carbon Copy(抄送)、Bcc = Blind Carbon Copy(密送)、Reply-To(回复地址)、Subject(主题)、Message-ID(报文唯一标识)、Return-Path(退信路径)、MIME-Version(MIME 版本)、Content-Type(内容类型)、Content-Transfer-Encoding(内容传输编码)、Authentication-Results(认证校验结果)。

MIME 多部分结构

看这段结构前先记住:MIME 就是给一封信"分格子"——用 boundary 这条分隔线,把正文、HTML 版、附件各放一格。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Content-Type: multipart/mixed; boundary="B1"
--B1
Content-Type: multipart/alternative; boundary="B2" ← 同内容多种呈现
--B2
Content-Type: text/plain; charset=UTF-8 ← 纯文本版
--B2
Content-Type: text/html; charset=UTF-8 ← HTML 版(优先渲染)
--B2--
--B1
Content-Type: application/pdf; name="r.pdf" ← 附件
Content-Transfer-Encoding: base64
--B1--
  • multipart/mixed:正文 + 附件并列;multipart/alternative:同内容不同格式择一渲染;multipart/related:HTML 内嵌图片(用 Content-ID 引用)。
  • Base64 使体积膨胀约 33%——这是「10MB 附件限制」实际仅能发约 7.5MB 文件的原因。

交互时序

一句话看懂这张图:先打招呼摸清对方能力 → 升级加密 → 报上账号密码 → 写信封 → 给内容 → 说再见,每一步都是"我说一句、你回一句"。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
sequenceDiagram
    autonumber
    participant C as 客户端
    participant S as 服务器
    Note over C,S: TCP 握手(端口 587)
    S->>C: 220 mail.example.com ESMTP ready
    C->>S: EHLO client.example.net
    S-->>C: 250-SIZE 52428800<br/>250-STARTTLS<br/>250-AUTH LOGIN PLAIN<br/>250 PIPELINING
    C->>S: STARTTLS
    S-->>C: 220 Ready to start TLS
    Note over C,S: TLS 握手,后续加密
    C->>S: EHLO client.example.net(重发)
    C->>S: AUTH LOGIN
    S-->>C: 334 VXNlcm5hbWU6
    C->>S: <Base64 用户名>
    S-->>C: 334 UGFzc3dvcmQ6
    C->>S: <Base64 授权码>
    S-->>C: 235 Authentication successful
    C->>S: MAIL FROM:<alice@example.net> SIZE=1024
    S-->>C: 250 OK
    C->>S: RCPT TO:<bob@example.org>
    S-->>C: 250 Accepted
    C->>S: DATA
    S-->>C: 354 End with <CR><LF>.<CR><LF>
    C->>S: From/To/Subject + 空行 + 正文
    C->>S: . (独占一行的点)
    S-->>C: 250 OK id=1sXyZa
    Note right of S: 收 250 后服务器接管投递
    C->>S: QUIT
    S-->>C: 221 Bye

跨域时接收方 MTA 查询收件域 MX 记录,按优先级(数字越小越优先)连目标 MTA 的 25 端口,重复上述事务。

关键机制 / 变体

  • Dot Stuffing(透明填充)——这是为了防止正文里的一个点把信提前"掐断"<CRLF>.<CRLF> 是结束标记,正文若有独占一行的 . 须在行首再加一个点(..),接收端去掉一个。自研程序忽略此细节会截断邮件。
  • 中继与开放中继——这决定了你的服务器会不会被垃圾邮件团伙白嫖:中继指转发收发双方均非本域的邮件;不做鉴权的开放中继会被垃圾邮件滥用并进入 Spamhaus 黑名单。现代规范:25 端口只做 MTA 入站投递,用户提交走 587 + AUTH(RFC 6409)。
  • SMTP AUTH——这是证明"我确实是这个邮箱的主人"AUTH PLAIN(一次性 \0用户\0密码 Base64,非加密须 TLS 内用)、AUTH LOGIN(两步交互)、AUTH CRAM-MD5(质询-响应,密码不明文过网)、AUTH XOAUTH2(Gmail/365 主推)。邮箱要求的「授权码」即 AUTH 独立凭据。
  • 反垃圾三件套——这是收件方用来判断"这封信是不是冒名顶替"SPF(RFC 7208,校验信封 MAIL FROM 域的发送 IP)、DKIM(RFC 6376,私钥签名头部+正文,DNS 公钥验签)、DMARC(RFC 7489,要求 SPF 或 DKIM 至少一项与 From: 域对齐,声明 none/quarantine/reject 策略)。
  • PIPELINING 与 8BITMIME——这两个是提速与省编码的优化项PIPELINING(RFC 2920,连续发命令不等响应,减 RTT)与 8BITMIME(RFC 6152,允许 8 位字节,避免中文必须 Base64)。

常见误区

  1. “头部 From: 就是真实发件人” —— 错。决定投递和退信的是信封 MAIL FROM:,头部 From: 只用于显示、可任意填写。所以才需要 DMARC 强制二者对齐。
  2. "DATA 之后服务器回 250 表示对方已经收到信了" —— 不完全对。250 只表示当前这台服务器接管了投递,后续还要逐跳中继,中途仍可能退信。
  3. “STARTTLS 用了就一定安全” —— 未必。STARTTLS 是先明文再升级,存在被剥离降级的风险;升级后还必须重发 EHLO,否则之前那份能力清单不可信。465 是连上即 TLS,没有明文阶段。
  4. “10MB 附件限制就能发 10MB 文件” —— 不能。Base64 编码会让体积膨胀约 33%,10MB 的文件编码后约 13.3MB,会被 552 拒绝。

速记口诀

两百二打招呼,三五四等正文,二五零算过关;四是等等再来,五是彻底完蛋。

信封决定送到哪,头部只管给人看;点要加倍防截断,Base64 涨三成。

知识框架

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
mindmap
  root((SMTP))
    定位
      应用层推送协议
      TCP 25 中继/587 提交/465 隐式TLS
      RFC 5321
    会话模型
      220 欢迎→EHLO 协商
      邮件事务→QUIT 221
    命令集
      MAIL FROM 信封发件人
      RCPT TO 信封收件人
      DATA 点结束
      STARTTLS AUTH
    响应码
      2xx 成功
      4xx 临时重试
      5xx 永久退信
    信封与报文
      Envelope 仅 MTA 可见
      From 可伪造
      Received 倒序追踪
    MIME
      mixed/alternative/related
      Base64 膨胀33%
    #安全
      STARTTLS 加密
      AUTH PLAIN/LOGIN/XOAUTH2
      SPF/DKIM/DMARC
    关键细节
      Dot Stuffing
      开放中继风险
      PIPELINING 降RTT