解决spring boot JavaMailSender部分收件人错误导致发送失败的问题
使用spring boot通常使用spring-boot-starter-mail进行邮件的发送。当进行邮件群发的话,如果一个收件人的地址错误,会导致所有邮件都发送失败。因此我们需要在邮件发送失败的时候把错误的收件人移除,重新发送。
当邮件发送失败的时候会抛出MailSendException,异常信息中包含错误的收件人信息。
主要代码如下:
private void sendMail(List<String> mailList, MimeMessageHelper message){ try{ this.mailSender.send(message.getMimeMessage()); }catch (MailSendException e){ Set<String> tmpInvalidMails = getInvalidAddress(e); // 非无效收件人导致的异常,暂不处理 if (tmpInvalidMails.isEmpty()){ logger.error(e.getMessage()); return; } mailList.removeAll(tmpInvalidMails); if(mailList.isEmpty()){ logger.error("邮件发送失败,无收件人" + e.getMessage()); return; } message.setTo(mailList.toArray(new String[0])); sendMail(mailList, message) } }
捕获邮件发送失败的异常,首先判断是否是收件人无效导致的异常。从异常中解析无效收件人,收件人例表中移除无效的收件人,重新发送邮件。
如何从从异常中获取无效收件人,首先看下JavaMailSenderImpl这个类的doSend方法
1 protected void doSend(MimeMessage[] mimeMessages, @Nullable Object[] originalMessages) throws MailException { 2 Map<Object, Exception> failedMessages = new LinkedHashMap<>(); 3 Transport transport = null; 4 try { 5 for (int i = 0; i < mimeMessages.length; i++) { 6 ... 7 // Send message via current transport... 8 MimeMessage mimeMessage = mimeMessages[i]; 9 try { 10 ... 11 } 12 catch (Exception ex) { 13 Object original = (originalMessages != null ? originalMessages[i] : mimeMessage); 14 failedMessages.put(original, ex); 15 } 16 } 17 } 18 finally { 19 try { 20 if (transport != null) { 21 transport.close(); 22 } 23 } 24 catch (Exception ex) { 25 if (!failedMessages.isEmpty()) { 26 throw new MailSendException("Failed to close server connection after message failures", ex, 27 failedMessages); 28 } 29 else { 30 throw new MailSendException("Failed to close server connection after message sending", ex); 31 } 32 } 33 } 34 35 if (!failedMessages.isEmpty()) { 36 throw new MailSendException(failedMessages); 37 } 38 }
当邮件发送过程中遇到异常会保存到failedMessages中,我们需要从中解析收件人无效导致的异常。
接着继续看源码SMTPTransport的rcptTo方法,会去校验每个收件人,通过向服务器发送RCPT TO:<地址>,根据响应码来判断收件人是否有效。
protected void rcptTo() throws MessagingException { List<InternetAddress> valid = new ArrayList(); List<InternetAddress> validUnsent = new ArrayList(); List<InternetAddress> invalid = new ArrayList(); ... int k; for(k = 0; k < this.addresses.length; ++k) { sfex = null; InternetAddress ia = (InternetAddress)this.addresses[k]; String cmd = "RCPT TO:" + this.normalizeAddress(ia.getAddress()); if (dsn) { cmd = cmd + " NOTIFY=" + notify; } this.sendCommand(cmd); int retCode = this.readServerResponse(); switch(retCode) { case 250: case 251: valid.add(ia); ... break; case 450: case 451: case 452: case 552: ... validUnsent.add(ia); ... break; case 501: case 503: case 550: case 551: case 553: ... invalid.add(ia); ... break; default: if (retCode >= 400 && retCode <= 499) { validUnsent.add(ia); } else { ... invalid.add(ia); } ... } } if (sendPartial && valid.size() == 0) { sendFailed = true; } int lrc; if (sendFailed) { this.invalidAddr = new Address[invalid.size()]; invalid.toArray(this.invalidAddr); this.validUnsentAddr = new Address[valid.size() + validUnsent.size()]; k = 0; for(lrc = 0; lrc < valid.size(); ++lrc) { this.validUnsentAddr[k++] = (Address)valid.get(lrc); } for(lrc = 0; lrc < validUnsent.size(); ++lrc) { this.validUnsentAddr[k++] = (Address)validUnsent.get(lrc); } } ... if (sendFailed) { this.logger.fine("Sending failed because of invalid destination addresses"); ... throw new SendFailedException("Invalid Addresses", (Exception)mex, this.validSentAddr, this.validUnsentAddr, this.invalidAddr); } }
当收件人无效发送失败会抛出SendFailedException异常,异常中包含收件人是否有效的信息。
因此我们只要从failedMessages查找是否含有SendFailedException,然后从SendFailedException直接得到无效的收件人信息。代码如下:
private Set<String> getInvalidAddress(MailSendException e){ Set<String> mails = new HashSet<>(); for(Exception exception: e.getFailedMessages().values()){ if(exception instanceof SendFailedException){ for(Address address: ((SendFailedException) exception).getInvalidAddresses()){ mails.add(address.toString()); } } } return mails; }