需求描述:公司最近有个项目邮件通知功能,但是客户上传的邮件地址并不一定存在,以及其他的各种问题。所有希望发送通知后有个回执,及时发现地址存在问题的邮箱。

需求分析:经过分析JavaMail可以读取收件箱邮件,我们可以通过对应通知的退信来回写通知状态。那么问题来了,发送通知和退信如何建立映射?经过调研,最终确定采用以下方案解决。

映射方案:

  1. 在发送邮件通知时在Header中指定自定义的Message_Id,作为唯一标示,本系统中采用UUID。
  2. 定时任务扫描服务器邮箱的收件箱,本系统我们搜索收件箱中前30分钟内的主题为:“来自postmaster@net.cn的退信”,的退信邮件。
  3. 分析退信附件,退信关联邮件信息存在附件中,我们需要的Message_Id也在其中,解析附件获取Message_Id回写通知状态。

核心代码:

邮件搜索

 package com.yinghuo.yingxinxin.notification.service;

 import com.yinghuo.yingxinxin.notification.domain.PayrollNotificationEntity;
import com.yinghuo.yingxinxin.notification.domain.valobj.EmailNotificationStatus;
import com.yinghuo.yingxinxin.notification.repository.NotificationRepository;
import com.yinghuo.yingxinxin.notification.util.DateUtil;
import com.yinghuo.yingxinxin.notification.util.EmailUtil;
import com.yinghuo.yingxinxin.notification.util.StringUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import javax.mail.*;
import javax.mail.search.AndTerm;
import javax.mail.search.ComparisonTerm;
import javax.mail.search.SearchTerm;
import javax.mail.search.SentDateTerm;
import javax.mail.search.SubjectTerm;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.Properties; @Service
@Slf4j
@Data
@ConfigurationProperties(prefix = "spring.mail")
public class EmailBounceScanService {
private final static String subjectKeyword = "来自postmaster@net.cn的退信"; private String popHost;
private String username;
private String password;
private Integer timeOffset;
private final NotificationRepository payrollSendRecordRepository; private Properties buildInboxProperties() {
Properties properties = new Properties();
properties.setProperty("mail.store.protocol", "pop3");
properties.setProperty("mail.pop3.host", popHost);
properties.setProperty("mail.pop3.auth", "true");
properties.setProperty("mail.pop3.default-encoding", "UTF-8");
return properties;
} public void searchInboxEmail() {
Session session = Session.getInstance(this.buildInboxProperties());
Store store = null;
Folder receiveFolder = null;
try {
store = session.getStore("pop3");
store.connect(username, password);
receiveFolder = store.getFolder("inbox");
receiveFolder.open(Folder.READ_ONLY); int messageCount = receiveFolder.getMessageCount();
if (messageCount > 0) {
Date now = Calendar.getInstance().getTime();
Date timeOffsetAgo = DateUtil.nextXMinute(now, timeOffset);
SearchTerm comparisonTermGe = new SentDateTerm(ComparisonTerm.GE, timeOffsetAgo);
SearchTerm search = new AndTerm(new SubjectTerm(subjectKeyword), comparisonTermGe); Message[] messages = receiveFolder.search(search);
if (messages.length == 0) {
log.info("No bounce email was found.");
return;
}
this.messageHandler(messages);
}
} catch (MessagingException e) {
log.error("Exception in searchInboxEmail {}", ExceptionUtils.getFullStackTrace(e));
e.printStackTrace();
} finally {
try {
if (receiveFolder != null) {
receiveFolder.close(true);
}
if (store != null) {
store.close();
}
} catch (MessagingException e) {
log.error("Exception in searchInboxEmail {}", ExceptionUtils.getFullStackTrace(e));
e.printStackTrace();
}
}
} @Transactional
public void messageHandler(Message[] messageArray) {
Arrays.stream(messageArray).filter(EmailUtil::isContainAttachment).forEach((message -> {
String messageId = null;
try {
messageId = EmailUtil.getMessageId(message);
} catch (Exception e) {
log.error("getMessageId:", ExceptionUtils.getFullStackTrace(e));
e.printStackTrace();
}
if (StringUtil.isEmpty(messageId)) return; PayrollNotificationEntity payrollNotificationEntity = payrollSendRecordRepository.findFirstByMessageId(messageId);
if (payrollNotificationEntity == null || EmailNotificationStatus.BOUNCE.getStatus() == payrollNotificationEntity.getStatus()) {
log.warn("not found payrollNotificationEntity by messageId:{}", messageId);
return;
} payrollNotificationEntity.setStatus(EmailNotificationStatus.BOUNCE.getStatus());
payrollNotificationEntity.setErrorMessage(EmailNotificationStatus.BOUNCE.getErrorMessage());
payrollSendRecordRepository.save(payrollNotificationEntity);
}));
}
}

