如何实现识别邮件弹回
在发送完营销邮件后,往往会存在一个需求,就是对发送的邮件进行统计分析,如发送率(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进行对比,获取到日志中对应的发送状态,就可以得到了邮件弹回的信息。