Alex Yuan/Emondora256

dti

如何命名一个电子邮件地址 —— 谈谈RFC 822、RFC 2822、RFC 5322与RFC 6854

1982 年 1 月 11 日,22 名计算机科学家在一起讨论"计算机邮件" (也就是今天人们所熟知的电子邮件)。与会人员有创建了 Sun Microsystems 的那个家伙、开发了文字冒险游戏 Zork 的那个家伙、发明了 NTP 协议的那个家伙、还有说服政府应该购买 Linux 的那个家伙。他们要解决的问题很简单: ARPANET 上已经接入了 455 个终端,现在快要失控啦。

2026-07-22


A large number of terminals have already been connected to ARPANET, and the situation is now getting out of hand.

无论是应用层的数据校验与输入规范化,还是网络服务的用户身份鉴权,电子邮件地址均作为系统的核心通用标识符 Unique Identifier 被广泛应用。过去数十年,在现代互联网环境,Email Address 成为了数字世界中基础底层网络服务之一。它的结构由一个标识用户身份的前缀和“@”符号,以及一个标识服务器位置的域名组成 local-part@domain

当我们深入网络协议的底层架构时,会发现“如何命名一个电子邮件地址”并不是简单地拼接字符串。该架构是在理论与工程实用性约束下,漫长折衷实践与评估后的结果。自 ARPANET 期的初始原型构建,演化至当下大规模异构的邮件路由网络拓扑,电子邮件地址的命名规范经历了RFC 822、RFC 2822、RFC 5322等数代标准定义的演进。

RFC 822 与词法分析的妥协

作为早期的奠基文献,RFC 822 (Standard for the Format of ARPA Internet Text Messages)确立了电子邮件地址最核心的拓扑结构,即由本地部分和域部分构成的二元结构。当时的ARPANET正处于发展期,不同的网络节点、操作系统和邮件传输系统之间急需一种统一的消息格式。

在RFC 822的定义中,采用了自顶向下的词法分析 Lexical Analysis 方法来定义地址。一个电子邮件地址不仅仅是一串字符,它是由一系列的词法标记 (Tokens) 构成的。这就引入了邮件地址命名最令人头疼的两个概念:折叠空白符 (FWS) 与注释(Comments)。

协议设计者认为,邮件头部 (Header) 是由人类阅读和编辑的,因此允许在地址的各个标记之间插入空格、制表符甚至换行符,只要下一行以空白符开头即可。更重要是,RFC 822 允许在地址中插入用圆括号包裹的注释。

在RFC 822的规则下,以下命名不仅是合法的,而且是等价的:

john.doe@example.com
john.doe (This is a comment) @ example.com
(Comment) john (Comment) . (Comment) doe @ example . com

此外,对于本地部分 (Local-part),RFC 822 规定它可以是原子串 (Atom) 的组合,也可以是 引用字符串 (Quoted-string) 。只要你将本地部分用双引号包裹,你几乎可以在里面放入任何ASCII字符,包括空格、甚至“@”符号本身。例如:"john doe"@example.com"much.more unusual"@example.com 均符合规范。

这种极度灵活的命名规则,虽然在当时满足了各种异构系统(如早期的大型机、不同架构的终端)对接的需求,但也为后续邮件解析系统的语法解析和边界处理留下隐患。使用正则表达式来完全匹配 RFC 822 规范下的邮件地址被证明是一个极其复杂的数学问题。

RFC 2822 与 ABNF 范式

随着互联网在20世纪90年代的爆炸式增长,电子邮件从学术和国防网络走向大众商业领域。垃圾邮件 (Spam)、恶意代码注入和极其复杂的路由拓扑开始考验早期的协议。RFC 822 中过度宽松的语法和一些过时的路由规则 (如源路由,Source Routing,即明确指定邮件经过的路径节点:@hosta,@hostb:user@hostc)成为了安全隐患和负担。

2001年, RFC 2822 (Internet Message Format) 正式发布,宣布废弃RFC 822。作为系统性的重构,标志着邮件地址命名规范从宽容转向严谨语法。

