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 ...
随机推荐
- POJ - 2976 Dropping tests(01分数规划---二分(最大化平均值))
题意:有n组ai和bi,要求去掉k组,使下式值最大. 分析: 1.此题是典型的01分数规划. 01分数规划:给定两个数组,a[i]表示选取i的可以得到的价值,b[i]表示选取i的代价.x[i]=1代表 ...
- you-get使用
1.pip install you-get 2.如果出错 查看错误bug you-get http://www.iqiyi.com/v_19rrnqxz7k.html#vfrm=2-4-0-1 ...
- 多线程之间通讯JDK1.5-Lock
synchronized:代码开始上锁,代码结束时释放锁:内置锁.自动化的.效率低.扩展性不高(不够灵活): JDK1.5并发包Lock锁 --保证线程安全问题,属于手动挡,手动开始上锁,手动释放锁, ...
- dd if= of= MBR
1.备份分区表信息 sudo fdisk -l >hda.txt #分区表信息重定向输出到文件中 2.备份MBR sudo dd if=/dev/sda of=mbr bs=512 count ...
- mac item2自定义光标移动快捷键,移动行首行尾,按单词跳转
To jump between words and start/end of lines in iTerm2 follow these steps: iTerm2 -> Preferences ...
- SQL基础教程(第2版)第2章 查询基础:2-2 算数运算符和比较运算符&2-3 逻辑运算符
● 包含NULL的运算,其结果也是NULL. ● 判断是否为NULL,需要使用IS NULL或者IS NOT NULL运算符. ■算术运算符 ■需要注意NULL ■比较运算符 这些比较运算符可以对字符 ...
- Django1.11序列化与反序列化
django序列化与反序列化 from rest_framwork import serializers serializers.ModelSerializer 模型类序列化器,必须依据模型类创建序列 ...
- ssh 账号密码登录设置
找到/etc/ssh/sshd_config文件中的 PasswordAuthentication no 改为PasswordAuthentication yes 并保存. 重启ssh服务:sudo ...
- ModernCNN
# 深度卷积神经网络(AlexNet) LeNet: 在大的真实数据集上的表现并不尽如⼈意. 1.神经网络计算复杂. 2.还没有⼤量深⼊研究参数初始化和⾮凸优化算法等诸多领域. 机器学习的特征提取:手 ...
- DB---WAL技术
摘要:这个WAL技术也需要加以整理,即 write-ahead logging (预写式日志),待整理...