如何实现识别邮件弹回

如何实现识别邮件弹回

在发送完营销邮件后,往往会存在一个需求,就是对发送的邮件进行统计分析,如发送率(delivered)、打开率(opened)、点击率(clicked)、弹回率(bounced)等等。发送率和打开率都相对比较容易进行统计,前者可以很容易地获得,后者也只需要在邮件中附加一个像素级别的图片,并将其中的src指向服务器的特定服务,从而进行统计。而点击率在本文中暂时不予考虑,我们重点讨论的是如何进行弹回的识别与统计。

在前文中(《使用Rabbitmq实现营销邮件的异步发送》)曾经提到邮件发送有两种方式,其中较为灵活的方式如下:

    // 配置发送邮件的环境属性
    final Properties props = new Properties();

    // 表示SMTP发送邮件,需要进行身份验证
    props.put("mail.smtp.auth", "true");
    props.put("mail.smtp.host", smtpServer);
    //加密方式:
    props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
    props.put("mail.smtp.socketFactory.port", smtpPort);
    props.put("mail.smtp.port", smtpPort);
    props.put("mail.smtp.from", mailSender);
    props.put("mail.user", mailSender);
    props.put("mail.password", password);
    props.setProperty("mail.smtp.ssl.enable", "true");

    // 构建授权信息,用于进行SMTP进行身份验证
    Authenticator authenticator = new Authenticator() {
        @Override
        protected PasswordAuthentication getPasswordAuthentication() {
            // 用户名、密码
            String userName = props.getProperty("mail.user");
            String password = props.getProperty("mail.password");
            return new PasswordAuthentication(userName, password);
        }
    };

    // 使用环境属性和授权信息,创建邮件会话
    Session mailSession = Session.getInstance(props, authenticator);
    final String messageIDValue = genMessageID(props.getProperty("mail.user"));
    //创建邮件消息
    MimeMessage message = new MimeMessage(mailSession) {
        @Override
        protected void updateMessageID() throws MessagingException {
            //设置自定义Message-ID值
            setHeader("Message-ID", messageIDValue);//创建Message-ID
        }
    };

    try {
        InternetAddress from = new InternetAddress(mailSender, mailSender);
        message.setFrom(from);
        message.setSentDate(new Date()); // 设置时间
        //设置邮件标题
        message.setSubject(subject);
        message.setContent(content, "text/html");
        message.setRecipients(Message.RecipientType.TO, to);
        // 发送邮件
        Transport.send(message);
    } catch (MessagingException | UnsupportedEncodingException e) {
        e.printStackTrace();
        throw new CusobException(ResultCodeEnum.EMAIL_SEND_FAIL);
    }
    System.out.println("success");
}

最开始我理解的是,如果Transport.send(message);发送失败,出现弹回和退信,那么应当会抛出一个异常。只要捕获这个异常并进行统计,就可以轻松地获得弹回的信息了。但是,这是我的一厢情愿,实际上,在我进行测试时发现,即使收件方是一个不存在的邮件,也不会在此处报任何错误,一切都是完全正确的!但是postfix的日志却很明确地告诉我,这封信被退回了,以下是给出地信息:

mail postfix/smtp[426704]: 03A66804AF: to=<[email protected]>, relay=mx3.qq.com[203.205.219.57]:25, delay=3.6, delays=0.92/0.01/1.7/0.97, dsn=5.0.0, status=bounced (host mx3.qq.com[203.205.219.57] said: 550 Mailbox unavailable or access denied [MEhaAuZRBnt9frB3Ea7HxDO1qqeQDLfD0TMRsEwumfowzjSEM7Wyc7PmllTuytRf/w== IP: 50.116.19.239]. https://service.mail.qq.com/detail/0/166. (in reply to end of DATA command))

其表明,[email protected]这个邮箱并不存在,发送状态显示为bounced。现在问题就变得比较棘手了,这种情况的错误作为邮件服务提供商是一定要去处理和解决的,如果联系人的列表中存在大量这样的邮箱,会很大程度影响服务的稳定性和信誉度。关于这个问题网上也有一些人进行询问,但几乎没有人有过合适的解答。目前经过我的调研,我已基本理清大致的思路:想要统计弹回的信息,就必须要与自己的邮件服务器进行交互,如果邮件服务器依赖于其他供应商,那么该服务也必须依赖于这个供应商,否则是无法实现的。

由于postfix是以docker形式运行的,获取其日志通常采用

docker logs -f container-id

指令来获得。如果我们能够使用一些方案或工具来维护这个日志,使得能够使用数据库或Java来与之进行交互,那么这个问题就变得可解决了。我了解到的是Filebeat:

Filebeat 是一个轻量级的日志收集器,属于 Elastic Stack(通常称为 ELK Stack,包含 Elasticsearch、Logstash 和 Kibana)的一部分。它的主要功能是从不同的来源收集日志数据,并将其传输到指定的输出目的地,例如 Elasticsearch 或 Logstash。

Elasticsearch 是一个开源的分布式搜索和分析引擎,基于 Apache Lucene 构建。它被设计用于实时搜索和数据分析,并在处理大规模数据时具有高效、快速和可靠的性能。Elasticsearch 广泛应用于日志分析、全文搜索、商业智能、数据可视化等领域。 我们可以使用Elasticsearch来进行日志分析,在邮件头中有一个唯一的参数为MessageId:

可以通过获取到Message-ID,与日志中的MessageID进行对比,获取到日志中对应的发送状态,就可以得到了邮件弹回的信息。

 

留下回复