前言

在国外,大家更加倾向于使用邮件进行沟通,而不像国内,钉钉微信一顿轰炸。而且在国外,最常见和有效的营销方式还是EDM,做EDM过程中经常会遇到的一个问题就是如何验证一个电子邮箱地址的真实性?发送一封含有验证链接的邮件,这在网站注册时验证用户是很有效的,但并不适合EDM中大规模的邮件验证。接下来介绍一下基于SMTP协议验证邮箱有效性的基本原理,和具体的实现。

SMTP

简单邮件传输协议(Simple Mail Transfer Protocol,SMTP)是在Internet传输电子邮件事实标准

SMTP是一个相对简单的基于文本协议。在其之上指定了一条消息的一个或多个接收者(在大多数情况下被确认是存在的),然后消息文本会被传输。可以很简单地通过telnet程序来测试一个SMTP服务器。SMTP使用TCP端口25。要为一个给定的域名决定一个SMTP服务器,需要使用MX (Mail eXchange) DNS

基于 SMTP,我们可以使用如下的验证思路:

  1. 查找邮箱域名的 MX 记录,即查找邮箱域名所对应的 SMTP 服务器地址
  2. 与该服务器建立连接
  3. 尝试向需要验证的邮箱发送邮件
  4. 根据返回结果判定邮箱地址的真实性

注意:

有些公司将邮箱服务设置为 CATCH ALL, 这意味着该域名下的每个邮箱地址,都会被认为是存在的。

验证过程

我们以 not.exists@hotmail.com,在 centos 系统下,为例来说明整个流程。

0. 查找 MX 记录

我们使用 nslookup 查找对应域名的的 MX记录,此例中是 hotmail.com。如下:

> nslookup -type=MX hotmail.com

Server:     10.202.72.118
Address:    10.202.72.118#53

Non-authoritative answer:
hotmail.com mail exchanger = 2 hotmail-com.olc.protection.outlook.com.

Authoritative answers can be found from:

只找到了一条 MX 记录,那么 SMTP 服务器地址为 hotmail-com.olc.protection.outlook.com

1. 建立连接

之前我们在维基百科的定义中可以看到,SMTP默认是TCP端口25,我们可以使用 telnet命令进行TCP的连接

> telnet hotmail-com.olc.protection.outlook.com 25

Trying 104.47.45.33...
Connected to hotmail-com.olc.protection.outlook.com.
Escape character is '^]'.
220 CO1NAM04FT042.mail.protection.outlook.com Microsoft ESMTP MAIL Service ready at Thu, 7 Mar 2019 08:59:22 +0000

看到 220 的状态响应码就代表我们连接成功了。

2. 使用 SMTP 命令校验邮件

整个校验过程中,我们会使用到 HELO 或者 EHLOMAIL FROMRCPT TO 这四个命令。

2.1 首先使用 HELO 或者 EHLO 向服务器表名邮件发送的服务器

> HELO gmail.com
250 BY2NAM03FT057.mail.protection.outlook.com Hello [xx.xx.xx.xx]
# 或者使用 EHLO
> EHLO gmail.com
250-DM3NAM05FT023.mail.protection.outlook.com Hello [xx.xx.xx.xx]
250-SIZE 49283072
250-PIPELINING
250-DSN
250-ENHANCEDSTATUSCODES
250-STARTTLS
250-8BITMIME
250-BINARYMIME
250-CHUNKING
250 SMTPUTF8

服务端相应 250 状态码,这一步就成功了。

2.2 使用 MAIL FROM 表明发件人

$ MAIL FROM:<test@gmail.com>
MAIL FROM:<test@gmail.com>

2.3 使用 RCPT TO 测试邮件地址

如果 RCPT TO 的响应码是 250 或者 251 都表示邮件地址存在,如果响应吗是 5xx,则表明邮件地址不存在,如果是 4xx 则代表无法确认。

$ RCPT TO:<not.exists@hotmail.com>
250 2.1.5 Recipient OK

什么?!咱们觉得肯定不存在的地址经常可以校验通过。别急,还记得文章开头提到的CATCH ALL吗?实际上,hotmail 正是做了这种配置。我们再用 nicaicaizhegeyouxiangshicunzaidema@hotmail.com 测试下:

$ RCPT TO:<nicaicaizhegeyouxiangshicunzaidema@hotmail.com>
250 2.1.5 Recipient OK

响应码依然是 250。由此我们基本可以断定,所有的 Hotmail 的邮箱地址,使用这种方法都会校验通过的。

2.4 使用 QUIT 退出连接

$ QUIT

Python 实现

我们使用 python 来实现以下上述的流程,需要安装dnspython

首先是 MX 记录的查找,我们以 gmail.com 为例

from dns import resolver
import pprint
def find_mx_record(*, host):
    """找到指定域名的 MX 记录
    """
    try:
        mx_records = [record.to_text().split() for record in resolver.query(host, 'MX')]
    except (resolver.NoAnswer, resolver.NXDOMAIN, resolver.NoNameservers):
        mx_records = []
    return mx_records

if __name__ == "__main__":
    pprint.pprint(find_mx_record(host='gmail.com'))

"""输出结果为
[['20', 'alt2.gmail-smtp-in.l.google.com.'],
 ['40', 'alt4.gmail-smtp-in.l.google.com.'],
 ['10', 'alt1.gmail-smtp-in.l.google.com.'],
 ['5', 'gmail-smtp-in.l.google.com.'],
 ['30', 'alt3.gmail-smtp-in.l.google.com.']]
"""

然后我们使用内置的 smtplib 库来进行邮件的校验:

from smtplib import SMTP, SMTPServerDisconnected
import pprint

def verify(email, smtp_host):
    """校验email的地址是否真实存在
    """
    try:
        with SMTP(smtp_host) as smtp:
            # send the HELO command
            smtp.helo()
            # send the MAIL FROM
            smtp.mail('test@hotmail.com') 
            # send the RCPT TO
            resp = smtp.rcpt(email)
            # check the status code
            return resp[0] == 250 or resp[0] == 251
    except SMTPServerDisconnected:
        return False

if __name__ == "__main__":
    pprint.pprint(verify("cmsdnfnskdnfkds@gmail.com", 'alt2.gmail-smtp-in.l.google.com'))
    pprint.pprint(verify("xupengfei806@gmail.com", 'alt2.gmail-smtp-in.l.google.com'))

"""输出结果为
False
True
"""

Github 地址:email_verifier.py

其实,关于Gmail的邮箱的校验,我们可以通过别的办法,具体参见另一篇文章 关于 Gmail 邮件地址的校验

参考链接