📧 Spring Boot 邮件发送系统 - 从零到精通教程
本教程面向编程小白,详细讲解如何在 Spring Boot 项目中设计和实现一个完整的邮件发送系统。
学完本教程,你将能够:
- 理解邮件发送的基本原理
- 掌握 Spring Boot 邮件集成
- 设计灵活的邮件模板系统
- 实现异步邮件发送
- 在任何项目中复制这套设计
📚 目录
1. 邮件发送基础知识
1.1 什么是 SMTP?
SMTP(Simple Mail Transfer Protocol) = 简单邮件传输协议
类比理解:
-
你写了一封信(邮件内容)
-
邮局(SMTP 服务器)帮你送信
-
收件人的邮箱(收件人邮箱服务器)收到信
你的程序 → SMTP服务器 → 收件人邮箱
(邮局) (对方邮箱)
1.2 常用邮箱的 SMTP 配置
| 邮箱提供商 | SMTP 地址 | 端口 | 加密方式 |
|---|---|---|---|
| Gmail | smtp.gmail.com | 587 | TLS |
| QQ 邮箱 | smtp.qq.com | 587/465 | SSL/TLS |
| 163 邮箱 | smtp.163.com | 465 | SSL |
| Outlook | smtp.office365.com | 587 | TLS |
1.3 发送邮件需要什么?
必需信息:
- SMTP 服务器地址 - 邮局的地址
- 端口号 - 邮局的窗口号
- 发件人邮箱 - 你的邮箱地址
- 授权密码 - 证明是你本人(不是登录密码!)
- 收件人邮箱 - 对方的邮箱地址
- 邮件内容 - 你要发的内容
⚠️ 重要:授权密码 ≠ 登录密码
- 登录密码:登录邮箱用的密码
- 授权密码:给第三方程序发邮件用的密码(更安全)
如何获取授权密码:
- QQ 邮箱:设置 → 账户 → POP3/SMTP服务 → 开启服务 → 获取授权码
- Gmail:设置 → 安全性 → 应用专用密码
- 163:设置 → POP3/SMTP/IMAP → 开启服务 → 新增授权密码
2. 本项目的设计架构
2.1 架构图
┌─────────────────────────────────────────────────────────────┐
│ 应用层 (Controller) │
│ - KybController.sendEmailNotification() │
│ - 处理业务逻辑,调用邮件服务 │
└────────────────────┬────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 服务层 (EmailService) │
│ - sendEmail(EmailRequest) │
│ - sendSubmissionSuccessEmail() │
│ - sendReviewApprovedEmail() │
│ - 提供便捷方法,封装业务逻辑 │
└────────────────────┬────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 实现层 (EmailServiceImpl) │
│ 1. 加载邮件模板 (loadEmailTemplate) │
│ 2. 替换变量 (prepareTemplateVariables) │
│ 3. 发送邮件 (doSendEmail) │
│ 4. 异步处理 (sendEmailAsync) │
└────────────────────┬────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 底层 (JavaMail API + SMTP) │
│ - 创建邮件会话 (Session) │
│ - 构建邮件消息 (MimeMessage) │
│ - 发送到 SMTP 服务器 │
└─────────────────────────────────────────────────────────────┘
2.2 设计理念
我们的设计遵循以下原则:
- 📦 模块化:邮件功能独立为公共模块(common-public)
- 🎨 模板化:使用 HTML 模板,内容与代码分离
- ⚡ 异步化:不阻塞主业务流程
- 🔧 配置化:所有配置从配置文件读取
- 🚀 易扩展:添加新邮件类型只需加模板和枚举
2.3 目录结构
zkme-kyb/
├── common-public/ # 公共模块
│ ├── src/main/java/
│ │ └── com/zkme/kyb/common/
│ │ ├── configuration/
│ │ │ └── EmailConfig.java # 📄 邮件配置类
│ │ ├── service/
│ │ │ ├── EmailService.java # 📄 邮件服务接口
│ │ │ └── impl/
│ │ │ └── EmailServiceImpl.java # 📄 邮件服务实现
│ │ ├── model/
│ │ │ └── EmailRequest.java # 📄 邮件请求模型
│ │ └── constant/enums/
│ │ └── EmailTypeEnum.java # 📄 邮件类型枚举
│ └── src/main/resources/
│ ├── application-email.yml # 📄 邮件配置文件
│ └── templates/email/ # 📁 邮件模板目录
│ ├── submission_success.html # 📄 提交成功模板
│ ├── review_approved.html # 📄 审核通过模板
│ └── review_rejected.html # 📄 审核拒绝模板
└── zkme-kyb-popup-api/ # 业务模块
└── src/main/java/
└── controller/
└── KybController.java # 📄 调用邮件服务
3. 核心组件详解
3.1 配置文件(application-email.yml)
作用:存储邮件相关的所有配置
yaml
kyb:
email:
# 是否启用邮件功能(开发时可以关闭)
enabled: true
# SMTP 服务器配置
host: smtp.gmail.com # SMTP 服务器地址
port: 587 # 端口号
# 发件人信息
from: noreply@zkme.com # 发件人邮箱
from-name: zkMe KYB System # 发件人名称(显示给收件人)
# 邮箱认证信息
username: noreply@zkme.com # 登录用户名(通常与 from 相同)
password: your-app-password # 授权密码(不是登录密码!)
# SSL/TLS 加密配置
ssl-enabled: true # 是否启用 SSL
tls-enabled: true # 是否启用 TLS
# 其他配置
async: true # 是否异步发送(推荐开启)
timeout: 10000 # 超时时间(毫秒)
💡 小白提示:
enabled: false→ 邮件功能关闭,所有发送请求都会被跳过async: true→ 发送邮件在后台进行,不会让用户等待timeout: 10000→ 10秒后还没发送成功就超时
3.2 配置类(EmailConfig.java)
作用:将配置文件的内容映射到 Java 对象
java
@Data
@Configuration
@ConfigurationProperties(prefix = "kyb.email") // 读取 kyb.email 开头的配置
public class EmailConfig {
private String host; // 对应 kyb.email.host
private Integer port; // 对应 kyb.email.port
private String from; // 对应 kyb.email.from
private String fromName; // 对应 kyb.email.from-name
private String username; // 对应 kyb.email.username
private String password; // 对应 kyb.email.password
private Boolean sslEnabled = true; // 默认值
private Boolean tlsEnabled = true;
private Boolean enabled = true;
private Boolean async = true;
private Integer timeout = 10000;
}
💡 小白提示:
@ConfigurationProperties(prefix = "kyb.email")→ Spring 会自动从配置文件读取值@Data→ Lombok 自动生成 getter/setter= true→ 默认值,如果配置文件里没写,就用这个值
3.3 邮件请求模型(EmailRequest.java)
作用:封装发送邮件所需的所有信息
java
@Data
public class EmailRequest implements Serializable {
/**
* 收件人邮箱地址
* 例如:user@example.com
*/
private String to;
/**
* 邮件类型(对应 EmailTypeEnum)
* 例如:SUBMISSION_SUCCESS
*/
private String emailType;
/**
* 企业ID(用于查询业务数据)
* 例如:BIZ123456
*/
private String businessId;
/**
* 模板变量(动态内容)
* 例如:{"companyName": "ABC Corp", "submissionTime": "2025-10-31 10:00:00"}
*/
private Map<String, String> templateVariables;
/**
* 附加信息(如审核备注)
* 例如:"缺少营业执照扫描件"
*/
private String additionalInfo;
}
💡 小白提示:
to→ 发给谁emailType→ 发什么类型的邮件templateVariables→ 邮件里的动态内容(如公司名称)additionalInfo→ 额外的信息(可选)
3.4 邮件类型枚举(EmailTypeEnum.java)
作用:定义所有邮件类型及其对应的模板
java
@Getter
public enum EmailTypeEnum {
// 提交成功通知
SUBMISSION_SUCCESS(
"SUBMISSION_SUCCESS", // 代码
"Submission Success", // 邮件主题
"submission_success.html" // 模板文件名
),
// 审核通过通知
REVIEW_APPROVED(
"REVIEW_APPROVED",
"Review Approved",
"review_approved.html"
),
// 审核拒绝通知
REVIEW_REJECTED(
"REVIEW_REJECTED",
"Action Required",
"review_rejected.html"
);
private final String code; // 邮件类型代码
private final String subject; // 邮件主题
private final String templateName; // 模板文件名
// 根据代码查找枚举
public static EmailTypeEnum fromCode(String code) {
for (EmailTypeEnum type : values()) {
if (type.getCode().equals(code)) {
return type;
}
}
return null;
}
}
💡 小白提示:
- 每种邮件类型对应一个枚举值
code→ 用于 API 调用时指定类型subject→ 邮件主题(收件人看到的标题)templateName→ 对应的 HTML 模板文件
3.5 邮件模板(submission_success.html)
作用:定义邮件的 HTML 内容,支持变量替换
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>KYB Submission Success</title>
<style>
/* CSS 样式 */
body {
font-family: Arial, sans-serif;
max-width: 600px;
margin: 0 auto;
}
.header {
background-color: #4CAF50;
color: white;
padding: 20px;
text-align: center;
}
</style>
</head>
<body>
<div class="header">
<h1>✓ KYB Submission Successful</h1>
</div>
<div class="content">
<p>Dear <strong>{{companyName}}</strong>,</p>
<p>Thank you for submitting your KYB information.</p>
<div class="info-box">
<div>Business ID: {{businessId}}</div>
<div>Company Name: {{companyName}}</div>
<div>Submission Time: {{submissionTime}}</div>
</div>
<p>Best regards,<br>zkMe KYB Team</p>
</div>
</body>
</html>
变量替换原理:
模板内容:<p>Dear {{companyName}}</p>
↓ 替换
实际内容:<p>Dear ABC Corporation</p>
💡 小白提示:
{``{变量名}}→ 会被实际值替换- 可以使用完整的 HTML/CSS
- 模板文件放在
resources/templates/email/目录
3.6 邮件服务接口(EmailService.java)
作用:定义邮件服务的公共接口
java
public interface EmailService {
/**
* 通用发送邮件方法
*
* @param req 邮件请求对象
*/
void sendEmail(EmailRequest req);
/**
* 发送提交成功邮件(便捷方法)
*
* @param businessId 企业ID
* @param toEmail 收件人邮箱
*/
void sendSubmissionSuccessEmail(String businessId, String toEmail);
/**
* 发送审核通过邮件(便捷方法)
*
* @param businessId 企业ID
* @param toEmail 收件人邮箱
* @param reviewerName 审核人姓名
*/
void sendReviewApprovedEmail(String businessId, String toEmail, String reviewerName);
}
💡 小白提示:
sendEmail()→ 通用方法,可以发任何类型的邮件sendSubmissionSuccessEmail()→ 专门发提交成功邮件的便捷方法- 便捷方法内部调用
sendEmail()
3.7 邮件服务实现(EmailServiceImpl.java)
这是核心实现类,分几个部分讲解:
3.7.1 发送邮件主流程
java
@Service
public class EmailServiceImpl implements EmailService {
@Resource
private EmailConfig emailConfig;
@Override
public void sendEmail(EmailRequest req) {
log.info("Send email request. to={}, type={}", req.getTo(), req.getEmailType());
// 1. 检查邮件功能是否启用
if (!emailConfig.getEnabled()) {
log.warn("Email feature is disabled.");
return; // 功能关闭,直接返回
}
// 2. 验证邮件类型
EmailTypeEnum emailType = EmailTypeEnum.fromCode(req.getEmailType());
if (emailType == null) {
throw new IllegalArgumentException("Invalid email type");
}
// 3. 准备模板变量
Map<String, String> variables = prepareTemplateVariables(req);
// 4. 加载并处理邮件模板
String htmlContent = loadEmailTemplate(emailType.getTemplateName(), variables);
// 5. 发送邮件(同步或异步)
if (emailConfig.getAsync()) {
sendEmailAsync(req.getTo(), emailType.getSubject(), htmlContent);
} else {
sendEmailSync(req.getTo(), emailType.getSubject(), htmlContent);
}
log.info("Email sent successfully. to={}", req.getTo());
}
}
流程图:
开始
↓
检查邮件功能是否启用?
├─ 否 → 返回(不发送)
└─ 是 ↓
验证邮件类型
↓
准备模板变量
↓
加载邮件模板
↓
替换变量
↓
异步发送?
├─ 是 → 后台发送
└─ 否 → 同步发送
↓
结束
3.7.2 准备模板变量
java
private Map<String, String> prepareTemplateVariables(EmailRequest req) {
Map<String, String> variables = new HashMap<>();
// 1. 添加用户自定义变量
if (!CollectionUtils.isEmpty(req.getTemplateVariables())) {
variables.putAll(req.getTemplateVariables());
}
// 2. 添加系统通用变量
variables.put("currentYear", String.valueOf(LocalDateTime.now().getYear()));
variables.put("dashboardUrl", "https://kyb.zkme.com/dashboard");
// 3. 添加业务相关变量
if (StringUtils.hasText(req.getBusinessId())) {
variables.put("businessId", req.getBusinessId());
}
// 4. 添加附加信息
if (StringUtils.hasText(req.getAdditionalInfo())) {
variables.put("additionalInfo", req.getAdditionalInfo());
}
return variables;
}
变量优先级:
1. 用户自定义变量(templateVariables)← 最高优先级
2. 系统通用变量(currentYear, dashboardUrl)
3. 业务相关变量(businessId)
4. 附加信息(additionalInfo)
3.7.3 加载邮件模板
java
private String loadEmailTemplate(String templateName, Map<String, String> variables) {
try {
// 1. 从 classpath 加载模板文件
String templatePath = "/templates/email/" + templateName;
InputStream inputStream = getClass().getResourceAsStream(templatePath);
if (inputStream == null) {
throw new RuntimeException("Template not found: " + templateName);
}
// 2. 读取模板内容
String content = new BufferedReader(
new InputStreamReader(inputStream, StandardCharsets.UTF_8))
.lines()
.collect(Collectors.joining("\n"));
// 3. 替换所有变量
for (Map.Entry<String, String> entry : variables.entrySet()) {
String placeholder = "{{" + entry.getKey() + "}}";
String value = entry.getValue() != null ? entry.getValue() : "";
content = content.replace(placeholder, value);
}
return content;
} catch (Exception e) {
log.error("Failed to load template: {}", templateName, e);
throw new RuntimeException("Failed to load template", e);
}
}
替换示例:
html
<!-- 原始模板 -->
<p>Dear {{companyName}}, your ID is {{businessId}}</p>
<!-- variables = {"companyName": "ABC Corp", "businessId": "BIZ123"} -->
<!-- 替换后 -->
<p>Dear ABC Corp, your ID is BIZ123</p>
3.7.4 执行邮件发送
java
private void doSendEmail(String to, String subject, String htmlContent)
throws MessagingException {
log.info("Sending email. to={}, subject={}", to, subject);
// 1. 配置 SMTP 属性
Properties props = new Properties();
props.put("mail.smtp.host", emailConfig.getHost());
props.put("mail.smtp.port", emailConfig.getPort());
props.put("mail.smtp.auth", "true");
if (emailConfig.getSslEnabled()) {
props.put("mail.smtp.ssl.enable", "true");
}
if (emailConfig.getTlsEnabled()) {
props.put("mail.smtp.starttls.enable", "true");
}
props.put("mail.smtp.timeout", emailConfig.getTimeout());
// 2. 创建邮件会话(Session)
Session session = Session.getInstance(props, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(
emailConfig.getUsername(),
emailConfig.getPassword()
);
}
});
// 3. 创建邮件消息
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(emailConfig.getFrom(), emailConfig.getFromName()));
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));
message.setSubject(subject);
message.setContent(htmlContent, "text/html; charset=utf-8");
// 4. 发送邮件
Transport.send(message);
log.info("Email sent successfully. to={}", to);
}
发送流程:
1. 配置 SMTP 参数
├─ 服务器地址和端口
├─ 认证信息
└─ 加密方式
2. 创建邮件会话
└─ 提供用户名和密码
3. 构建邮件消息
├─ 设置发件人
├─ 设置收件人
├─ 设置主题
└─ 设置内容(HTML格式)
4. 调用 Transport.send() 发送
3.7.5 异步发送
java
/**
* 异步发送邮件(不阻塞主线程)
*/
@Async // Spring 注解,标记为异步方法
public void sendEmailAsync(String to, String subject, String htmlContent) {
try {
doSendEmail(to, subject, htmlContent);
} catch (Exception e) {
log.error("Failed to send email asynchronously. to={}", to, e);
// 异步发送失败,记录日志但不抛出异常
}
}
/**
* 同步发送邮件(阻塞主线程)
*/
private void sendEmailSync(String to, String subject, String htmlContent) {
try {
doSendEmail(to, subject, htmlContent);
} catch (Exception e) {
log.error("Failed to send email synchronously. to={}", to, e);
throw new RuntimeException("Failed to send email", e);
}
}
异步 vs 同步:
同步发送:
用户提交表单 → 发送邮件(3秒) → 返回成功
用户等待时间:3秒
异步发送:
用户提交表单 → 返回成功(立即) → 后台发送邮件(3秒)
用户等待时间:<1秒
💡 小白提示:
- 异步发送需要在启动类上加
@EnableAsync - 异步方法必须是
public且被 Spring 管理 - 异步发送失败不会影响主业务
4. 实施步骤(分步骤实现)
按照以下步骤,你可以在任何 Spring Boot 项目中实现邮件功能。
步骤1:添加依赖
在 pom.xml 中添加邮件依赖:
xml
<dependencies>
<!-- Spring Boot Starter Mail -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- Lombok(可选,简化代码) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
步骤2:创建配置文件
创建 src/main/resources/application-email.yml:
yaml
# application-email.yml
kyb:
email:
enabled: true
host: smtp.gmail.com
port: 587
from: your-email@gmail.com
from-name: Your App Name
username: your-email@gmail.com
password: your-app-password # 获取方法见1.3节
ssl-enabled: true
tls-enabled: true
async: true
timeout: 10000
在主配置文件 application.yml 中引入:
yaml
# application.yml
spring:
profiles:
include: email
步骤3:创建配置类
创建 EmailConfig.java:
java
package com.yourproject.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "kyb.email")
public class EmailConfig {
private String host;
private Integer port;
private String from;
private String fromName;
private String username;
private String password;
private Boolean sslEnabled = true;
private Boolean tlsEnabled = true;
private Boolean enabled = true;
private Boolean async = true;
private Integer timeout = 10000;
}
步骤4:创建邮件类型枚举
创建 EmailTypeEnum.java:
java
package com.yourproject.enums;
import lombok.Getter;
@Getter
public enum EmailTypeEnum {
WELCOME("WELCOME", "Welcome", "welcome.html"),
PASSWORD_RESET("PASSWORD_RESET", "Password Reset", "password_reset.html");
private final String code;
private final String subject;
private final String templateName;
EmailTypeEnum(String code, String subject, String templateName) {
this.code = code;
this.subject = subject;
this.templateName = templateName;
}
public static EmailTypeEnum fromCode(String code) {
for (EmailTypeEnum type : values()) {
if (type.getCode().equals(code)) {
return type;
}
}
return null;
}
}
步骤5:创建请求模型
创建 EmailRequest.java:
java
package com.yourproject.model;
import lombok.Data;
import java.io.Serializable;
import java.util.Map;
@Data
public class EmailRequest implements Serializable {
private String to;
private String emailType;
private Map<String, String> templateVariables;
}
步骤6:创建邮件服务接口
创建 EmailService.java:
java
package com.yourproject.service;
import com.yourproject.model.EmailRequest;
public interface EmailService {
void sendEmail(EmailRequest req);
}
步骤7:创建邮件服务实现
创建 EmailServiceImpl.java(完整代码见3.7节)。
步骤8:创建邮件模板
创建 src/main/resources/templates/email/welcome.html:
html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Welcome</title>
</head>
<body>
<h1>Welcome, {{userName}}!</h1>
<p>Thank you for registering.</p>
</body>
</html>
步骤9:启用异步支持
在主启动类上添加 @EnableAsync:
java
@SpringBootApplication
@EnableAsync // 启用异步支持
public class YourApplication {
public static void main(String[] args) {
SpringApplication.run(YourApplication.class, args);
}
}
步骤10:使用邮件服务
在 Controller 或 Service 中注入并使用:
java
@RestController
@RequestMapping("/api")
public class UserController {
@Autowired
private EmailService emailService;
@PostMapping("/register")
public ApiResp register(@RequestBody UserRegisterReq req) {
// 1. 注册用户
userService.register(req);
// 2. 发送欢迎邮件
EmailRequest emailReq = new EmailRequest();
emailReq.setTo(req.getEmail());
emailReq.setEmailType("WELCOME");
Map<String, String> variables = new HashMap<>();
variables.put("userName", req.getName());
emailReq.setTemplateVariables(variables);
emailService.sendEmail(emailReq);
return ApiResp.success();
}
}
5. 使用示例
5.1 发送提交成功邮件
java
@Autowired
private EmailService emailService;
public void notifySubmissionSuccess(String businessId, String userEmail) {
// 方式1:使用便捷方法
emailService.sendSubmissionSuccessEmail(businessId, userEmail);
// 方式2:使用通用方法
EmailRequest req = new EmailRequest();
req.setTo(userEmail);
req.setEmailType("SUBMISSION_SUCCESS");
req.setBusinessId(businessId);
Map<String, String> variables = new HashMap<>();
variables.put("companyName", "ABC Corporation");
variables.put("submissionTime", "2025-10-31 10:00:00");
req.setTemplateVariables(variables);
emailService.sendEmail(req);
}
5.2 发送审核通过邮件
java
public void notifyApproval(String businessId, String userEmail, String reviewer) {
emailService.sendReviewApprovedEmail(businessId, userEmail, reviewer);
}
5.3 发送审核拒绝邮件
java
public void notifyRejection(String businessId, String userEmail, String reason) {
emailService.sendReviewRejectedEmail(businessId, userEmail, reason);
}
5.4 发送自定义邮件
java
public void sendCustomEmail(String to, String subject, String content) {
// 1. 创建新的邮件类型枚举
// CUSTOM("CUSTOM", subject, "custom.html")
// 2. 创建模板文件 custom.html
// 3. 发送邮件
EmailRequest req = new EmailRequest();
req.setTo(to);
req.setEmailType("CUSTOM");
Map<String, String> variables = new HashMap<>();
variables.put("content", content);
req.setTemplateVariables(variables);
emailService.sendEmail(req);
}
6. 常见问题与解决方案
Q1: 发送邮件失败,报 "Authentication failed"
原因:授权密码错误或未开启SMTP服务
解决方案:
- 检查
password是否是授权码(不是登录密码) - 检查邮箱是否开启了 SMTP 服务
- Gmail 用户需要开启"不太安全的应用访问权限"或使用应用专用密码
Q2: 邮件发送很慢,影响用户体验
原因:使用了同步发送
解决方案:
- 配置文件设置
async: true - 启动类添加
@EnableAsync - 确保
sendEmailAsync方法是public
Q3: 模板变量没有被替换
原因:
- 变量名拼写错误
- 变量值为 null
- 模板语法错误
解决方案:
java
// 错误示例
variables.put("companyname", "ABC"); // 小写
模板: {{companyName}} // 驼峰 → 不匹配
// 正确示例
variables.put("companyName", "ABC"); // 驼峰
模板: {{companyName}} // 驼峰 → 匹配
Q4: 发送HTML邮件显示为纯文本
原因:Content-Type 设置错误
解决方案:
java
// 确保使用
message.setContent(htmlContent, "text/html; charset=utf-8");
// 而不是
message.setText(htmlContent); // ❌ 这会显示为纯文本
Q5: 邮件进入垃圾箱
原因:
- 发件人声誉低
- 缺少 SPF/DKIM 配置
- 邮件内容被识别为垃圾邮件
解决方案:
- 使用企业邮箱(不要用免费邮箱发大量邮件)
- 配置域名的 SPF 和 DKIM 记录
- 避免使用垃圾邮件常用词(免费、中奖等)
- 添加退订链接
Q6: 开发环境不想发真实邮件
解决方案1:关闭邮件功能
yaml
kyb:
email:
enabled: false # 开发环境关闭
解决方案2:使用邮件测试工具
yaml
# 开发环境配置
kyb:
email:
host: smtp.mailtrap.io
port: 2525
username: your-mailtrap-username
password: your-mailtrap-password
7. 最佳实践
7.1 配置管理
✅ 推荐做法:
yaml
# 使用环境变量
kyb:
email:
password: ${EMAIL_PASSWORD} # 从环境变量读取
# 或使用配置中心
spring:
cloud:
config:
uri: http://config-server:8888
❌ 避免做法:
yaml
# 不要把密码直接写在配置文件里提交到 Git
kyb:
email:
password: my-real-password # ❌ 危险!
7.2 错误处理
✅ 推荐做法:
java
@Override
public void sendEmail(EmailRequest req) {
try {
// 发送邮件逻辑
doSendEmail(to, subject, content);
// 记录成功日志
log.info("Email sent successfully. to={}, type={}",
req.getTo(), req.getEmailType());
} catch (MessagingException e) {
// 记录详细错误
log.error("Failed to send email. to={}, type={}, error={}",
req.getTo(), req.getEmailType(), e.getMessage(), e);
// 异步发送时不抛出异常
if (!emailConfig.getAsync()) {
throw new RuntimeException("Email sending failed", e);
}
}
}
7.3 日志记录
✅ 推荐做法:
java
// 记录关键信息
log.info("Sending email. to={}, subject={}, type={}",
to, subject, emailType);
log.info("Email sent successfully. to={}, duration={}ms",
to, System.currentTimeMillis() - startTime);
// 记录错误详情
log.error("Failed to send email. to={}, subject={}, error={}",
to, subject, e.getMessage(), e);
7.4 性能优化
1. 使用异步发送
java
// ✅ 推荐:异步发送
@Async
public void sendEmailAsync(String to, String subject, String content) {
doSendEmail(to, subject, content);
}
// ❌ 避免:在关键业务流程中同步发送
public void processOrder(Order order) {
saveOrder(order);
sendEmailSync(order.getEmail(), "Order Confirmed", content); // 用户要等
return "success";
}
2. 使用连接池
java
// 配置 SMTP 连接池
props.put("mail.smtp.connectionpool.enable", "true");
props.put("mail.smtp.connectionpool.size", "10");
3. 批量发送
java
// ✅ 推荐:批量发送
public void sendBulkEmail(List<String> recipients, String subject, String content) {
// 使用线程池
ExecutorService executor = Executors.newFixedThreadPool(10);
for (String recipient : recipients) {
executor.submit(() -> sendEmail(recipient, subject, content));
}
executor.shutdown();
}
7.5 安全性
1. 保护授权密码
java
// ✅ 推荐:从环境变量或密钥管理服务读取
@Value("${EMAIL_PASSWORD}")
private String password;
// ❌ 避免:硬编码
private String password = "my-password"; // ❌ 危险!
2. 验证收件人邮箱
java
// ✅ 推荐:验证邮箱格式
private boolean isValidEmail(String email) {
String regex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$";
return email != null && email.matches(regex);
}
@Override
public void sendEmail(EmailRequest req) {
if (!isValidEmail(req.getTo())) {
throw new IllegalArgumentException("Invalid email address");
}
// 发送邮件...
}
7.6 模板管理
1. 模板复用
html
<!-- 创建公共头部 header.html -->
<div class="header">
<img src="{{logoUrl}}" alt="Logo">
<h1>{{companyName}}</h1>
</div>
<!-- 在其他模板中引用 -->
<!-- welcome.html -->
{{include:header.html}}
<p>Welcome, {{userName}}!</p>
2. 多语言支持
templates/email/
├── en/
│ ├── welcome.html
│ └── password_reset.html
└── zh/
├── welcome.html
└── password_reset.html
java
private String loadEmailTemplate(String templateName, String language) {
String templatePath = "/templates/email/" + language + "/" + templateName;
// 加载逻辑...
}
7.7 监控与告警
1. 记录发送统计
java
@Component
public class EmailMetrics {
private AtomicLong sentCount = new AtomicLong(0);
private AtomicLong failedCount = new AtomicLong(0);
public void recordSuccess() {
sentCount.incrementAndGet();
}
public void recordFailure() {
failedCount.incrementAndGet();
}
@Scheduled(fixedRate = 60000) // 每分钟打印一次
public void printMetrics() {
log.info("Email metrics - Sent: {}, Failed: {}",
sentCount.get(), failedCount.get());
}
}
2. 失败重试机制
java
@Retryable(
value = {MessagingException.class},
maxAttempts = 3,
backoff = @Backoff(delay = 2000)
)
public void sendEmailWithRetry(String to, String subject, String content) {
doSendEmail(to, subject, content);
}
8. 总结
8.1 核心要点回顾
- 配置化 - 所有配置从配置文件读取
- 模板化 - 使用 HTML 模板,内容与代码分离
- 异步化 - 不阻塞主业务流程
- 模块化 - 邮件功能独立为公共模块
- 易扩展 - 添加新类型只需加模板和枚举
8.2 项目结构总结
邮件系统架构
├── 配置层 (EmailConfig + application-email.yml)
├── 枚举层 (EmailTypeEnum)
├── 模型层 (EmailRequest)
├── 服务层 (EmailService + EmailServiceImpl)
└── 模板层 (HTML 模板文件)
8.3 关键技术点
- Spring Boot Mail
- JavaMail API
- SMTP 协议
- HTML 邮件模板
- 异步处理 (@Async)
- 配置管理 (@ConfigurationProperties)
8.4 下一步学习
- 邮件队列 - 使用 RabbitMQ/Kafka 管理邮件队列
- 定时任务 - 定时发送邮件(如每日报告)
- 邮件跟踪 - 记录邮件打开率、点击率
- 富文本编辑器 - 在管理后台编辑邮件模板
- A/B测试 - 测试不同邮件内容的效果
附录
A. 完整代码仓库
本教程的完整代码可以在以下位置找到:
- 配置文件:
common-public/src/main/resources/application-email.yml - 服务实现:
common-public/src/main/java/com/zkme/kyb/common/service/impl/EmailServiceImpl.java - 邮件模板:
common-public/src/main/resources/templates/email/
B. 参考资料
C. 常用邮箱SMTP配置速查表
| 邮箱 | SMTP | 端口 | SSL | 备注 |
|---|---|---|---|---|
| Gmail | smtp.gmail.com | 587 | TLS | 需开启应用专用密码 |
| Outlook | smtp.office365.com | 587 | TLS | |
| smtp.qq.com | 465/587 | SSL/TLS | 需开启SMTP服务 | |
| 163 | smtp.163.com | 465 | SSL | 需开启SMTP服务 |
| Yahoo | smtp.mail.yahoo.com | 587 | TLS | |
| iCloud | smtp.mail.me.com | 587 | TLS |
🎉 恭喜!你已经掌握了 Spring Boot 邮件系统的设计和实现!
如有问题,欢迎查阅代码或提问。祝你在其他项目中成功实现邮件功能!