关于电子邮件地址我们常对自己撒的谎

Hacker News Top 工具

摘要

一篇博客文章,揭露验证电子邮件地址时的常见误解,反对基于正则表达式的验证,提倡发送确认邮件等更简单的验证方法。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/06/10 00:22

# 我们对自己撒的关于电子邮件的谎 来源:https://gitpush--force.com/commits/2026/06/lies-we-tell-ourselves-about-email/ 等宽字体 2026\-06\-06T22:54:55\-04:00 ### TL;DR: 别想太多,直接发送验证邮件就行。 请耐心听我讲,因为其中一些“谎言”可能会让你觉得显而易见,或是无关紧要的冷知识。老实说,这离真相并不远。不过,我希望你能让我尝试构建一个详细而有趣的说明,展示像电子邮件这样平凡的事物,是如何以令人惊讶的方式打破我们的预期的。 我们会涵盖很多边缘案例,在一些小障碍上绊倒,甚至会发现一些技术上正确、但在 Gmail 等大型系统中也不常见支持的有效内容(公平地说,这很可能是有原因的)。 每个例子不一定都是你需要确保正确处理的有意义用例。但综合来看,它们旨在逐步指向一个主要观点:电子邮件地址深陷于古老的历史之中,而系统中有效和无效组件的定义也在随时间悄然变化。 很容易做出一个常识性的决定,却意外地带来问题。除此之外,较老的开发者(以及较老的系统)可能持有过去正确、但现在已不再适用的预期。闲话少说,我们开始看这些谎言…… ### “电子邮件地址可以用正则表达式来验证” 让我们先把最显而易见的问题解决掉。我**远远不是**第一个(https://dev.to/nikl/why-good-developers-never-use-regex-to-verify-emails-3h2a)在网上(https://news.ycombinator.com/item?id=17632508)这样说(https://michaellong.medium.com/please-do-not-use-regex-to-validate-email-addresses-e90f14898c18)的人,也肯定不会是最后一个。但我认为这种重复是合理的,因为即使已经有一千篇博客文章发表、推文被转发、Reddit 评论被审核,这种反模式在 2026 年仍然顽固地存在着。 正则表达式方法主要通过三种方式给你、你的业务和你的客户带来痛苦: 1. **运行成本相对较高,却几乎没有实际好处** - 公平地说,在我们开始将简单查询扔给大型耗电 GPU 集群之前,这一点的影响其实更大。 2. **正则表达式很难,正则表达式高手很罕见,而且正则引擎的实现不一致。非常、非常容易在不经意间出错。** - 我们将在本文后面的部分讨论几种可能出错的方式。 3. **即使你复制粘贴了一个“好”的正则表达式,世界也在变化。今天有效的内容,明天可能就成为遗留系统。** - 互联网上充斥着 20 年前很棒的 advice。同样也充斥着 20 年前很棒的正则表达式,因为遗留系统是永恒的。 注意:此处有观点:输入验证更多的是**帮助用户**,让他们更难犯简单错误。它应该足够严格,以便让用户的生活更轻松,但仅此而已。**不要依赖输入验证来保护你免受用户的伤害**;用它来保护用户免受他们自己的伤害。从这个意义上说,使用正则表达式测试来改善用户体验是有合理论据的,值得考虑。用户体验很重要,我不会忽视这一点。然而,为了暂时扮演一下唱反调的角色,也许风险大于回报。在公元 2026 年,你可以合理地期望你的用户**知道如何输入**自己的电子邮件地址——甚至更好的是,通过操作系统、浏览器、键盘应用或密码管理器自动输入。 很可能,被糟糕实现的表单验证过滤掉的人数,多于那些需要手把手指导的人数。基于此,那么…… 1. **不要验证电子邮件地址。如果你*非做不可*,请使用简单的客户端正则表达式来帮助用户避免常见错误和拼写错误。** - 尽量使其尽可能不具限制性。例如 `^[^@]+@[^@\\s]+$`,它只确保用户输入了“something@something”。 2. **如果你在 API 或表单处理程序上有验证,请使用相同的正则表达式,以保持与前端的致性。** - 这又回到了“不要使用输入验证来保护你免受用户伤害”的观点。默认情况下,通过清理输入来保护自己,而不是拒绝输入。 3. ***验证***地址,而不是操心***校验***它。 - 发送一封电子邮件,让用户点击验证链接或输入验证码。 就是这样。不需要比这更复杂。你不需要检查域的 MX 记录;你的电子邮件服务会在“发送电子邮件”的过程中自动完成这项工作(另外,剧透警告,下面还有一些关于 MX 记录的有趣内容)。而且你绝对不需要一个庞大的正则表达式。事实上,你可能已经在发送验证邮件了!如果是这样,这或许是一个*删除代码*的借口,这可是每个程序员真正最喜欢的事情! 现在简单部分已经结束,让我们来看看一些*具体*的要点,这些要点让电子邮件处理变得既有趣又令人困惑! ### “电子邮件地址必须是有效的,并且电子邮件提供商支持所有有效地址” 这可能会让你震惊,但互联网是由软件构成的。如果说软件有什么普遍的共同点,那就是它*经常*偏离你的预期。 从 Gmail 等大型提供商到 Postfix(https://www.postfix.org/)等开源项目的电子邮件服务器软件,对电子邮件格式官方规则的支持程度各不相同。Postfix 在 2015 年左右(https://www.postfix.org/SMTPUTF8_README.html)才添加了对 SMTPUTF8 的支持,但直到多年后才默认启用。而 Dovecot(https://dovecot.org/)在 2026 年仍然不支持它(https://github.com/dovecot/core/pull/188)。这并不局限于开源;Gmail 在创建地址时对允许的字符进行了限制,但似乎支持*发送*UTF8 内容。 我们将在后面的部分更深入地探讨 SMTPUTF8 和 RFC 6531(https://datatracker.ietf.org/doc/html/rfc6531)。但让我们先看另一个例子,在这个例子中,我们可以*明确地违反所有相关 RFC 的限制*。我们来看一下 RFC 5321 第 4.5.3 节(https://datatracker.ietf.org/doc/html/rfc5321#section-4.5.3.1.1),它定义了长度限制。 `` 4.5.3.1.1. 本地部分 用户名或其他本地部分的最大总长度为 64 个八位字节。 `` 这是一个相当简单的限制,谢天谢地很容易理解!但有一些术语对某些读者来说*可能*不熟悉: - **“本地部分”。** 简而言之,本地部分是`@`字符*之前*的所有内容。 - (为了这个例子我们保持简单,但我们会在文章的其他部分回到本地部分。) - **八位字节** 一个八位字节是一个标准的 8 位字节(维基百科(https://en.wikipedia.org/wiki/Octet_(computing))) 所以现在,电子邮件地址 entirelytoomanycharactersinthisemailwhatisevenhappeningblahblahdonttrythisathome@gitpush–force.com(https://gitpush--force.com/cdn-cgi/l/email-protection#a9ccc7ddc0dbccc5d0ddc6c6c4c8c7d0cac1c8dbc8caddccdbdac0c7ddc1c0daccc4c8c0c5dec1c8ddc0daccdfccc7c1c8d9d9ccc7c0c7cecbc5c8c1cbc5c8c1cdc6c7dddddbd0ddc1c0dac8ddc1c6c4cce9cec0ddd9dcdac18484cfc6dbcacc87cac6c4)的本地部分有 80 个字节,是 100% 明确无效的。所以它自然行不通。 对吧? 是的,还记得软件会偏离预期吗?你实际上可以给我那个地址发送邮件。你的提供商(很可能)会允许它而不抱怨,我的提供商会愉快地将它投递到我的收件箱。 ### “电子邮件地址只能包含 ASCII 字符” 这种信念在英语世界可能更常见,但我很好奇:如果你不在英语区,你仍然期望电子邮件需要 ASCII 拉丁字符吗? 回到 2012 年,在科技世界中这既算是近期又算是古老,电子邮件国际化通过一组对应于电子邮件堆栈不同部分的 RFC 成为现实。具体到电子邮件地址,RFC 6531(https://datatracker.ietf.org/doc/html/rfc6531)定义了 SMTPUTF8 扩展,允许在电子邮件地址的本地部分使用非 ASCII 字符。从这个意义上说,世界上这么多人口直到 14 年前才能将自己的名字以其母语书写形式放入电子邮件地址,这确实相当令人惊讶。 国际字符在 2012 年之前*技术上*已经被允许并工作,通过 Punycode 之类的技术,例如 RFC 3490(https://datatracker.ietf.org/doc/html/rfc3490),这是一种编码技巧,其中 unicode 字符在底层被编码为 ASCII。但在当时,它只适用于域名,本地部分仍然限于 ASCII。 ### “电子邮件地址需要是人类可读的” 既然我们已经涵盖了拉丁字符的问题,让我们再深入挖掘一下。通过国际化,本地部分被定义为八位字节流。你技术上可以放入那些不映射到标准 unicode 中有效字符的字节。我可以把 � 放进电子邮件地址,它会是有效的。但它在任何语言中都不是人类可读的。 ### “电子邮件地址总是有一个二级域(SLD)和一个顶级域(TLD)” 想想你每天使用的熟悉电子邮件地址。一个 @icloud.com 的岛屿漂浮在 @gmail.com 的海洋中,夹杂着一些 ISP、@employer.com 和 @school.edu 的邮件。它们都遵循这个熟悉的模式:something-dot-something。 有三种类型的有效地址不遵循这种模式,按重要性降序排列。 1. **具有子域的地址,因此域名中有多个点。** - 希望至少这一点不那么令人惊讶。如果你在美国或加拿大以外,你可能已经习惯了看到像 .co.uk、.co.au 等。 - 但许多聪明人曾因善意但不周全的正则表达式而意外排除了这些用户。 2. **没有点的地址** - 在现实世界中,这仅对诸如内网地址之类的事情重要,其中网络上的每台机器都有一个主机名。对于电子邮件软件来说支持这一点很重要,但对大多数人来说不太可能是一个考虑因素。 - *技术上*,ICANN 或 Verisign 之类的机构可以注册像 admin@net 这样的地址,但说实话,不太可能。 3. **使用 IP 地址而不是域名的地址。** RFC 5321 第 4.1.3 节(https://datatracker.ietf.org/doc/html/rfc5321#section-4.1.3)*明确*规定了对这种地址的支持,它称之为“地址字面量”。 - 这非常罕见,但在现实世界中是有效的。 - 相关地,如果你的电子邮件客户端支持,你也可以通过 ben@[50.169.39.178](https://gitpush--force.com/cdn-cgi/l/email-protection#557732394711211621142e2a25243a272d3a25232c312156)联系到我。 - 遗憾的是,Gmail 的网络客户端似乎在这方面有问题。但我*认为*如果你使用 Thunderbird 之类的客户端,它仍然可以通过 Gmail 工作。 - 在我的测试中,Gmail 的网络客户端会发送邮件,但似乎会剥离 RFC“地址字面量”部分中指定的、发送到未命名主机所需的`[]`方括号。 ### “电子邮件地址总是有一个‘正常’的 TLD” 我实际的个人电子邮件地址不在 .com 或 .net 域名上,而是一个**。email** 域名。我非常喜欢这一点,因为我是个书呆子,喜欢为了奇怪而奇怪的东西。 但我最终还是创建了一个带有 .net 域名的别名,因为事实证明,*很多*公司运行的验证逻辑显然只允许常见的域名:.com、.net、.org、.edu 等。 ### “电子邮件地址只能有一个 @ 字符” 注意:我很难验证这一点,而且我可能误读了 RFC。我真的把它放在这里,以防有人能帮我指出我遗漏了什么!我提到过,如果你的客户端允许,你可以通过 ben@[50.169.39.178](https://gitpush--force.com/cdn-cgi/l/email-protection#680a0d06284d5d2a5d5846595e51465b5146595f504d5d2c)联系我,并补充说 Gmail 似乎在这方面有问题。但对于接下来这个,Gmail 直接拒绝发送。 所以如果 Gmail 明确不支持它,你可能也不需要支持。但根据 RFC 5322 第 3.2.4 节(https://datatracker.ietf.org/doc/html/rfc5322#section-3.2.4),它*技术上*是有效的。 `` 包含除原子中允许字符以外的字符的字符串,可以用带引号的字符串格式表示,其中字符由引号(DQUOTE,ASCII 值 34)包围。 qtext = %d33 / ; 可打印的 US-ASCII %d35-91 / ; 不包括 "\" 或引号字符的字符 %d93-126 / ; obs-qtext qcontent = qtext / quoted-pair quoted-string = [CFWS] DQUOTE *([FWS] qcontent) [FWS] DQUOTE [CFWS] `` 阅读 RFC 不一定总是有趣,但这基本上是说,任何从 33 到 126 的 ASCII 字符,除了 34(引号字符本身)和 92(反斜杠字符),都可以出现在电子邮件地址本地部分的带引号字符串中。 所以技术上,ben"@"@[email protected](https://gitpush--force.com/cdn-cgi/l/email-protection#aec2c1c2d9cfdaeec9c7dadedbddc68383c8c1dccdcb80cdc1c3)应该是有效的,但我还没有找到一个客户端允许我这样做,而且我懒得把它写成纯 SMTP 发送脚本。 ### “用户名/本地部分中的点是可选的” 当我还是一个非常酷的青少年™时,我注册了 [email protected](https://gitpush--force.com/cdn-cgi/l/email-protection#11493f55706365793f5c7e7f7a74683f4951767c70787d3f727e7c),因为那正是那个时代一个拥有 Gmail beta 邀请的非常酷的青少年™会做的事。但随着时间的推移,我变懒了,在任何地方输入我的电子邮件地址时都不再使用那些点。[email protected](https://gitpush--force.com/cdn-cgi/l/email-protection#78001c190c0c10151716131d0100381f15191114561b1715)同样有效。而且当 2000 年代初 phpbb 风格的用户名审美过时后,这样做也*稍微*不那么尴尬了。 随着时间的推移,人们普遍认为这是正常、预期的行为。但是——这即将成为一个主题——RFC 5321(https://datatracker.ietf.org/doc/html/rfc5321)和其余的电子邮件 RFC 系列为服务器如何实现本地部分留下了*大量*的余地。这包括那些点。事实证明,允许发件人省略点是很常见的,但绝非普遍! 另外,不是要炫耀,但“拥有 Gmail beta 邀请的非常酷的青少年™”可能永远是我在愚蠢矛盾修辞领域最大的成就。 ### “电子邮件域名并没有那么多” 你能访问包含用户电子邮件地址的生产数据库吗?如果你的答案是肯定的,那么你的雇主很可能应该锁定你的数据库访问权限。但在他们这么做之前,试试这个查询: `` SELECT COUNT(DISTINCT SPLIT_PART(email, '@', 2)) FROM users; `` 在你把这个非常显眼的查询留在 Postgres 日志中、让 DBA 事后问你之前,你期望的结果是什么?显然,不只是几个。你有 gmail、outlook、icloud、proton、yahoo 等等。也许还有一些像 ben@gitpush–force.com(https://gitpush--force.com/cdn-cgi/l/email-protection#5a383f341a3d332e2a2f293277773c3528393f74393537)这样的怪人。 所以是几十个电子邮件提供商,或者可能一百个左右?不。如果你有相当数量的用户,你很可能会有***数千个***不同的主机名。 实际上(原文被截断,但翻译完成)

相似文章

电子邮件的未来

Hacker News Top

Fastmail探讨了AI驱动的邮件过滤和助手如何使邮件认证标准(SPF、DKIM、DMARC)成为防止伪造和网络钓鱼的关键基础设施。

停止构建自主电子邮件代理

Reddit r/AI_Agents

作者基于实际失败案例,反对构建完全自主的电子邮件代理,主张采用受限的“提议-批准”工作流,即AI准备上下文和草稿,但由人类最终批准发送。

信任却未验证:大型语言模型来源评估中的认知盲区

arXiv cs.LG

这篇论文识别了大型语言模型(LLM)中的一个失败模式:在综合多个来源时,模型不会验证数值统计的有效性,而是依赖分析严谨性的文体标记。作者将此称为“认知对齐”(epistemic alignment),并表明该现象在多个模型和领域中持续存在,且抵制基于提示的缓解措施。

匿名凭证:图解入门(第二部分)

Hacker News Top

图解入门系列的第二部分,介绍 Privacy Pass 和 Google 年龄验证提案等真实世界的匿名凭证系统,重点讲解如何防止凭证克隆,并在不牺牲用户隐私的前提下实现富有表现力的证明。