验证邮件的真实性

验证邮件的真实性

在发送邮件时,邮件接收方的服务器会考量发送方的邮件服务器的IP信誉,信誉越高越有可能到达收件箱,而不是被认定为垃圾邮件。因此,维护良好的信誉对于邮件服务提供商来说是至关重要的。有很多因素都影响着服务器的IP信誉,诸如投诉率、举报为垃圾邮件的数量,以及硬弹回的频率等等。这里硬弹回是指该邮件不存在或不可用,无法交付,从而导致退信的行为,这样的行为会导致IP信誉的下降,因为你不能有效地管理你的联系人发送列表,及时地清除那些无效的邮件地址。那么,我们就产生了一种需求,能否在添加时联系人时,就验证该邮件的真实性?经过我的调研,首先给出结论:能够一定程度上进行验证,但想要完全实现目前而言几乎不可能,因为其中涉及了一些安全性问题。

1.验证一个邮箱是否存在,首先先看其域名是否指向了某个邮件服务器,否则,该域名是无法进行邮件发送的,也谈不上该邮件是否存在了。判断的步骤也比较简单,去查询其MX记录即可。如果MX记录为空,表明其并不具备发送邮件的资质,当然应该认为不存在。

2.接下来是较为复杂的第二步,比较直观的思路是,发送一封邮件,如果返回错误码为550则表明该邮件不可用或不存在。但当然我们不能直接去这么尝试,否则验证邮件真实性的这个操作也没有意义了。而想要获取用户是否存在的信息,只能从目标服务器中去获取信息,我的实现如下:

@RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = MqConst.QUEUE_CHECK_EMAIL, durable = "true"),
        exchange = @Exchange(value = MqConst.EXCHANGE_CHECK_DIRECT),
        key = {MqConst.ROUTING_CHECK_EMAIL}
))
public void checkEmail(Contact contact, Message message, Channel channel) throws IOException {

    String email = contact.getEmail();
    Long groupId = contact.getGroupId();
    String domain = email.split("@")[1];
    boolean check = true;
    try {
        //检查MX记录
        Record[] records = new Lookup(domain, Type.MX).run();
        if (!(records != null && records.length > 0)) {
            check = false;
        }else {
            for (Record record : records) {
                MXRecord mxRecord = (MXRecord)record;
                //连接
                Socket socket = new Socket(mxRecord.getTarget().toString(), 25);
                BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                PrintWriter writer = new PrintWriter(socket.getOutputStream(), true) ;

                readResponse(reader);
                //握手
                sendCommand(writer, "HELO " + domain);
                readResponse(reader);
                //身份
                sendCommand(writer, "MAIL FROM:<verify@" + domain + ">");
                readResponse(reader);
                //验证
                sendCommand(writer, "RCPT TO:<" + email + ">");
                String response = readResponse(reader);

                // 断开
                if (response.startsWith("250") ) {
                    sendCommand(writer, "QUIT");
                    break;
                } else if (response.startsWith("550")) {
                    sendCommand(writer, "QUIT");
                    check = false;
                }
            }
        }
        contactService.updateByEmail(email, groupId,contact.getUserId(), check ? 1 : 0);
    } catch (Exception e) {
        contactService.updateByEmail(email, groupId,contact.getUserId(),1);
        //如果抛异常,可能是无法连接到相应的SMTP服务器,无法用这种方式判断存不存在,则先按存在处理
    }finally {
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }

}

该方法是在添加联系人后调用的,去异步地判断该邮箱是否可用,从而避免用户过长时间的等待。第一步当然是先获取MX记录,并对每个MX记录进行轮询处理,在每次轮询中:
1.使用HELO example.com 进行握手。
2.使用MAIL FROM:<example> 指定发件人。
3.使用RCPT TO:<email> 指定收件方。
4.如果返回的结果为250,表明该邮件是可交付的,如果为550,则表明该邮箱不存在,通过这种方式,能够完成对邮箱是否可交付的验证。
注意这里讨论的是可交付,而非邮件是否真实存在。你在使用该系列指令去访问目标邮件服务器的时候,需要知道这些是它们返回给你的结果,那么它们当然能够决定返回的内容,很多邮箱为了安全考虑,很多时候对返回的是250,哪怕用户并不真实存在,这也是无法控制的。

我在使用第三方的所谓的验证邮件真实性的API工具时,发现其很多不存在的邮箱也是无法完全识别出来的。我在使用其他营销邮件发送时,如果故意填一些和真实邮件类似但实际并不存在的用户时,其也不会立刻显示错误,而是等到发送邮件失败时才显示出现硬弹回错误,这表明哪怕是比较成熟的邮件服务提供商,也无法完全在不发送邮件的情况下就能够知晓一个邮件是否真实存在。

留下回复