JavaMail读取收件箱退信邮件/分析邮件附件获取Message_Id
需求描述:公司最近有个项目邮件通知功能,但是客户上传的邮件地址并不一定存在,以及其他的各种问题。所有希望发送通知后有个回执,及时发现地址存在问题的邮箱。
需求分析:经过分析JavaMail可以读取收件箱邮件,我们可以通过对应通知的退信来回写通知状态。那么问题来了,发送通知和退信如何建立映射?经过调研,最终确定采用以下方案解决。
映射方案:
- 在发送邮件通知时在Header中指定自定义的Message_Id,作为唯一标示,本系统中采用UUID。
- 定时任务扫描服务器邮箱的收件箱,本系统我们搜索收件箱中前30分钟内的主题为:“来自postmaster@net.cn的退信”,的退信邮件。
- 分析退信附件,退信关联邮件信息存在附件中,我们需要的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的更多相关文章
- SendMail发送回执及读取收件箱
一.SendMail发送有回执提示 1.邮件发送配置 Properties props = new Properties(); String smtp = "smtp.qq.com" ...
- 通什翡翠商城大站协议邮件群发系统日发20-30万封不打码不换ip不需发件箱100%进收件箱
用一种新的技术思维去群发邮件一种不用换IP,不需要任何发件箱的邮件群发方式一种不需要验证码,不需要**代码变量的邮件群发方式即使需要验证码也能全自动识别验证码的超级智能软件教你最核心的邮件群发思维和软 ...
- 懒人邮件群发日发50-100万封不打码不换IP不需发件箱大站协议系统营销软件100%进收件箱
用一种新的技术思维去群发邮件 一种不用换IP,不需要任何发件箱的邮件群发方式 一种不需要验证码,不需要**代码变量的邮件群发方式 即使需要验证码也能全自动识别验证码的超级智能软件 教你最核心的邮件群发 ...
- Android4.4 往短信收件箱中插入自定义短信(伪造短信)
这段时间稍微有点空闲,把前一段学习Android做过的一些小项目整理整理.虽然没有什么工程量很大的项目,但是对于一个新手,解决这些问题还是花了一段时间.感觉还是非常有记录的意义呢~~~么么哒*—* 今 ...
- android 访问SMS短信收件箱
访问 SMS收件箱是另一个常见的需求.首先,需要将读取 SMS 的权限 <uses-permission android:name="android.permission.READ ...
- [C#]exchange发送,收件箱操作类
最近项目中需要用到exchange的操作,就参照msdn弄了一个简单的操作类.目前先实现了,发送邮件和拉取收件箱的功能,其他的以后在慢慢的添加. using Microsoft.Exchange.We ...
- 【排障】Outlook Express 2G收件箱大小限制
Outlook Express 2G收件箱大小限制 文:铁乐猫 ----------------------------- Outlook Express(以下简称OE)客户端收件箱大于或接近2G时, ...
- Win10 收件箱添加QQ邮箱(2019年5月19日)
Emmm弄的时候没截图,就语言描述吧,非常简单. 登录到网页端QQ邮箱.点我登录 登录之后,界面上端的Logo右边有个"设置"(字有点小).点它 邮箱设置下面有一堆标签,点击&qu ...
- AKKA Inbox收件箱
起因 得到ActorRef就可以给actor发送消息,但无法接收多回复,也不知道actor是否停止 Inbox收件箱出现就是解决这两个问题 示例 package akka.demo.actor imp ...
随机推荐
- Android群英传知识点回顾——第十章:Android性能优化
10.1 布局优化 10.1.1 Android UI渲染机制 10.1.2 避免Overdraw 10.1.3 优化布局层级 10.1.4 避免嵌套过多无用布局 10.1.5 Hierarchy V ...
- Day 12:枚举值、枚举类
jdk1.5新特性之-----枚举 问题:某些方法所接收的数据必须是在固定范围之内的, 解决方案: 这时候我们的解决方案就是自定义一个类,然后是私有化构造函数,在自定义类中创建本类的对象对外使用. ...
- python数组定义
转自:https://blog.csdn.net/minsenwu/article/details/7872679 Python中没有数组的数据结构,但列表很像数组,如: a=[0,1,2],这时a[ ...
- openstack trove weekly meeting时间即将更改
为了平衡英国.巴黎.德国.美国和中国开发者的作息习惯,openstack trove项目组在5月18日的weekly meeting上开始讨论新的开会时间. 当前的开会时间是,周三 UTC 18:00 ...
- 题解 Luogu P2499: [SDOI2012]象棋
关于这道题, 我们可以发现移动顺序不会改变答案, 具体来说, 我们有以下引理成立: 对于一个移动过程中的任意一个移动, 若其到达的位置上有一个棋子, 则该方案要么不能将所有棋子移动到最终位置, 要么可 ...
- FTP服务器 vsftp samba服务器 共享 smb
FTP服务器 vsftp samba服务器 共享 smb 马哥视频 参考1 参考3 参考2 参考4 vsftp服务器实现匿名用户上传.修改权限和一些设置 win7访问 地址栏输入 ftp://账号 ...
- UVA_10603 倒水问题 隐式图搜索
这道题目是刘汝佳白书上的例题,没有LRJ在白书上提到的划归为搜索问题,还真是一时难以想到好的解法.即三个瓶子,任意的一个状态都是一个节点,最后就划归为一个搜索问题. 由于题目数据量不大,三个杯子容量都 ...
- jq切换选择项
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- LeetCode No.166,167,168
No.166 FractionToDecimal 分数到小数 题目 给定两个整数,分别表示分数的分子 numerator 和分母 denominator,以字符串形式返回小数. 如果小数部分为循环小数 ...
- MyBatis+SpringMVC 框架搭建小结
前言:最近再写一款视频播放器的后台,踩了很多坑,在此总结. 设计顺序: 前提:搭建配置完好的Spring-MyBatis项目 1.流程分析,数据库设计(看似无用,真正做起来真的需要这个东西帮忙整理下思 ...