邮件群发问题

  在做邮件发送时遇到一个问题以及解决方式,记录一下。

  我们公司的邮箱是Coremail的企业邮箱,群发邮件时发现,如果有一个邮箱地址不存在,那么本次群发会失败;而群发其他的邮箱时如qq邮箱,163邮箱如果有一个地址不存在,那么本次发送还是成功的。所以猜测可能与邮箱服务器有关。

  为了解决给北纬邮箱群发的问题,我网上查了一下,看了一下 java mail 的 api,发现发送邮件调用 send 方法时,失败会抛出异常 javax.mail.SendFailedException,而这个异常里面封装了三个变量:
9c0c71bf00ea4f508c5250b8eb6073db.png
  invalid 是不存在的邮箱地址,validSent 是已发送的邮箱地址,validUnsent 是未发送成功的邮箱地址,因此思路就是捕获该异常,将不存在的邮箱地址去除后再次发送即可。简单的封装了一下 java mail:

  

  1 import javax.activation.DataHandler;
  2 import javax.activation.FileDataSource;
  3 import javax.mail.*;
  4 import javax.mail.internet.*;
  5 import java.io.File;
  6 import java.io.UnsupportedEncodingException;
  7 import java.util.LinkedList;
  8 import java.util.List;
  9 import java.util.Properties;
 10 
 11 /**
 12  * 邮件发送
 13  *
 14  * @author Xiaosy
 15  * @date 2017-12-28 14:26
 16  */
 17 public class EmailSender {
 18     /**
 19      * 邮件服务器地址
 20      */
 21     private String host;
 22     /**
 23      * 发件人用户名
 24      */
 25     private String username;
 26     /**
 27      * 发件人密码
 28      */
 29     private String password;
 30 
 31     private String from;
 32     /**
 33      * 邮件对象
 34      */
 35     private MimeMessage mimeMessage;
 36     /**
 37      * 接收方邮件地址
 38      */
 39     private InternetAddress[] addresses;
 40     /**
 41      * 无效的邮箱地址
 42      */
 43     private String[]invalidAddresses;
 44     /**
 45      * 已发送的邮箱
 46      */
 47     private String[]validSendAddresses;
 48     /**
 49      * 未发送成功的邮箱地址
 50      */
 51     private String[]validUnSendAddresses;
 52     private Session session;
 53     private Properties props;
 54     //附件添加的组件
 55     private Multipart mp;
 56     //附件文件
 57     private List<FileDataSource> files = new LinkedList<FileDataSource>();
 58 
 59 
 60     private EmailSender(String from, String smtpHost, String sendUserName, String sendUserPassword){
 61         this.from = from;
 62         this.host = smtpHost;
 63         this.username = sendUserName;
 64         this.password = sendUserPassword;
 65         init();
 66     }
 67     private void init(){
 68         if (props == null) {
 69             props = System.getProperties();
 70         }
 71         props.put("mail.smtp.host", this.host);
 72         props.put("mail.smtp.auth", "true"); // 需要身份验证
 73         props.put("mail.mime.splitlongparameters","false");
 74         session = Session.getDefaultInstance(props, null);
 75         // 置true可以在控制台(console)上看到发送邮件的过程
 76         session.setDebug(false);
 77         // 用session对象来创建并初始化邮件对象
 78         mimeMessage = new MimeMessage(session);
 79         // 生成附件组件的实例
 80         mp = new MimeMultipart();
 81     }
 82 
 83     public static EmailSender newInstance(String from, String smtpHost, String sendUserName, String sendUserPassword){
 84         return new EmailSender(from,smtpHost, sendUserName, sendUserPassword);
 85     }
 86 
 87     /**
 88      * 设置邮件主题
 89      * @param subject
 90      * @return
 91      * @throws MessagingException
 92      */
 93     public EmailSender subject(String subject) throws MessagingException{
 94         this.mimeMessage.setSubject(subject);
 95         return this;
 96     }
 97     public EmailSender subject(String subject, String charset) throws MessagingException{
 98         this.mimeMessage.setSubject(subject,charset);
 99         return this;
100     }
101 
102     /**
103      * 设置正文,支持html标签
104      * @param content
105      * @return
106      * @throws MessagingException
107      */
108     public EmailSender content(String content) throws MessagingException{
109         BodyPart bp = new MimeBodyPart();
110         bp.setContent("<meta http-equiv=Content-Type content=text/html; charset=UTF-8>" + content, "text/html;charset=UTF-8");
111         this.mp.addBodyPart(bp);
112         return this;
113     }
114 
115     /**
116      * 设置收件人
117      * @param to
118      * @return
119      * @throws MessagingException
120      */
121     public EmailSender to(String...to) throws MessagingException {
122         this.addresses = new InternetAddress[to.length];
123         for(int i =0;i<to.length;i++){
124             this.addresses[i] = new InternetAddress(to[i]);
125         }
126         return this;
127     }
128     public EmailSender to(String to) throws MessagingException {
129         this.addresses = new InternetAddress[]{new InternetAddress(to)};
130         return this;
131     }
132 
133     /**
134      * 设置抄送
135      * @param cc
136      * @return
137      * @throws MessagingException
138      */
139     public EmailSender cc(String...cc) throws MessagingException {
140         InternetAddress[]addresses = new InternetAddress[cc.length];
141         for(int i =0;i<cc.length;i++){
142             addresses[i] = new InternetAddress(cc[i]);
143         }
144         this.mimeMessage.setRecipients(Message.RecipientType.CC,addresses);
145         return this;
146     }
147     public EmailSender cc(String cc) throws MessagingException {
148         InternetAddress[]addresses = new InternetAddress[]{new InternetAddress(cc)};
149         this.mimeMessage.setRecipients(Message.RecipientType.TO,addresses);
150         return this;
151     }
152 
153     /**
154      * 添加附件,可添加多个
155      * @param fileName
156      * @param file
157      * @return
158      * @throws MessagingException
159      * @throws UnsupportedEncodingException
160      */
161     public EmailSender addAttachmentFile(String fileName, File file) throws MessagingException, UnsupportedEncodingException {
162         BodyPart bp = new MimeBodyPart();
163         FileDataSource fileds = new FileDataSource(file);
164         bp.setDataHandler(new DataHandler(fileds));
165         bp.setFileName(MimeUtility.encodeText(fileName, "utf-8", null)); // 解决附件名称乱码
166         this.mp.addBodyPart(bp);// 添加附件
167         this.files.add(fileds);
168         return this;
169     }
170 
171     /**
172      * 发送邮件
173      * @param skpiInvalidAddresses 是否跳过不正确的邮箱地址,true跳过,默认不跳过
174      * @return
175      */
176     public boolean send(boolean skpiInvalidAddresses){
177         Transport transport = null;
178         try {
179             this.mimeMessage.setRecipients(Message.RecipientType.TO,this.addresses);
180             this.mimeMessage.setFrom(this.from);
181             this.mimeMessage.setContent(this.mp);
182             this.mimeMessage.saveChanges();
183             System.out.println("邮件发送中...");
184             transport = this.session.getTransport("smtp");
185             //连接邮件服务器并进行身份验证
186             transport.connect(this.host,this.username,this.password);
187             //发送邮件
188             transport.sendMessage(this.mimeMessage,this.addresses);
189             System.out.println("发送成功");
190             this.validSendAddresses = new String[this.addresses.length];
191             for(int i=0;i<this.addresses.length;i++){
192                 this.validSendAddresses[i] = this.addresses[i].toString();
193             }
194         }catch (SendFailedException e){
195             Address[]invalid = e.getInvalidAddresses();
196             if(invalid != null && invalid.length > 0){
197                 this.invalidAddresses = new String[invalid.length];
198                 for(int i=0;i<invalid.length;i++){
199                     this.invalidAddresses[i]=invalid[i].toString();
200                 }
201             }
202             Address[]validSendTo = e.getValidSentAddresses();
203             if(validSendTo != null && validSendTo.length > 0){
204                 this.validSendAddresses = new String[validSendTo.length];
205                 for(int i=0;i<validSendTo.length;i++){
206                     this.validSendAddresses[i]=validSendTo[i].toString();
207                 }
208             }
209             Address[]validUnSendTo = e.getValidUnsentAddresses();
210             if(validUnSendTo != null && validUnSendTo.length > 0){
211                 this.validUnSendAddresses = new String[validUnSendTo.length];
212                 for(int i=0;i<validUnSendTo.length;i++){
213                     this.validUnSendAddresses[i]=validUnSendTo[i].toString();
214                 }
215             }
216             //跳过不正确的邮箱
217             if(skpiInvalidAddresses && invalid != null && invalid.length>0){
218                 if(validUnSendTo != null && validUnSendTo.length > 0){
219                     this.validUnSendAddresses = null;
220                     return sendFailedAddresses(validUnSendTo);
221                 }
222             }else {
223                 return false;
224             }
225 
226         } catch (MessagingException e) {
227             e.printStackTrace();
228             return false;
229         }finally {
230             if(transport != null){
231                 try {
232                     transport.close();
233                 } catch (MessagingException e) {
234                     e.printStackTrace();
235                 }
236             }
237         }
238         return true;
239     }
240 
241     public boolean send(){
242         return send(false);
243     }
244 
245     private boolean sendFailedAddresses(Address[]failedAddresses){
246         Transport transport = null;
247         try {
248             this.mimeMessage.setRecipients(Message.RecipientType.TO,failedAddresses);
249             transport = this.session.getTransport("smtp");
250             //连接邮件服务器并进行身份验证
251             transport.connect(this.host,this.username,this.password);
252             //发送邮件
253             transport.sendMessage(this.mimeMessage,failedAddresses);
254             this.validSendAddresses = new String[failedAddresses.length];
255             for(int i=0;i<failedAddresses.length;i++){
256                 this.validSendAddresses[i] = failedAddresses[i].toString();
257             }
258         }catch (SendFailedException e){
259             e.printStackTrace();
260             return false;
261         }catch (MessagingException e){
262             e.printStackTrace();
263             return false;
264         }
265         return true;
266     }
267 
268 
269     public String[] getInvalidAddresses() {
270         return invalidAddresses;
271     }
272 
273     public void setInvalidAddresses(String[] invalidAddresses) {
274         this.invalidAddresses = invalidAddresses;
275     }
276 
277     public String[] getValidSendAddresses() {
278         return validSendAddresses;
279     }
280 
281     public void setValidSendAddresses(String[] validSendAddresses) {
282         this.validSendAddresses = validSendAddresses;
283     }
284 
285     public String[] getValidUnSendAddresses() {
286         return validUnSendAddresses;
287     }
288 
289     public void setValidUnSendAddresses(String[] validUnSendAddresses) {
290         this.validUnSendAddresses = validUnSendAddresses;
291     }
292 }

  测试代码如下:

  

 1  @Test
 2 public void testSendMail(){
 3     String subject = "测试用";
 4     String content = "测试内容";
 5     String[]emails = new String[]{"myqq@qq.com","aaa@abc.com","bbb@bw.com"};
 6 
 7     try {
 8         EmailSender javaMailSender = EmailSender.newInstance(from,mailConfiguration.getHost(),mailConfiguration.getUsername(),mailConfiguration.getPassword())
 9                     .subject(subject).content(content).to(emails);
10         System.out.println(javaMailSender.send());
11         String[]invalidAddress = javaMailSender.getInvalidAddresses();
12         System.out.println("Invalid address : " + JSONObject.toJSONString(invalidAddress));
13         String[]validUnSentAddress = javaMailSender.getValidUnSendAddresses();
14         System.out.println("valid unsent address : " + JSONObject.toJSONString(validUnSentAddress));
15         String[]validSentAddress = javaMailSender.getValidSendAddresses();
16         System.out.println("valid sent address : " + JSONObject.toJSONString(validSentAddress));
17     } catch (MessagingException e) {
18         e.printStackTrace();
19     }
20 }

