注册场景下的无效邮箱分层过滤流程图

注册场景下的无效邮箱过滤:一套低成本分层方案

用户在注册页填邮箱时,会犯一些看起来很离谱、实际上又非常常见的错误:

  • gmail.com 写成 gmail.cn
  • gmail.com 写成 gmaii.com
  • 域名前后多了空格,或输入了全角的
  • uspeedo.console@gmail.com 记成 uspeedo-console@gmail.com
  • 邮箱格式没问题,域名也真实存在,但具体账号根本不存在。

对注册系统来说,这些错误不只浪费一封验证邮件。它们还会制造硬退信,污染用户库,增加重试与客服成本。当无效地址积累到一定比例,还会拖累发信域名和 IP 的信誉。

好消息是,大多数低级错误根本不需要 SMTP 探测。几层本地规则,加一次可缓存的 DNS 查询,就能以很低的成本拦下大部分问题地址。

优质的邮件渠道供应商:uSpeedo

第一层:先做标准化,但不要擅自“修好”邮箱

用户复制邮箱时,很容易带上首尾空格。手机输入法还可能产生全角符号。这些可以在前端即时处理:

  1. 去掉首尾空格;
  2. 将全角 转为半角;
  3. 将域名转为小写;
  4. 对国际化域名做 IDNA/Punycode 处理。

但不要默默删掉中间的字符,也不要自动把 gmaii.com 改成 gmail.com。一个看起来像错别字的域名,仍然可能是真实存在的企业域名。更安全的做法是显示建议:

你填写的是 name@gmaii.com,是否想输入 name@gmail.com

让用户点击确认,比后台自作主张地改写更可靠。

第二层:用“实用语法”拦下明显错误

邮箱的标准语法很复杂。注册页没必要追求一条无所不包的巨型正则,一组容易维护的实用规则更合适:

  • 必须且只能有一个 @
  • @ 前后都不能为空;
  • 域名不能有空格、连续的点或空标签;
  • 域名每一段不能以连字符开头或结尾;
  • 检查本地部分、域名和完整地址的长度上限;
  • 产品若不支持国际化邮箱,应明确提示,不要把非 ASCII 字符一概当成“用户乱填”。

语法校验只能证明“看起来像邮箱”,不能证明它能收信。但它几乎没有网络成本,应当成为所有注册页的第一道门。

第三层:给常见邮箱域名做“拼写建议”

先维护一份与业务用户匹配的常见域名表,例如 Gmail、Outlook、Hotmail、Yahoo,再加上目标市场常用的邮箱服务商。对用户输入的域名计算 Damerau-Levenshtein 距离,即插入、删除、替换或相邻字符交换的次数。

典型情况包括:

输入可能的问题产品动作
gmaii.coml 被输成 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.comuspeedoconsole@gmail.com 会投递到同一个收件箱。但这条规则不能套用到使用 Gmail 的企业或学校域名,更不能默认所有邮箱服务商都忽略点。

服务商规则只建议覆盖头部平台,并且将规则做成可配置、可灰度、可回滚。不要在代码里堆几百家服务商的猜测。

第五层:查 DNS,判断这个域名是否具备收信条件

语法正确、拼写也不可疑,不等于域名能收邮件。后端可以按以下顺序查询:

  1. 域名不存在(NXDOMAIN),可以直接判定无效;
  2. 查询域名的 MX 记录;
  3. 如果域名声明 Null MX,直接判定为不接收邮件;
  4. 如果没有 MX,再根据邮件标准检查 A/AAAA 回退路径;存在 A/AAAA 只表示可以尝试回退投递,不能证明上面一定运行着邮件服务;
  5. 既没有 MX,也没有可用的 A/AAAA,可以判定域名不可投递;
  6. DNS 超时或 SERVFAIL 应返回 unknown,不能当成永久无效。

DNS 可以排除一部分明确不可投递的域名,却无法证明 alice@example.com 这个具体账号存在。但查询结果可以按域名缓存,对大量注册请求来说,平均成本很低。

一个可落地的判定流程

不建议只返回 truefalse。把结果拆成“状态 + 原因 + 建议”,前端提示、日志分析和后续规则调整都会轻松很多。

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探测是最精准的,但供应商对此有严格的限制。 基于此,如果想要提升域名的信誉度,除了自己可以实现的邮箱格式校验,还可以接入邮件检测能力,帮助我们提升提升发信域名信誉,降低邮件硬退信。

参考资料

Related Posts