邮件发送兜底邮箱策略 - SMTPSendFailedException: 421 4.4.5 HL:ICC
项目测试和生产环境使用的都是163企业邮箱发送,如果测试和生产定时任务在同一时间会产生如上163的提示报错。
并且增加了重试机制,邮件发送失败后,重试2次,间隔30秒。
服务邮件发送服务优化-增加重试机制和减少并发,测试环境和生产环境的xxlJob执行错开时间,且将测试环境的定时任务暂停。
解决方案:
1.邮件发送失败,增加失败重试发送机制。
2.测试环境的定时任务邮件发送错开时间,暂停发送。减少对线上同一个邮件任务的并发发送的冲突
3.邮件地址不存在或错误的地址,更正。
1.针对邮件列表中邮箱地址不存在的情况(离职人员的邮箱地址不存在),增加了补充兜底的可确定的邮箱地址来接收,避免邮件发送失败而不可知。
javax.mail.SendFailedException: Invalid Addresses 邮件地址错误,兜底发送。
2.成员变量的赋值。 否则都是 list为空
3.邮件发送返回成功和失败标识 @Async 异步不能返回,捕获异常。需要接口的返回值,需要去掉@Async
4.设计思路:
1.两份邮件,邮件收件人的地址配置分开单独配置,为了后续的扩展。可以允许单独配置,比较灵活。
2.两份邮件,数据查询的实体类Entity分开创建,为了后续的扩展。
5.//使用465
final String SSLport="465"; final String sslFactory = "javax.net.ssl.SSLSocketFactory"; props.put("mail.smtp.port", SSLport); props.put("mail.smtp.socketFactory.class", sslFactory); props.put("mail.smtp.socketFactory.fallback", "false"); props.put("mail.smtp.socketFactory.port", SSLport); //参考源码(commons-email:1.3.3):org.apache.commons.mail.Email.java SimpleEmail.java EmailConstants.java public static final String MAIL_SMTP_SOCKET_FACTORY_FALLBACK = "mail.smtp.socketFactory.fallback"; public static final String MAIL_SMTP_SOCKET_FACTORY_CLASS = "mail.smtp.socketFactory.class"; public static final String MAIL_SMTP_SOCKET_FACTORY_PORT = "mail.smtp.socketFactory.port"; public static final String MAIL_PORT = "mail.smtp.port"; Email.java protected String sslSmtpPort = "465"; @Deprecated public static final String MAIL_PORT = EmailConstants.MAIL_PORT; @Deprecated public static final String MAIL_SMTP_SOCKET_FACTORY_PORT = EmailConstants.MAIL_SMTP_SOCKET_FACTORY_PORT; @Deprecated public static final String MAIL_SMTP_SOCKET_FACTORY_CLASS = EmailConstants.MAIL_SMTP_SOCKET_FACTORY_CLASS; @Deprecated public static final String MAIL_SMTP_SOCKET_FACTORY_FALLBACK = EmailConstants.MAIL_SMTP_SOCKET_FACTORY_FALLBACK; if (isSSLOnConnect()) { properties.setProperty(MAIL_PORT, this.sslSmtpPort); properties.setProperty(MAIL_SMTP_SOCKET_FACTORY_PORT, this.sslSmtpPort); properties.setProperty(MAIL_SMTP_SOCKET_FACTORY_CLASS, "javax.net.ssl.SSLSocketFactory"); properties.setProperty(MAIL_SMTP_SOCKET_FACTORY_FALLBACK, "false"); }
6.发信频率过高的解决办法——降低频率
可以通过设置线程 或将邮件先存入数据库再设置定时JOB发送的方式实施
参考:Spring异常重试框架Spring Retry 重试机制应用
https://www.cnblogs.com/oktokeep/p/18279179
//伪代码 @Retryable(value = {RetrySendEmailException.class},maxAttempts = 3,backoff = @Backoff(delay=60000, multiplier = 2)) public boolean sendEmailLog(ReqEmail req,boolean isDelay) throws Exception{ //将本次的请求对象转换为json,做md5,避免重复发送 //如果是重试发送,则需要绕过 重复发送的拦截 /** * 发送失败重试,则无需该拦截判断 */ if(req.isRetrySendFlag() == false && emailLogMapper.getEmailLogExists(md5) > 0){ throw new CommonEmailException(ErrorCode.SEND_SAME_ERROR); } if(req.isRetrySendFlag()){ //产生新的md5标识,继承上一次的标识。 重新赋值 LocalDateTime ldt = LocalDateTime.now(); String minute = String.valueOf(ldt.getMinute()); String second = String.valueOf(ldt.getSecond()); md5 = md5 +"-"+ minute+second; entity.setEmailContentMd5(md5); } //发送邮件 sendFlag = sendEmail(); if(sendFlag == false){ logger.info("发送异常,重新发送>>>[],retryFlag=[{}]", req.getEmailSubject(),req.isRetrySendFlag()); //重新赋值,表示需要重试发送邮件。在下一次的执行中,请求对象用应用该新值。 req.setRetrySendFlag(true); throw new RetrySendEmailException("999999","发送异常,重新发送"); } }
备注:maxAttempts = 3,backoff = @Backoff(delay=60000, multiplier = 2)
测试下来:执行了2次重试,每次间隔30秒。而不是1分钟。