附件解析

 package com.yinghuo.yingxinxin.notification.util;

 import lombok.extern.slf4j.Slf4j;

 import javax.mail.BodyPart;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Part;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader; @Slf4j
public final class EmailUtil {
private static final String multipart = "multipart/*"; public static String getMessageId(Part part) throws Exception {
if (!part.isMimeType(multipart)) {
return "";
} Multipart multipart = (Multipart) part.getContent();
for (int i = 0; i < multipart.getCount(); i++) {
BodyPart bodyPart = multipart.getBodyPart(i); if (part.isMimeType("message/rfc822")) {
return getMessageId((Part) part.getContent());
}
InputStream inputStream = bodyPart.getInputStream(); try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) {
String strLine;
while ((strLine = br.readLine()) != null) {
if (strLine.startsWith("Message_Id:")) {
String[] split = strLine.split("Message_Id:");
return split.length > 1 ? split[1].trim() : null;
}
}
}
} return "";
} public static boolean isContainAttachment(Part part) {
boolean attachFlag = false;
try {
if (part.isMimeType(multipart)) {
Multipart mp = (Multipart) part.getContent();
for (int i = 0; i < mp.getCount(); i++) {
BodyPart mpart = mp.getBodyPart(i);
String disposition = mpart.getDisposition();
if ((disposition != null) && ((disposition.equals(Part.ATTACHMENT)) || (disposition.equals(Part.INLINE))))
attachFlag = true;
else if (mpart.isMimeType(multipart)) {
attachFlag = isContainAttachment((Part) mpart);
} else {
String contype = mpart.getContentType();
if (contype.toLowerCase().contains("application"))
attachFlag = true;
if (contype.toLowerCase().contains("name"))
attachFlag = true;
}
}
} else if (part.isMimeType("message/rfc822")) {
attachFlag = isContainAttachment((Part) part.getContent());
}
} catch (MessagingException | IOException e) {
e.printStackTrace();
}
return attachFlag;
}
}

