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

需求分析:经过分析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. Day 3:集合

    数组: 存储同一种数据类型的集合容器数组的特点: 1. 只能存储同一种数据类型的数据. 2. 一旦初始化,长度固定.  3. 数组中的元素与元素之间的内存地址是连续的. 注意: Object类型的数组 ...

  2. 提升Python编程效率的几种方法

    前言 我们知道Python这门语言在运行速度上已经败给了许多别的语言(比如C, C++, Java, Golang....).但从一个开发者的角度来看Python是我最喜欢的语言,很大一部分原因在于其 ...

  3. Filter过滤器技术详解

    前言 有这样一个常见的开发场景,我们编写一套系统,或者分析一套系统如何实现的过程中,我们肯定会发现这套系统的拦截机制.比如说京东或者淘宝之类的,存在这种拦截机制,这套拦截机制能够过滤掉哪些错误的登录注 ...

  4. xlua 原理

    基于版本 104 可以直接在lua访问c#函数原理: CS 是一个table,设置了一个__index函数,如果访问不存在的成员的时候,会走__index函数,调用import_type从C#中找到具 ...

  5. 如何用Python统计《论语》中每个字的出现次数?10行代码搞定--用计算机学国学

    编者按: 上学时听过山师王志民先生一场讲座,说每个人不论干什么,都应该学习国学(原谅我学了计算机专业)!王先生讲得很是吸引我这个工科男,可能比我的后来的那些同学听课还要认真些,当然一方面是兴趣.一方面 ...

  6. dac FDMemTable

    unit Unit1; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System ...

  7. Delphi流的操作_文件合并

    unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms ...

  8. 数组分组(DP)

    一个长度为n的数组a,我们可以把它分成任意组,每一组是一段连续的区间. 比如数组1,2,3,4,5可以分成(1,2),(3,4,5)两个组.每个分组都有一个权值,这个权值就是分组里面每个数的乘积对10 ...

  9. 吴裕雄--天生自然 JAVASCRIPT开发学习:HTML DOM 元素 (节点)

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

  10. opencv python图片编码解码

    cv2.imdecode()函数从指定的内存缓存中读取数据,并把数据转换(解码)成图像格式;主要用于从网络传输数据中恢复出图像.cv2.imencode()函数是将图片格式转换(编码)成流数据,赋值到 ...