SpringBoot整合mail发送邮件

最初的时候我们会使用 JavaMail 相关 api 来写发送邮件的相关代码,后来 Spring 推出了 JavaMailSender 更加简化了邮件发送的过程,再之后 Spring Boot 对此进行了封装就有了现在的 spring-boot-starter-mail。

简单使用

pom.xml 包配置

在 pom.xml 包里面添加 spring-boot-starter-mail 包引用:

1
2
3
4
5
6
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
</dependencies>

在 application.properties 中添加邮箱配置

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
# 163个人邮箱
spring.mail.host=smtp.163.com
spring.mail.username=chrootliu@163.com
spring.mail.password=密码(开启POP3/SMTP的授权码)
spring.mail.default-encoding=UTF-8
mail.fromMail.addr=chrootliu@163.com

#qq个人邮箱
#spring.mail.host=smtp.qq.com
#spring.mail.username=xxxxxxxxxx@qq.com
#spring.mail.password=密码(开启POP3/SMTP的授权码)
#spring.mail.default-encoding=UTF-8
#spring.mail.properties.mail.smtp.starttls.enable=true
#spring.mail.properties.mail.smtp.starttls.required=true
#mail.fromMail.addr=xxxxxxx@qq.com

# 微软outlook邮箱 yml 配置
host: smtp.office365.com
username: xxxxxxxxxxx@outlook.com
password: 密码
default-encoding: UTF-8
port: 587
protocol: smtp
properties:
mail:
smtp:
auth: true
starttls:
enable: true
required: true
fromMail:
addr: xxxxx@outlook.com

编写 mailService,这里只提出实现类

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
@Component
public class MailServiceImpl implements MailService{

private final Logger logger = LoggerFactory.getLogger(this.getClass());

@Autowired
private JavaMailSender mailSender;

@Value("${mail.fromMail.addr}")
private String from;

@Override
public ServerResponse sendSimpleMail(String to, String subject, String content) {
MimeMessage message = mailSender.createMimeMessage();

try {
SimpleMailMessage message = new SimpleMailMessage();
// helper.setFrom("xxxx@outlook.com");
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(content, true);

mailSender.send(message);
// logger.info("简单邮件已经发送。");
return ServerResponse.createBySuccessMessage("发送邮件成功");
} catch (MessagingException e) {
// logger.error("发送简单邮件时发生异常!", e);
return ServerResponse.createByErrorMessage("发送邮件失败");
}
}
}

编写 test 类进行测试

1
2
3
4
5
6
7
8
9
10
11
12
@RunWith(SpringRunner.class)
@SpringBootTest
public class MailServiceTest {

@Autowired
private MailService MailService;

@Test
public void testSimpleMail() throws Exception {
MailService.sendSimpleMail("fromEmail@outlook.com","test simple mail"," hello this is simple mail");
}
}

增加其它功能

发送 html 格式邮件

其它都不变在 MailService 添加 sendHtmlMail 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public ServerResponse sendHtmlMail(String to, String subject, String content) {
MimeMessage message = mailSender.createMimeMessage();

try {
// true表示需要创建一个multipart message
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom("xxxxx@outlook.com");
helper.setTo(to);
helper.setSubject(subject);
helper.setText(content, true);

mailSender.send(message);
return ServerResponse.createBySuccessMessage("发送邮件成功");
} catch (MessagingException e) {
return ServerResponse.createByErrorMessage("发送邮件失败");
}
}

在测试类中构建 html 内容,测试发送:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void testHtmlMail() throws Exception {
String content="<html>\n" +
"<body>\n" +
" <h3>hello world ! 这是一封Html邮件!</h3>\n" +
"</body>\n" +
"</html>";
String content="<html>\n" +
"<body>\n" +
" <h3>这是一封注册邀请邮件!</h3>\n\n" +
" <h4>请点击以下链接继续完成注册</h4>\n" +
" <a href='http://xxxxx/register?email="+email+"&inviteCode="+inviteCode+"'>http://xxxxx/register?email="+email+"&inviteCode="+inviteCode+"</a>" +
"</body>\n" +
"</html>";
MailService.sendHtmlMail("fromEmail@outlook.com","Invite Email",content);
}

