注册场景下的无效邮箱过滤:一套低成本分层方案
USpeedo
精选干货教程
23 Jul, 2026
用户在注册页填邮箱时,会犯一些看起来很离谱、实际上又非常常见的错误:
- 把
gmail.com写成gmail.cn; - 把
gmail.com写成gmaii.com; - 域名前后多了空格,或输入了全角的
@和.; - 把
uspeedo.console@gmail.com记成uspeedo-console@gmail.com; - 邮箱格式没问题,域名也真实存在,但具体账号根本不存在。
对注册系统来说,这些错误不只浪费一封验证邮件。它们还会制造硬退信,污染用户库,增加重试与客服成本。当无效地址积累到一定比例,还会拖累发信域名和 IP 的信誉。
好消息是,大多数低级错误根本不需要 SMTP 探测。几层本地规则,加一次可缓存的 DNS 查询,就能以很低的成本拦下大部分问题地址。
优质的邮件渠道供应商:uSpeedo
第一层:先做标准化,但不要擅自“修好”邮箱
用户复制邮箱时,很容易带上首尾空格。手机输入法还可能产生全角符号。这些可以在前端即时处理:
- 去掉首尾空格;
- 将全角
@和.转为半角; - 将域名转为小写;
- 对国际化域名做 IDNA/Punycode 处理。
但不要默默删掉中间的字符,也不要自动把 gmaii.com 改成 gmail.com。一个看起来像错别字的域名,仍然可能是真实存在的企业域名。更安全的做法是显示建议:
你填写的是
name@gmaii.com,是否想输入name@gmail.com?
让用户点击确认,比后台自作主张地改写更可靠。
第二层:用“实用语法”拦下明显错误
邮箱的标准语法很复杂。注册页没必要追求一条无所不包的巨型正则,一组容易维护的实用规则更合适:
- 必须且只能有一个
@; @前后都不能为空;- 域名不能有空格、连续的点或空标签;
- 域名每一段不能以连字符开头或结尾;
- 检查本地部分、域名和完整地址的长度上限;
- 产品若不支持国际化邮箱,应明确提示,不要把非 ASCII 字符一概当成“用户乱填”。
语法校验只能证明“看起来像邮箱”,不能证明它能收信。但它几乎没有网络成本,应当成为所有注册页的第一道门。
第三层:给常见邮箱域名做“拼写建议”
先维护一份与业务用户匹配的常见域名表,例如 Gmail、Outlook、Hotmail、Yahoo,再加上目标市场常用的邮箱服务商。对用户输入的域名计算 Damerau-Levenshtein 距离,即插入、删除、替换或相邻字符交换的次数。
典型情况包括:
| 输入 | 可能的问题 | 产品动作 |
|---|---|---|
gmaii.com | l 被输成 i | 建议 gmail.com,让用户确认 |
gmial.com | 相邻字符交换 | 建议 gmail.com,让用户确认 |
gmail.con | 顶级域名误输 | 建议 gmail.com,让用户确认 |
gmail.cn | 可能选错后缀 | 结合 DNS 结果提示,不盲改 |
距离阈值不宜太宽。对短域名,一个字符的差异已经很大;对企业邮箱,更不能因为它与某个知名域名相似就直接拦截。
第四层:少量加入邮箱服务商的特定规则
有些错误符合通用邮件语法,却不符合某家邮箱服务商的账号规则。
例如,Google 公开的 Gmail 用户名规则允许字母、数字和点,但不允许连字符 -。因此,uspeedo-console@gmail.com 虽然能通过很多通用邮箱正则,却值得在注册时直接提示检查。
另一个容易误判的点是:对个人 @gmail.com 地址,Google 会忽略用户名中的点,uspeedo.console@gmail.com 和 uspeedoconsole@gmail.com 会投递到同一个收件箱。但这条规则不能套用到使用 Gmail 的企业或学校域名,更不能默认所有邮箱服务商都忽略点。
服务商规则只建议覆盖头部平台,并且将规则做成可配置、可灰度、可回滚。不要在代码里堆几百家服务商的猜测。
第五层:查 DNS,判断这个域名是否具备收信条件
语法正确、拼写也不可疑,不等于域名能收邮件。后端可以按以下顺序查询:
- 域名不存在(
NXDOMAIN),可以直接判定无效; - 查询域名的 MX 记录;
- 如果域名声明 Null MX,直接判定为不接收邮件;
- 如果没有 MX,再根据邮件标准检查 A/AAAA 回退路径;存在 A/AAAA 只表示可以尝试回退投递,不能证明上面一定运行着邮件服务;
- 既没有 MX,也没有可用的 A/AAAA,可以判定域名不可投递;
- DNS 超时或
SERVFAIL应返回unknown,不能当成永久无效。
DNS 可以排除一部分明确不可投递的域名,却无法证明 alice@example.com 这个具体账号存在。但查询结果可以按域名缓存,对大量注册请求来说,平均成本很低。
一个可落地的判定流程
不建议只返回 true 或 false。把结果拆成“状态 + 原因 + 建议”,前端提示、日志分析和后续规则调整都会轻松很多。
type EmailCheck = {
status: "valid" | "confirm" | "invalid" | "unknown";
reason?:
| "syntax_invalid"
| "provider_rule"
| "possible_typo"
| "domain_no_mail"
| "dns_temporary_failure";
normalized: string;
suggestion?: string;
};
async function checkRegistrationEmail(raw: string): Promise<EmailCheck> {
const normalized = normalizeEmailInput(raw);
if (!passesPracticalSyntax(normalized)) {
return { status: "invalid", reason: "syntax_invalid", normalized };
}
const providerIssue = checkProviderSpecificRules(normalized);
if (providerIssue) {
return { status: "confirm", reason: "provider_rule", normalized };
}
const suggestion = suggestCommonDomain(normalized);
if (suggestion) {
return {
status: "confirm",
reason: "possible_typo",
normalized,
suggestion,
};
}
const dns = await checkMailDnsWithCache(domainOf(normalized));
if (dns === "no_mail") {
return { status: "invalid", reason: "domain_no_mail", normalized };
}
if (dns === "temporary_failure") {
return { status: "unknown", reason: "dns_temporary_failure", normalized };
}
return { status: "valid", normalized };
}
在产品上,四种状态可以这样处理:
| 状态 | 前端行为 | 是否发验证邮件 |
|---|---|---|
valid | 正常进入下一步 | 是 |
confirm | 展示问题和建议,由用户确认 | 确认后发送 |
invalid | 拦截并说明原因 | 否 |
unknown | 允许谨慎继续,或稍后重试 | 按业务风险决定 |
进阶
做好这些低成本的措施可以降低邮件发送的成本,但还是有其局限性,比如
- 邮件供应商太多了,gmail.com 和mail.com,其实都是正常的邮箱,维护需要成本
- 面对临时邮箱、高风险无能为力,这些邮箱不断的更换域名后缀,不容易拦截
- SMTP探测是最精准的,但供应商对此有严格的限制。 基于此,如果想要提升域名的信誉度,除了自己可以实现的邮箱格式校验,还可以接入邮件检测能力,帮助我们提升提升发信域名信誉,降低邮件硬退信。