JavaMail读取收件箱退信邮件/分析邮件附件获取Message_Id的更多相关文章

  1. SendMail发送回执及读取收件箱

    一.SendMail发送有回执提示 1.邮件发送配置 Properties props = new Properties(); String smtp = "smtp.qq.com" ...

  2. 通什翡翠商城大站协议邮件群发系统日发20-30万封不打码不换ip不需发件箱100%进收件箱

    用一种新的技术思维去群发邮件一种不用换IP,不需要任何发件箱的邮件群发方式一种不需要验证码,不需要**代码变量的邮件群发方式即使需要验证码也能全自动识别验证码的超级智能软件教你最核心的邮件群发思维和软 ...

  3. 懒人邮件群发日发50-100万封不打码不换IP不需发件箱大站协议系统营销软件100%进收件箱

    用一种新的技术思维去群发邮件 一种不用换IP,不需要任何发件箱的邮件群发方式 一种不需要验证码,不需要**代码变量的邮件群发方式 即使需要验证码也能全自动识别验证码的超级智能软件 教你最核心的邮件群发 ...

  4. Android4.4 往短信收件箱中插入自定义短信(伪造短信)

    这段时间稍微有点空闲,把前一段学习Android做过的一些小项目整理整理.虽然没有什么工程量很大的项目,但是对于一个新手,解决这些问题还是花了一段时间.感觉还是非常有记录的意义呢~~~么么哒*—* 今 ...

  5. android 访问SMS短信收件箱

    访问 SMS收件箱是另一个常见的需求.首先,需要将读取 SMS 的权限   <uses-permission android:name="android.permission.READ ...

  6. [C#]exchange发送,收件箱操作类

    最近项目中需要用到exchange的操作,就参照msdn弄了一个简单的操作类.目前先实现了,发送邮件和拉取收件箱的功能,其他的以后在慢慢的添加. using Microsoft.Exchange.We ...

  7. 【排障】Outlook Express 2G收件箱大小限制

    Outlook Express 2G收件箱大小限制 文:铁乐猫 ----------------------------- Outlook Express(以下简称OE)客户端收件箱大于或接近2G时, ...

  8. Win10 收件箱添加QQ邮箱(2019年5月19日)

    Emmm弄的时候没截图,就语言描述吧,非常简单. 登录到网页端QQ邮箱.点我登录 登录之后,界面上端的Logo右边有个"设置"(字有点小).点它 邮箱设置下面有一堆标签,点击&qu ...

  9. AKKA Inbox收件箱

    起因 得到ActorRef就可以给actor发送消息,但无法接收多回复,也不知道actor是否停止 Inbox收件箱出现就是解决这两个问题 示例 package akka.demo.actor imp ...

随机推荐

  1. 《打造扛得住的MySQL数据库架构》第7章 SQL查询优化

    SQL查询优化 7-1 获取有性能问题SQL的三种方法 如何设计最优的数据库表结构 如何建立最好的索引 如何拓展数据库的查询 查询优化,索引优化,库表结构优化 如何获取有性能问题的SQL 1.通过测试 ...

  2. java中执行javascript案例

    Nashorn js engine官方文档 https://docs.oracle.com/javase/7/docs/technotes/guides/scripting/programmer_gu ...

  3. 7.3 使用while 循环来处理列表和字典

    # 7.3.1 在列表之间移动元素 # confirmed_users.py # 首先,创建一个待验证用户列表 # 和一个用于存储已验证用户的空列表 uncomfirmed_users = ['jam ...

  4. MySQL数据库索引常见问题

    笔者看过很多数据库相关方面的面试题,但大多数答案都不太准确,因此决定在自己blog进行一个总结. Q1:数据库有哪些索引?优缺点是什么? 1.B树索引:大多数数据库采用的索引(innoDB采用的是b+ ...

  5. PAT-树-DFS-BFS相关问题解决方案整理

    如何建树? 二叉树-建树-方式一 dfs使用root左右指针建立树节点关系,返回根节点root 二叉树-建树-方式二 dfs使用二维数组,int nds[n][2],如:nds[i][0]表示i节点的 ...

  6. 寒假day17-本周计划

    完善人才的数据挖掘模块 结合当下疫情完成人才动态模块 修正人才标签部分 优化界面

  7. h5-其他伪元素

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  8. html属性,上传图片选择时只显示图片文件

    这个实现比较简单,就是用到accept属性: 注意这里我们对这个file元素进行了隐藏,因为它默认呈现是下面这个样子的,并不好看. <div style="display:none;& ...

  9. java实现图片文件与Base64的互转

    通过form表单上传图片时,有时候web容器对文件大小的限制会影响我们上传.这时,前端页面可以考虑将图片转换成base64串来实现上传. 图片与Base64的互转,其实就是利用了文件流与Base64的 ...

  10. vue-router HTML5 History 模式(转自官网)

    vue-router 默认 hash 模式 -- 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载. 如果不想要很丑的 hash,我们可以用路由的 his ...