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

需求分析:经过分析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. 洛谷 P1220 关路灯(区间dp,前缀和)

    传送门 解题思路 先明确一下题意,c指的是路灯的编号而不是位置. 然后根据贪心,在从点i去关点j的路灯时,所有经过的路灯都会随手关掉(不耗时间),所以我们可以确定,若i点和j点的路灯已经关闭,那么区间 ...

  2. POJ 1519:Digital Roots

    Digital Roots Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 25766   Accepted: 8621 De ...

  3. Python爬虫连载1-urllib.request和chardet包使用方式

    一.参考资料 1.<Python网络数据采集>图灵工业出版社 2.<精通Python爬虫框架Scrapy>人民邮电出版社 3.[Scrapy官方教程](http://scrap ...

  4. 线段树--线段树【模板1】P3372

    题目描述 如题,已知一个数列,你需要进行下面两种操作: 1.将某区间每一个数加上x 2.求出某区间每一个数的和 输入格式 第一行包含两个整数N.M,分别表示该数列数字的个数和操作的总个数. 第二行包含 ...

  5. hdu 3483 矩阵乘法

    这个题目上周对抗赛题目,搞了我好久 对数学这种不是很敏感 其实都不是自己想出来的,看其他的资料和博客的推导 还是有点难度的,反正我是推不出来 通过二项式定理的化简 有两个博客写得比较好 http:// ...

  6. tp5 输入域名即访问指定页面

    遇到PC官网类型的项目,经常会遇到隐藏入口文件和输入域名即可打开官网首页的需求.需要修改站点的默认加载文件和伪静态的配置才可以生效. 以下为nginx1.15版本,宝塔面板的修改方式.修改入口文件为w ...

  7. Linux(CENTOS7) Redis安装

    1.下载redis         在disk目录下,输入以下命令进行下载: wget http://download.redis.io/releases/redis-2.8.3.tar.gz 2.解 ...

  8. ADB 用法大全 【转】

    https://github.com/mzlogin/awesome-adb awesome-adb ADB,即 Android Debug Bridge,它是 Android 开发/测试人员不可替代 ...

  9. 吴裕雄--天生自然 PHP开发学习:表单验证

    <!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <title> ...

  10. PAT Advanced 1029 Median (25) [two pointers]

    题目 Given an increasing sequence S of N integers, the median is the number at the middle position. Fo ...