邮件群发问题

  • 发表时间:2021-08-22 00:12:26
  • 文章来源:网络

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

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

为了解决给北纬邮箱群发的问题,我网上查了一下,看了一下 java mail 的 api,发现发送邮件调用 send 方法时,失败会抛出异常 javax.mail.SendFailedException,而这个异常里面封装了三个变量:  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 即可:默认时的结果如下:

参数设置为 true 时结果如下:

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

 

如果使用 spring 封装的 mail,调用 mailSender.send(MimeMessage message) 方法时抛出的异常是 org.springframework.mail.MailSendException,查看源码发现这个异常并没有 ivali dAddress、validSentAddress 和 validUnSentAddress 三个变量,但是它有一个异常数组: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 的内容时,如果有图片时,使用图片链接显示的话,很多邮箱服务器会屏蔽外部链接导致图片显示出现问题,可以采用内嵌方式将图片内嵌入正文中,但是这种方式会增加邮件大小。