发送电子邮件到用户是项目中一个很常见的功能,如邮件通知、邮件营销、通过邮件激活账号、通过邮件找回密码等。
Spring Framework 提供了一个用于发送电子邮件的实用库,保护用户免受底层邮件系统的细节影响。提供了发送电子邮件的简单抽象 JavaMailSender 接口,Spring Boot 为 JavaMail提供了自动配置和启动模块。
Spring Boot 对 Email 的支持官方说明 ,有关使用 JavaMail 的详细说明,可参阅官方的参考文档 , JavaMail 参考实现 。
基本概念 邮件协议 :目前常用的客户端/服务器协议有两种,分别是 POP3/SMTP
和IMAP/SMTP
:
SMTP (Simple Mail Transfer Protocol):即简单邮件传输协议(发送邮件),一组用于由源地址 到目的地址 传送邮件的规范,由它来控制邮件的中转方式,帮助计算机在发送或中转邮件时找到下一个目的地。属于TCP/IP 协议簇,默认端口是25
。
POP3 (Post Office Protocol - Version 3):即邮局协议版本3 ,主要用于支持使用客户端管理在服务器上的电子邮件。属于Tcp/IP 家族,默认端口是110
。
IMAP (Internet Mail Access Protocol):Internet邮件访问协议,主要用于支持客户端通过该协议从邮件服务器获取邮件信息、下载邮件等。 IMAP 协议运行在 Tcp/IP 协议之上,默认端口是143
。
开启 POP3/SMTP 服务、IMAP/SMTP 服务。通常邮件服务商提供的邮箱设置页面可设置是否开启服务支持,并可查看服务器地址。例如 163 的邮箱设置:
自动配置分析 Spring Boot 为 JavaMail 提供的自动配置源码位于 org.springframework.boot.autoconfigure.mail 包下。 自动配置的属性配置以spring.mail
为前缀,配置如下:
spring.mail.default-encoding=UTF-8 # Default MimeMessage encoding. spring.mail.host= # SMTP server host. For instance, smtp.example.com
. spring.mail.port= # SMTP server port. spring.mail.username= # Login user of the SMTP server. spring.mail.password= # Login password of the SMTP server. spring.mail.properties.*= # Additional JavaMail Session properties. spring.mail.protocol=smtp # Protocol used by the SMTP server. spring.mail.jndi-name= # Session JNDI name. When set, takes precedence over other Session settings. spring.mail.test-connection=false # Whether to test that the mail server is available on startup.
自动配置提供的邮件默认编码是UTF-8
,默认的协议是smtp
,源码文件是 MailProperties.java。 若配置文件包含 spring.mail.host 属性就会自动注册mailSender
Bean ,在使用时可直接注入mailSender 来发送邮件。
默认的邮件超时是无限的,可以自定义超时时长来避免线程被无响应的邮件服务器阻塞,配置如下:
spring.mail.properties.mail.smtp.connectiontimeout=5000 spring.mail.properties.mail.smtp.timeout=3000 spring.mail.properties.mail.smtp.writetimeout=5000
集成JavaMail
pom.xml 添加依赖1 2 3 4 5 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-mail</artifactId > </dependency >
application.properties 添加配置1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 spring.mail.default-encoding =UTF-8 spring.mail.host =smtp.xxx.com spring.mail.port =25 spring.mail.username =xxxx@xxx.com spring.mail.password =xxxxxx spring.mail.protocol =smtp spring.mail.test-connection =true spring.mail.properties.mail.smtp.connectiontimeout =5000 spring.mail.properties.mail.smtp.timeout =3000 spring.mail.properties.mail.smtp.writetimeout =5000
发送邮件
发送邮件有些基本元素是需要需要填写:
from :邮件发送者,即源地址,一般设置在配置文件中或做为常量配置。to :邮件接收者,此参数一般从用户信息中获取,参数可为 String 数组,同时发送多人。subject :邮件主题。content :邮件正文(内容)。cc :抄送给用户,值是邮箱地址(可选)。
构建邮件对象有两种方式:SimpleMailMessage,MimeMessagePreparator。
SimpleMailMessage :简单邮件模型,只包含 from,to,cc,subject,text等几个字段。MimeMessagePreparator :MIME 邮件的回调接口,当邮件内容出现特殊字符编码时,此接口非常有用。还有一个重要的帮助类MimeMessageHelper ,该类是 MimeMessageHelper 的帮助类,用于构建复杂的邮件对象,如带附件的邮件,HTML邮件等。
简单文本邮件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 @Service public class sendEmailImpl implements sendEmail { private static final Logger logger = LogManager.getLogger(sendEmailImpl.class); @Autowired private JavaMailSender mailSender; @Value("${spring.mail.username}") private String from; @Override public void sendSimpleMail () { User user = new User().setName("Andy" ).setEmailAddress("xxxx@163.com" ); SimpleMailMessage message = new SimpleMailMessage(); message.setFrom(from); message.setTo(user.getEmailAddress()); message.setSubject("账号注册激活确认" ); message.setText("请点击下面链接激活注册的账号:" + "\n\t" + "http://xxxx.xxxx.com?code=xxxxxx" ); message.setCc("xxxx@qq.com" ); try { mailSender.send(message); logger.info("邮件发送成功!" ); } catch (MailException e) { logger.error("邮件发送异常:{}" , e); e.printStackTrace(); } } @Override public void sendMailUseMimeMessagePreparator () { User user = new User().setName("Andy" ).setEmailAddress("xxxx@163.com" ); MimeMessagePreparator preparator = new MimeMessagePreparator() { public void prepare (MimeMessage mimeMessage) throws Exception { mimeMessage.setRecipient(Message.RecipientType.TO, new InternetAddress(user.getEmailAddress())); mimeMessage.setFrom(from); mimeMessage.setSubject("幸运观众" ); mimeMessage.setText("你已被抽中为幸运观众,可获得8888元的奖品大礼包!" ); } }; try { mailSender.send(preparator); logger.info("邮件发送成功!" ); } catch (MailException e) { logger.error("邮件发送异常:{}" , e); e.printStackTrace(); } } @Override public void sendMailUseMimeMessageHelper () { User user = new User().setName("Andy" ).setEmailAddress("xxxx@163.com" ); MimeMessage message = mailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(message); try { helper.setFrom(new InternetAddress(from)); helper.setTo(user.getEmailAddress()); helper.setSubject("双色球中奖" ); helper.setText("恭喜你中了双色球一等奖!!" ); mailSender.send(message); logger.info("邮件发送成功!" ); } catch (MessagingException e) { logger.error("邮件发送异常:{}" , e); e.printStackTrace(); } } }
带附件的邮件 只能使用MimeMessageHelper
,并开启 multipart 模式。注意附件的大小,过大有可能被邮件服务商的拦截掉。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Override public void sendMailWithAttachments () { User user = new User().setName("Andy" ).setEmailAddress("xxxx@163.com" ); MimeMessage message = mailSender.createMimeMessage(); try { MimeMessageHelper helper = new MimeMessageHelper(message, true ); String path = sendEmailImpl.class.getClassLoader().getResource("" ).getPath(); File file = new File(path + "/happy.jpg" ); FileSystemResource resource = new FileSystemResource(file); helper.setFrom(new InternetAddress(from)); helper.setTo(user.getEmailAddress()); helper.setSubject("设计图" ); helper.setText("设计图见附件" ); helper.addAttachment("happy.jpg" , resource); mailSender.send(message); logger.info("邮件发送成功!" ); } catch (MessagingException e) { logger.error("邮件发送异常:{}" , e); e.printStackTrace(); } }
HTML邮件 发送 HTML 邮件,并在邮件正文内嵌静态文件(如显示图片)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @Override public void sendMailInlineResource () { User user = new User().setName("Andy" ).setEmailAddress("xxxx@163.com" ); MimeMessage message = mailSender.createMimeMessage(); String mailContent = "<html><body>" + "<h1>Hello World!</h1>" + "<img src='cid:happy.jpg'>" + "</body></html>" ; try { MimeMessageHelper helper = new MimeMessageHelper(message, true ); String path = sendEmailImpl.class.getClassLoader().getResource("" ).getPath(); File file = new File(path + "/happy.jpg" ); FileSystemResource resource = new FileSystemResource(file); helper.setFrom(new InternetAddress(from)); helper.setTo(user.getEmailAddress()); helper.setSubject("设计图" ); helper.setText("设计图见附件" ); helper.setText(mailContent, true ); helper.addInline("happy.jpg" , resource); mailSender.send(message); logger.info("邮件发送成功!" ); } catch (MessagingException e) { logger.error("邮件发送异常:{}" , e); e.printStackTrace(); } }
thymeleaf模板邮件 为固定的场景设置邮件模板,如重置密码、账号激活、营销活动,给每个用户发送的内容只有小部分变化。 这类需求可以使用模板引擎来为各类邮件设置成模板,通过模板引擎将小部分变化的地方参数化 ,返回的是 HTML 代码的字符串,即实际发送的是 HTML 邮件。
pom.xml 添加模板依赖1 2 3 4 5 <!--thymeleaf--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
创建模板 emailTemplate.html1 2 3 4 5 6 7 8 9 10 11 <!DOCTYPE html > <html lang ="en" xmlns:th ="http://www.thymeleaf.org" > <head > <meta charset ="UTF-8" > <title > 邮件模板</title > </head > <body > 淘宝 11.11,打折、优惠、红包、抽奖不断,大礼包就等你。</br > <a href ="#" th:href ="@{http://www.taobao.com/{name}(name=${name})}" > 开始抽奖</a > </body > </html >
发送模板邮件
注意: 发送模板邮件的业务类里,必须使用 Spring Boot 为模板提供自动配置时注册的模板引擎 Bean,若自己创建模板引擎对象会读不到模板文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 @Service public class SendEmailImpl implements SendEmail { private static final Logger logger = LogManager.getLogger(SendEmailImpl.class); @Autowired private JavaMailSender mailSender; @Autowired private TemplateEngine templateEngine; @Value("${spring.mail.username}") private String from; @Override public void sendMailTemplate () { User user = new User().setName("Andy" ).setEmailAddress("xxxx@163.com" ); try { Context context = new Context(); context.setVariable("name" , user.getName()); String emailContent = templateEngine.process("emailTemplate" , context); MimeMessage message = mailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(message, true ); helper.setFrom(from); helper.setTo(user.getEmailAddress()); helper.setSubject("淘宝 11.11 活动" ); helper.setText(emailContent,true ); mailSender.send(message); logger.info("邮件发送成功!" ); } catch (MessagingException e) { e.printStackTrace(); } } }
spring-boot-email 源码 -> GitHub
邮件系统 若是微服务架构,可将发送邮件抽出为一个独立的微服务来使用,邮件系统作为平台的基础服务来支持整个系统。 作为一个完整的邮件系统,需要完整的记录发送邮件的任务和执行情况,包括发送失败的处理等。
异步发送邮件 通常主业务并不需要发送邮件及时响应执行结果,比如营销类、通知类、提醒类的业务可以允许延时或者失败。这时可采用异步的方式发送邮件,以加快主业务的执行速度。
在实际项目中,邮件发送可采用消息中间件MQ 来实现异步发送,主业务将发送邮件任务入库,通过 MQ 通知邮件系统,邮件系统收到通知就从库中取出任务执行发送逻辑;或者创建一个邮件发送的消息队列,给对应消息队列按照规定参数发送消息,邮件系统监听此队列,当有消息时对消息进行解析,按消息所带参数处理邮件发送的逻辑。
发送失败处理
将发送邮件任务入库,执行任务后同时标记任务的处理结果。
根据需要,可启用定时任务来扫描发送失败的邮件,对重发次数小于 3 次的邮件执行再次发送。
重发邮件的时间间隔,建议以 2 的次方间隔时间,如:2、4、8、16….
独立的管理后台 一个完善的邮件系统,可以设计一个独立的邮件管理后台,提供图形化界面方便公司运营,对发送邮件业务进行统计等。也可以设置一些代码钩子,统计用户点击固定链接次数,方便公司营销人员监控邮件营销转化率。
还可设置白名单、黑名单来做邮件接收人的过滤机制。
【参考】如何使用 Spring Boot 开发邮件系统?