前言
邮件发送是应用中的常见需求,如用户注册验证、密码重置、通知告警等。Spring Boot 通过 spring-boot-starter-mail 提供了便捷的邮件发送功能。
快速开始
1. 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
2. 基础配置
spring:
mail:
host: smtp.qq.com
port: 465
username: your-email@qq.com
password: your-auth-code
properties:
mail:
smtp:
auth: true
ssl:
enable: true
socketFactory:
class: javax.net.ssl.SSLSocketFactory
3. 发送简单邮件
@Service
@RequiredArgsConstructor
public class EmailService {
private final JavaMailSender mailSender;
@Value("${spring.mail.username}")
private String from;
/**
* 发送简单邮件
*/
public void sendSimpleEmail(String to, String subject, String content) {
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(from);
message.setTo(to);
message.setSubject(subject);
message.setText(content);
mailSender.send(message);
log.info("简单邮件发送成功:{}", to);
}
/**
* 发送验证码
*/
public void sendVerificationCode(String to, String code) {
String subject = "验证码";
String content = String.format(
"您的验证码是:%s,有效期 10 分钟。",
code
);
sendSimpleEmail(to, subject, content);
}
}
4. 发送 HTML 邮件
@Service
@RequiredArgsConstructor
public class EmailService {
private final JavaMailSender mailSender;
private final String from;
/**
* 发送 HTML 邮件
*/
public void sendHtmlEmail(String to, String subject, String htmlContent) {
MimeMessage message = mailSender.createMimeMessage();
try {
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(htmlContent, true); // true 表示 HTML
mailSender.send(message);
log.info("HTML 邮件发送成功:{}", to);
} catch (MessagingException e) {
log.error("HTML 邮件发送失败", e);
throw new RuntimeException("邮件发送失败", e);
}
}
/**
* 发送欢迎邮件
*/
public void sendWelcomeEmail(String to, String username) {
String subject = "欢迎注册";
String htmlContent = String.format("""
<html>
<body>
<h1>欢迎 %s 加入我们!</h1>
<p>感谢您注册我们的平台。</p>
<p>如有任何问题,请随时联系客服。</p>
<br/>
<p>此致</p>
<p>团队</p>
</body>
</html>
""", username);
sendHtmlEmail(to, subject, htmlContent);
}
}
5. 发送带附件的邮件
@Service
@RequiredArgsConstructor
public class EmailService {
/**
* 发送带附件的邮件
*/
public void sendAttachmentEmail(
String to,
String subject,
String content,
File attachment
) {
MimeMessage message = mailSender.createMimeMessage();
try {
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(content, true);
// 添加附件
helper.addAttachment(attachment.getName(), attachment);
mailSender.send(message);
log.info("带附件邮件发送成功:{}", to);
} catch (MessagingException e) {
log.error("带附件邮件发送失败", e);
throw new RuntimeException("邮件发送失败", e);
}
}
/**
* 发送多个附件
*/
public void sendMultipleAttachments(
String to,
String subject,
String content,
List<File> attachments
) {
MimeMessage message = mailSender.createMimeMessage();
try {
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(content, true);
// 添加多个附件
for (File attachment : attachments) {
helper.addAttachment(attachment.getName(), attachment);
}
mailSender.send(message);
log.info("多附件邮件发送成功:{}", to);
} catch (MessagingException e) {
log.error("多附件邮件发送失败", e);
throw new RuntimeException("邮件发送失败", e);
}
}
}
模板邮件
1. 集成 Thymeleaf
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2. 创建邮件模板
<!-- templates/email/verification.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>验证码</title>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
color: #333;
}
.container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.code {
font-size: 24px;
font-weight: bold;
color: #007bff;
padding: 10px 20px;
background: #f0f0f0;
display: inline-block;
margin: 20px 0;
}
.footer {
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #ddd;
font-size: 12px;
color: #999;
}
</style>
</head>
<body>
<div class="container">
<h2>验证码</h2>
<p>您好 <span th:text="${username}">用户</span>,</p>
<p>您的验证码是:</p>
<div class="code" th:text="${code}">123456</div>
<p>验证码有效期 10 分钟,请尽快使用。</p>
<p>如果不是您本人操作,请忽略此邮件。</p>
<div class="footer">
<p>此邮件由系统自动发送,请勿回复。</p>
<p>© 2024 Demo Company. All rights reserved.</p>
</div>
</div>
</body>
</html>
3. 发送模板邮件
@Service
@RequiredArgsConstructor
public class TemplateEmailService {
private final JavaMailSender mailSender;
private final TemplateEngine templateEngine;
private final String from;
/**
* 发送模板邮件
*/
public void sendTemplateEmail(
String to,
String subject,
String templateName,
Map<String, Object> variables
) {
try {
// 渲染模板
Context context = new Context();
variables.forEach(context::setVariable);
String htmlContent = templateEngine.process(templateName, context);
// 发送邮件
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(htmlContent, true);
mailSender.send(message);
log.info("模板邮件发送成功:{}", to);
} catch (MessagingException | RuntimeException e) {
log.error("模板邮件发送失败", e);
throw new RuntimeException("邮件发送失败", e);
}
}
/**
* 发送验证码邮件
*/
public void sendVerificationCodeEmail(String to, String username, String code) {
Map<String, Object> variables = new HashMap<>();
variables.put("username", username);
variables.put("code", code);
sendTemplateEmail(to, "验证码", "email/verification", variables);
}
/**
* 发送订单确认邮件
*/
public void sendOrderConfirmationEmail(String to, Order order) {
Map<String, Object> variables = new HashMap<>();
variables.put("order", order);
variables.put("username", order.getUsername());
sendTemplateEmail(
to,
"订单确认",
"email/order-confirmation",
variables
);
}
}
批量邮件
1. 批量发送
@Service
@RequiredArgsConstructor
public class BatchEmailService {
private final JavaMailSender mailSender;
private final String from;
/**
* 批量发送简单邮件
*/
public void sendBatchEmails(
List<String> recipients,
String subject,
String content
) {
for (String to : recipients) {
try {
sendSimpleEmail(to, subject, content);
} catch (Exception e) {
log.error("发送邮件失败:{}", to, e);
}
}
}
/**
* 异步批量发送
*/
@Async
public void sendBatchEmailsAsync(
List<String> recipients,
String subject,
String content
) {
CountDownLatch latch = new CountDownLatch(recipients.size());
for (String to : recipients) {
CompletableFuture.runAsync(() -> {
try {
sendSimpleEmail(to, subject, content);
} catch (Exception e) {
log.error("发送邮件失败:{}", to, e);
} finally {
latch.countDown();
}
});
}
try {
latch.await();
log.info("批量邮件发送完成");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
2. 限流发送
@Service
@RequiredArgsConstructor
public class RateLimitEmailService {
private final JavaMailSender mailSender;
private final RateLimiter rateLimiter = RateLimiter.create(10); // 每秒 10 封
public void sendWithRateLimit(String to, String subject, String content) {
// 获取许可(限流)
rateLimiter.acquire();
sendSimpleEmail(to, subject, content);
}
}
最佳实践
1. 邮件队列
@Component
public class EmailQueueConsumer {
@Autowired
private EmailService emailService;
@RabbitListener(queues = "email.queue")
public void consume(EmailMessage emailMessage) {
try {
emailService.sendSimpleEmail(
emailMessage.getTo(),
emailMessage.getSubject(),
emailMessage.getContent()
);
} catch (Exception e) {
log.error("邮件发送失败", e);
// 重试或记录失败
handleFailure(emailMessage, e);
}
}
}
2. 错误重试
@Service
@RequiredArgsConstructor
public class RetryEmailService {
private final JavaMailSender mailSender;
@Retryable(
value = {MailSendException.class},
maxAttempts = 3,
backoff = @Backoff(delay = 1000)
)
public void sendWithRetry(String to, String subject, String content) {
sendSimpleEmail(to, subject, content);
}
@Recover
public void recover(MailSendException e, String to, String subject, String content) {
log.error("邮件发送最终失败:{}", to, e);
// 记录到失败队列
failedEmailRepository.save(new FailedEmail(to, subject, content, e.getMessage()));
}
}
3. 邮件追踪
@Service
public class TrackingEmailService {
@Autowired
private JavaMailSender mailSender;
public void sendTrackedEmail(String to, String subject, String content) {
MimeMessage message = mailSender.createMimeMessage();
try {
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(content, true);
// 添加追踪头
message.setHeader("X-Mailer", "Demo-Mailer");
message.setHeader("X-Priority", "3");
// 记录发送日志
emailLogRepository.save(new EmailLog(to, subject, LocalDateTime.now()));
mailSender.send(message);
log.info("追踪邮件发送成功:{}", to);
} catch (MessagingException e) {
log.error("追踪邮件发送失败", e);
throw new RuntimeException("邮件发送失败", e);
}
}
}
4. 配置优化
spring:
mail:
host: smtp.qq.com
port: 465
username: your-email@qq.com
password: your-auth-code
# 连接池配置
properties:
mail:
smtp:
auth: true
ssl:
enable: true
connectiontimeout: 5000
timeout: 3000
writetimeout: 5000
# 连接池
smtps:
connectionpool:
enable: true
timeout: 300000
idleTimeout: 60000
5. 多邮箱服务商
@Configuration
public class MultiMailConfig {
@Bean
@Primary
@ConfigurationProperties("spring.mail")
public JavaMailSender primaryMailSender() {
return new JavaMailSenderImpl();
}
@Bean
@ConfigurationProperties("spring.mail.backup")
public JavaMailSender backupMailSender() {
return new JavaMailSenderImpl();
}
}
spring:
mail:
host: smtp.qq.com
port: 465
username: primary@qq.com
password: xxx
mail:
backup:
host: smtp.163.com
port: 465
username: backup@163.com
password: xxx
总结
邮件发送要点:
- ✅ 简单邮件 - SimpleMailMessage
- ✅ HTML 邮件 - MimeMessageHelper
- ✅ 附件邮件 - 添加附件
- ✅ 模板邮件 - Thymeleaf 模板
- ✅ 最佳实践 - 队列、重试、追踪
邮件是用户触达的重要渠道。