Spring Boot 邮件发送系统 - 从零到精通教程
📧 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)
作用:存储邮件相关的所有配置
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 # 是否启用 SSLtls-enabled: true # 是否启用 TLS# 其他配置async: true # 是否异步发送(推荐开启)timeout: 10000 # 超时时间(毫秒)
💡 小白提示:
enabled: false→ 邮件功能关闭,所有发送请求都会被跳过async: true→ 发送邮件在后台进行,不会让用户等待timeout: 10000→ 10秒后还没发送成功就超时
3.2 配置类(EmailConfig.java)
作用:将配置文件的内容映射到 Java 对象
@Data
@Configuration
@ConfigurationProperties(prefix = "kyb.email") // 读取 kyb.email 开头的配置
public class EmailConfig {private String host; // 对应 kyb.email.hostprivate Integer port; // 对应 kyb.email.portprivate String from; // 对应 kyb.email.fromprivate String fromName; // 对应 kyb.email.from-nameprivate String username; // 对应 kyb.email.usernameprivate String password; // 对应 kyb.email.passwordprivate 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)
作用:封装发送邮件所需的所有信息
@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)
作用:定义所有邮件类型及其对应的模板
@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 内容,支持变量替换
<!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)
作用:定义邮件服务的公共接口
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 发送邮件主流程
@Service
public class EmailServiceImpl implements EmailService {@Resourceprivate EmailConfig emailConfig;@Overridepublic 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 准备模板变量
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 加载邮件模板
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);}
}
替换示例:
<!-- 原始模板 -->
<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 执行邮件发送
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() {@Overrideprotected 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 异步发送
/*** 异步发送邮件(不阻塞主线程)*/
@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 中添加邮件依赖:
<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:
# application-email.yml
kyb:email:enabled: truehost: smtp.gmail.comport: 587from: your-email@gmail.comfrom-name: Your App Nameusername: your-email@gmail.compassword: your-app-password # 获取方法见1.3节ssl-enabled: truetls-enabled: trueasync: truetimeout: 10000
在主配置文件 application.yml 中引入:
# application.yml
spring:profiles:include: email
步骤3:创建配置类
创建 EmailConfig.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:
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:
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:
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:
<!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:
@SpringBootApplication
@EnableAsync // 启用异步支持
public class YourApplication {public static void main(String[] args) {SpringApplication.run(YourApplication.class, args);}
}
步骤10:使用邮件服务
在 Controller 或 Service 中注入并使用:
@RestController
@RequestMapping("/api")
public class UserController {@Autowiredprivate 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 发送提交成功邮件
@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 发送审核通过邮件
public void notifyApproval(String businessId, String userEmail, String reviewer) {emailService.sendReviewApprovedEmail(businessId, userEmail, reviewer);
}
5.3 发送审核拒绝邮件
public void notifyRejection(String businessId, String userEmail, String reason) {emailService.sendReviewRejectedEmail(businessId, userEmail, reason);
}
5.4 发送自定义邮件
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
- 模板语法错误
解决方案:
// 错误示例
variables.put("companyname", "ABC"); // 小写
模板: {{companyName}} // 驼峰 → 不匹配// 正确示例
variables.put("companyName", "ABC"); // 驼峰
模板: {{companyName}} // 驼峰 → 匹配
Q4: 发送HTML邮件显示为纯文本
原因:Content-Type 设置错误
解决方案:
// 确保使用
message.setContent(htmlContent, "text/html; charset=utf-8");// 而不是
message.setText(htmlContent); // ❌ 这会显示为纯文本
Q5: 邮件进入垃圾箱
原因:
- 发件人声誉低
- 缺少 SPF/DKIM 配置
- 邮件内容被识别为垃圾邮件
解决方案:
- 使用企业邮箱(不要用免费邮箱发大量邮件)
- 配置域名的 SPF 和 DKIM 记录
- 避免使用垃圾邮件常用词(免费、中奖等)
- 添加退订链接
Q6: 开发环境不想发真实邮件
解决方案1:关闭邮件功能
kyb:email:enabled: false # 开发环境关闭
解决方案2:使用邮件测试工具
- Mailtrap - 邮件测试服务
- MailHog - 本地邮件测试
# 开发环境配置
kyb:email:host: smtp.mailtrap.ioport: 2525username: your-mailtrap-usernamepassword: your-mailtrap-password
7. 最佳实践
7.1 配置管理
✅ 推荐做法:
# 使用环境变量
kyb:email:password: ${EMAIL_PASSWORD} # 从环境变量读取# 或使用配置中心
spring:cloud:config:uri: http://config-server:8888
❌ 避免做法:
# 不要把密码直接写在配置文件里提交到 Git
kyb:email:password: my-real-password # ❌ 危险!
7.2 错误处理
✅ 推荐做法:
@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 日志记录
✅ 推荐做法:
// 记录关键信息
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. 使用异步发送
// ✅ 推荐:异步发送
@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. 使用连接池
// 配置 SMTP 连接池
props.put("mail.smtp.connectionpool.enable", "true");
props.put("mail.smtp.connectionpool.size", "10");
3. 批量发送
// ✅ 推荐:批量发送
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. 保护授权密码
// ✅ 推荐:从环境变量或密钥管理服务读取
@Value("${EMAIL_PASSWORD}")
private String password;// ❌ 避免:硬编码
private String password = "my-password"; // ❌ 危险!
2. 验证收件人邮箱
// ✅ 推荐:验证邮箱格式
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. 模板复用
<!-- 创建公共头部 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
private String loadEmailTemplate(String templateName, String language) {String templatePath = "/templates/email/" + language + "/" + templateName;// 加载逻辑...
}
7.7 监控与告警
1. 记录发送统计
@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. 失败重试机制
@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. 参考资料
- Spring Boot Mail 官方文档
- JavaMail API 文档
- HTML Email 最佳实践
- 邮件模板设计工具
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 邮件系统的设计和实现!
如有问题,欢迎查阅代码或提问。祝你在其他项目中成功实现邮件功能!