发送带附件的邮件

在 MailService 添加 sendAttachmentsMail 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void sendAttachmentsMail(String to, String subject, String content, String filePath){
MimeMessage message = mailSender.createMimeMessage();

try {
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(content, true);

FileSystemResource file = new FileSystemResource(new File(filePath));
String fileName = filePath.substring(filePath.lastIndexOf(File.separator));
helper.addAttachment(fileName, file);

mailSender.send(message);
logger.info("带附件的邮件已经发送。");
} catch (MessagingException e) {
logger.error("发送带附件的邮件时发生异常!", e);
}
}

在测试类中添加测试方法:

1
2
3
4
5
@Test
public void sendAttachmentsMail() {
String filePath="文件路径xxxxx";
mailService.sendAttachmentsMail("fromEmail@outlook.com", "主题:带附件的邮件", "有附件,请查收!", filePath);
}

发送带静态资源的邮件

邮件中的静态资源一般就是指图片,在 MailService 添加 sendAttachmentsMail 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void sendInlineResourceMail(String to, String subject, String content, String rscPath, String rscId){
MimeMessage message = mailSender.createMimeMessage();

try {
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(content, true);

FileSystemResource res = new FileSystemResource(new File(rscPath));
helper.addInline(rscId, res);

mailSender.send(message);
logger.info("嵌静态资源的邮件已经发送。");
} catch (MessagingException e) {
logger.error("发送带静态资源的邮件时发生异常!", e);
}
}

在测试类中添加测试方法:

1
2
3
4
5
6
7
8
@Test
public void sendInlineResourceMail() {
String rsVariableId = "aaaaa";
String content="<html><body>这是有图片的邮件:<img src=\'variableId:" + rsVariableId + "\' ></body></html>";
String imgPath = "http://xxxx.png";

mailService.sendInlineResourceMail("ityouknow@126.com", "主题:这是有图片的邮件", content, imgPath, rscId);
}

添加多个图片可以使用多条 和 helper.addInline(rsVariableId, res) 来实现

到此所有的基本的邮件发送服务已经完成了。

邮件模板

当邮件内容比较复杂,或者变量比较多的话,如果每次发送邮件都需要手动拼接的话会不够优雅,并且每次模板的修改都需要改动代码的话也很不方便,因此对于这类邮件需求,都建议做成邮件模板来处理。模板的本质很简单,就是在模板中替换变化的参数,转换为 html 字符串即可,这里以 thymeleaf 为例。

在 pom.xml 中导入 thymeleaf 的包

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