RFC 2822 最大的贡献之一,是引入了 ABNF (Augmented Backus-Naur Form,扩充的巴科斯-瑙尔范式) 来精确定义邮件地址的结构。这种形式化语言彻底消除了早期 RFC 文档中因自然语言描述而产生的歧义。

在 RFC 2822 中,邮件地址 (addr-spec)的命名规则:

addr-spec       = local-part "@" domain
local-part      = dot-atom / quoted-string / obs-local-part
domain          = dot-atom / domain-literal / obs-domain

这里引入了 dot-atom 的概念。它明确规定了不使用引号包裹时,本地部分只能由特定的可见 ASCII 字符组成,并且点号(.)只能用于分隔这些字符,不能连续出现,也不能出现在开头或结尾。

例如:

john.doe@example.com (legal)
.john.doe@example.com (illegal)
john..doe@example.com (illegal)

RFC 2822 没有直接砍掉 RFC 822 时代的语法特征,而是将其归类为 废弃语法 (Obsolete Syntax,以 obs- 开头) 。现代的 MUA/MTA 不能 MUST NOT 生成这些废弃语法的地址,但现代的邮件解析器必须能够兼容并解析它们。这种设计哲学 (波斯特尔定律,Postel's Law),保障了互联网协议在代际更迭时的平滑过渡。

RFC 5322

2008年,互联网工程任务组 IETF 发布了 RFC 5322,取代了 RFC 2822,成为了当今互联网消息格式的基准标准。在命名邮件地址上,RFC 5322 并没有彻底改变前者的基本结构,而是针对多年工程实践中暴露出的边界情况进行了更严格的约束。

RFC 5322 强调了 atext 的有效字符集。一个没有被引号包裹的 local-part 只能包含以下字符:

a-z, A-Z
0-9
! # $ % & ' * + - / = ? ^ _  { | } ~`

实际上这定义了当今绝大多数系统所能接受的”常规电子邮箱名称”。例如,user+mailbox@example.com(加号寻址,常用于别名和邮件分类) 在理论和实践中都是被 RFC 5322 明确支持的。

CFWS 的限制与规范

RFC 5322 对注释 Comments 和折叠空白 (FWS)的位置进行了更严格的语义限定。虽然理论上 (comment)local-part@domain 依然可以通过解析,但规范明确指出,这些注释不携带任何路由或身份识别信息,仅仅是给人看的。系统在处理邮件地址比对时,必须将被解析出来的 addr-spec 剥离掉所有的 CFWS。

IP 域名的支持

RFC 5322 保留了 domain-literal 的定义,在域名部分使用方括号包裹的 IP 地址是被标准认可的命名方式。

IPv4: jsmith@[192.168.2.1]
IPv6: jsmith@[IPv6:2001:db8::1]

尽管出于反垃圾邮件策略 (SPF, DKIM, DMARC 往往依赖完整的域名),当今的邮件服务商几乎全部拒收以 IP 地址作为域名的邮件,但在协议层面上,它依然是合法的命名。

RFC 6854 与 Group Syntax

在前述的 RFC 中,我们探讨的始终是单一的邮件地址 (addr-spec)或包含显示名称的邮箱 (Name-addr,如 John Doe <john@example.com>)。但在企业的实际业务场景和传统的邮件列表中,邮件的发送方往往代表着一个组织或一个群体。

2013年, RFC 6854 (Update to Internet Message Format to Allow Group Syntax in the "From:" and "Sender:" Header Fields) 的实施,对邮件地址在 Header 中的表现形式做出语义扩展。

在此之前,邮件的 From: (发件人)字段只能包含一个或多个具体的 Mailbox。但 RFC 6854 允许 Group Syntax 组语法出现在 From:Sender: 字段中。

组语法的基本结构为:Display-name ":" [mailbox-list / CFWS] ";"

你可以合法地构建如下形式的发件人标识:

From: Executive Committee: alice@example.com, bob@example.com;

甚至可以是一个不包含任何具体地址的空组(通常用于隐藏发送者列表或系统自动生成的通知):

From: Undisclosed recipients:;

RFC 6854 从协议层面补齐了群组语法 (Group Syntax) 规范,使得‘多对一’及多源映射下的发送行为得以通过标准化的语法结构进行精确描述,而无需强行添加一个虚拟 addr-spec

RFC 标准的目标是兼容,确保信息能够跨越数十年不同架构的系统传递。但现代的 Mail Service 面临的首要威胁是网络安全与系统稳定性。因此,绝大多数系统在表单验证,会采用远比 RFC 5322 严格得多的子集,通常只允许 [a-zA-Z0-9._-]+

正则表达式

RFC 5322 的规则支持递归的注释和引用的字符串,所以基于 RFC 5322 的邮件地址实际上属于上下文无关文法 CFG,而非严格意义上的 Regex。这意味着,使用正则表达式试图验证一个复杂的邮件地址,在数学上是极其困难的。

这就是为什么 HTML5 标准中的 <input type="email"> 所使用的正则验证规则,也仅仅是一个不要求完全精确匹配的子集。总的来说,要更快且可靠地验证一个邮件地址是否合法的方法,是向它发送一封邮件并返回相应状态。

Conclusion

从 RFC 822 到 RFC 6854,如何命名一个电子邮件地址,并不是简单的字符串排列。平均长度不足 20 个字符的字符串,融合了 ARPANET 对去中心化路由的构想,也凸显了 ABNF 范式对网络协议的严谨性。折射出了当代互联网在面对海量并发、安全威胁时所展现出的工程克制。

当我们在代码中敲下 email.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/) ,也要考虑网络在面对海量并发以及安全威胁时所需要或必要的架构设想。RFC 包含了大量现代系统不再使用的废弃语法,这种严密的理论与向后兼容,支撑着四十年来全球信息系统的稳定。