其中,send() 方法默认是不跳过不存在的邮箱地址的,如果要自动跳过,参数设置为 true 即可:
默认时的结果如下:
4bb20654a59b41f392d789ed33b26314.png

参数设置为 true 时结果如下:
6634e315f07c465ebd901129f51a9678.png

而我的 qq 邮箱也确实收到了邮件。

 

如果使用 spring 封装的 mail,调用 mailSender.send(MimeMessage message) 方法时抛出的异常是 org.springframework.mail.MailSendException,查看源码发现这个异常并没有 ivali dAddress、validSentAddress 和 validUnSentAddress 三个变量,但是它有一个异常数组:
2ec43987a24e420cb993f6760ceff111.png
javax.mail.SendFailedException 应该被放到了这里面,修改 spring 发送邮件的代码如下:

 1 /**
 2  * 发送邮件
 3  * @param skpiInvalidAddresses 是否跳过不正确的邮箱地址,true跳过,默认不跳过
 4  * @return
 5  */
 6 public boolean send(boolean skpiInvalidAddresses) throws IllegalStateException{
 7     if(mailSender == null){
 8         LOGGER.info("Mail Sender Instance is not allowed null");
 9         throw new IllegalStateException("You should call newInstance() method first before call send() method");
10     }
11     try {
12         mailSender.send(this.message);
13     }catch (MailSendException e){
14         Exception[] messageExceptions = e.getMessageExceptions();
15         if(messageExceptions != null && messageExceptions.length > 0) {
16             SendFailedException subEx = null;
17             for (int i = 0; i < messageExceptions.length; ++i) {
18                 if (messageExceptions[i] instanceof SendFailedException) {
19                     subEx = (SendFailedException) messageExceptions[i];
20                     break;
21                 }
22             }
23             if (subEx == null) {
24                 return false;
25             }
26             Address[] invalid = subEx.getInvalidAddresses();
27             if (invalid != null) {
28                 this.invalidAddresses = new String[invalid.length];
29                 for (int j = 0; j < invalid.length; j++) {
30                     this.invalidAddresses[j] = invalid[j].toString();
31                 }
32             }
33 
34             Address[] validUnsentAddresses = subEx.getValidUnsentAddresses();
35             if (validUnsentAddresses != null) {
36                 this.validUnSendAddresses = new String[validUnsentAddresses.length];
37                 for (int j = 0; j < validUnsentAddresses.length; j++) {
38                     this.validUnSendAddresses[j] = validUnsentAddresses[j].toString();
39                 }
40             }
41 
42             Address[]validSentAddresses = subEx.getValidSentAddresses();
43             if(validSentAddresses != null){
44                 this.validSendAddresses = new String[validSentAddresses.length];
45                 for(int j=0;j<validSentAddresses.length;j++){
46                     this.validSendAddresses[j] = validSentAddresses[j].toString();
47                 }
48             }
49 
50             if(skpiInvalidAddresses){
51                 return sendFailedAddress(this.validUnSendAddresses);
52             }else {
53                 return false;
54             }
55         }
56     }
57     return true;
58 }

经过测试同样可以正常工作。

需要注意的是,发送邮件前必须设置 From 字段,如果未设置此字段,invalidAddress 是获取不到地址错误的邮箱的,而导致群发的所有邮箱地址放到了 validUnSentAddress 里面,这样就跟通常的情况是一样的了。

小提示:邮件发送 html 的内容时,如果有图片时,使用图片链接显示的话,很多邮箱服务器会屏蔽外部链接导致图片显示出现问题,可以采用内嵌方式将图片内嵌入正文中,但是这种方式会增加邮件大小。

posted @ 2018-03-21 15:55  浅夏丶未央  阅读(595)  评论(0编辑  收藏