在 resorces/templates 下创建 emailTemplate.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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>Title</title>
<style>
*{
padding: 0;
margin: 0;
}
body,html{
color: #303030;
}
.container{
width: 600px;
padding: 40px;
}
h1{
color: #303030;
font-size: 30px;
text-align: center;
}
h3{
color: #303030;
font-size: 18px;
text-align: center;
}
.wrapper{
margin-top: 40px;
}
.item_li{
line-height: 28px;
}
.title{
font-size: 16px;
font-weight: bold;
line-height: 28px;
}
.content{
line-height: 28px;
font-size: 16px;
}
.email_content{
color: #ff6700;
}
.flex-align-space-between{
display: flex;
align-items: center;
justify-content: space-between;
}
</style>
</head>
<body>
<div class="container">
<h3>iVANKY</h3>
<h1>Warranty Request</h1>
<div class="wrapper">
<div class="title">Issue</div>
<div class="content" th:text="${subject}"></div>
<ul class="item_list">
<li class="item_li flex-align-space-between">
<div class="title">CountrySite</div>
<div class="content" th:text="${countrySite}"></div>
</li>
<li class="item_li flex-align-space-between">
<div class="title">City</div>
<div class="content" th:text="${city}"></div>
</li>
<li class="item_li flex-align-space-between">
<div class="title">Name</div>
<div class="content" th:text="${name}"></div>
</li>
<li class="item_li flex-align-space-between">
<div class="title">Email</div>
<div class="content email_content" th:text="${email}"></div>
</li>
<li class="item_li flex-align-space-between">
<div class="title">Phone</div>
<div class="content" th:text="${phone}"></div>
</li>
<li class="item_li flex-align-space-between">
<div class="title">OrderId</div>
<div class="content" th:text="${orderId}"></div>
</li>
<li class="item_li flex-align-space-between">
<div class="title">IsWithinDay</div>
<div class="content" th:text="${isWithinDay}"></div>
</li>
<li class="item_li flex-align-space-between">
<div class="title">IsOccurred</div>
<div class="content" th:text="${isOccurred}"></div>
</li>
<li class="item_li flex-align-space-between">
<div class="title">ProductCategory</div>
<div class="content" th:text="${productCategory}"></div>
</li>
<li class="item_li flex-align-space-between">
<div class="title">Image</div>
<img class="img_responsive" alt="Image" th:src="@{${image}}" />
</li>
<li class="item_li flex-align-space-between">
<div class="title">ProductCode</div>
<div class="content" th:text="${productCode}"></div>
</li>
</ul>
<div class="title">Message</div>
<div class="content" th:text="${message}"></div>
<div class="title">Address line 1</div>
<div class="content" th:text="${addressLineOne}"></div>
<div class="title">Address line 2</div>
<div class="content" th:text="${addressLineTwo}"></div>
<ul class="item_list">
<li class="item_li flex-align-space-between">
<div class="title">State/Province/Region</div>
<div class="content" th:text="${state}"></div>
</li>
<li class="item_li flex-align-space-between">
<div class="title">Postal code</div>
<div class="content" th:text="${postalCode}"></div>
</li>
</ul>
</div>
</div>
</body>
</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
@Test
public void sendTemplateMail() {
Context context = new Context();
context.setVariable("subject", subject);
context.setVariable("isWithinDay", isWithinDay);
context.setVariable("productCategory", productCategory);
context.setVariable("isOccurred", isOccurred);
context.setVariable("countrySite", countrySite);
context.setVariable("name", name);
context.setVariable("email", email);
context.setVariable("message", message);
context.setVariable("orderId", orderId);
context.setVariable("image", image);
context.setVariable("productCode", productCode);
context.setVariable("addressLineOne", addressLineOne);
context.setVariable("addressLineTwo", addressLineTwo);
context.setVariable("state", state);
context.setVariable("city", city);
context.setVariable("postalCode", postalCode);
context.setVariable("phone", phone);
String emailContent = templateEngine.process("emailTemplate", context);

return sendHtmlMail(Const.WARRANTY_EMAIL,"iVANKY Warranty",emailContent);
}

发送失败

因为各种原因,总会有邮件发送失败的情况,比如:邮件发送过于频繁、网络异常等。在出现这种情况的时候,我们一般会考虑重新重试发送邮件,会分为以下几个步骤来实现:

  1. 接收到发送邮件请求,首先记录请求并且入库。
  2. 调用邮件发送接口发送邮件,并且将发送结果记录入库。
  3. 启动定时系统扫描时间段内,未发送成功并且重试次数小于3次的邮件,进行再次发送

异步发送

很多时候邮件发送并不是我们主业务必须关注的结果,比如通知类、提醒类的业务可以允许延时或者失败。这个时候可以采用异步的方式来发送邮件,加快主交易执行速度,在实际项目中可以采用MQ发送邮件相关参数,监听到消息队列之后启动发送邮件。

可以参考文章 RabbitMQ 详解

参考文章 Spring Boot (十):邮件服务