Perl module用于验证Email地址是否合法 (符合 RFC 822 标准)的正则表达式:

(?:(?:\r\n)?[ \t])*(?:(?:(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t]
)+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:
\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(
?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ 
\t]))*"(?:(?:\r\n)?[ \t])*))*@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\0
31]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\
](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+
(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:
(?:\r\n)?[ \t])*))*|(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z
|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)
?[ \t])*)*\<(?:(?:\r\n)?[ \t])*(?:@(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\
r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[
 \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)
?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t]
)*))*(?:,@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[
 \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*
)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t]
)+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*)
*:(?:(?:\r\n)?[ \t])*)?(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+
|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r
\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t
]))*"(?:(?:\r\n)?[ \t])*))*@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031
]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](
?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?
:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?
:\r\n)?[ \t])*))*\>(?:(?:\r\n)?[ \t])*)|(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?
:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?
[ \t]))*"(?:(?:\r\n)?[ \t])*)*:(?:(?:\r\n)?[ \t])*(?:(?:(?:[^()<>@,;:\\".\[\] 
\000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|
\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>
@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"
(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*))*@(?:(?:\r\n)?[ \t]
)*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\
".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?
:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[
\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*|(?:[^()<>@,;:\\".\[\] \000-
\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(
?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)*\<(?:(?:\r\n)?[ \t])*(?:@(?:[^()<>@,;
:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([
^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\"
.\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\
]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*(?:,@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\
[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\
r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] 
\000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]
|\\.)*\](?:(?:\r\n)?[ \t])*))*)*:(?:(?:\r\n)?[ \t])*)?(?:[^()<>@,;:\\".\[\] \0
00-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\
.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,
;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?
:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*))*@(?:(?:\r\n)?[ \t])*
(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".
\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[
^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]
]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*\>(?:(?:\r\n)?[ \t])*)(?:,\s*(
?:(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\
".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)(?:\.(?:(
?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[
\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t
])*))*@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t
])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?
:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|
\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*|(?:
[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\
]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)*\<(?:(?:\r\n)
?[ \t])*(?:@(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["
()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)
?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>
@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*(?:,@(?:(?:\r\n)?[
 \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,
;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t]
)*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\
".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*)*:(?:(?:\r\n)?[ \t])*)?
(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".
\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)(?:\.(?:(?:
\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\[
"()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])
*))*@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])
+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\
.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z
|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*\>(?:(
?:\r\n)?[ \t])*))*)?;\s*)