Spring Boot 邮件发送系统 - 从零到精通教程

📧 Spring Boot 邮件发送系统 - 从零到精通教程

本教程面向编程小白,详细讲解如何在 Spring Boot 项目中设计和实现一个完整的邮件发送系统。

学完本教程,你将能够:

  • 理解邮件发送的基本原理
  • 掌握 Spring Boot 邮件集成
  • 设计灵活的邮件模板系统
  • 实现异步邮件发送
  • 在任何项目中复制这套设计

📚 目录

  1. 邮件发送基础知识
  2. 本项目的设计架构
  3. 核心组件详解
  4. 实施步骤(分步骤实现)
  5. 使用示例
  6. 常见问题与解决方案
  7. 最佳实践

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 发送邮件需要什么?

必需信息

  1. SMTP 服务器地址 - 邮局的地址
  2. 端口号 - 邮局的窗口号
  3. 发件人邮箱 - 你的邮箱地址
  4. 授权密码 - 证明是你本人(不是登录密码!)
  5. 收件人邮箱 - 对方的邮箱地址
  6. 邮件内容 - 你要发的内容

⚠️ 重要:授权密码 ≠ 登录密码

  • 登录密码:登录邮箱用的密码
  • 授权密码:给第三方程序发邮件用的密码(更安全)

如何获取授权密码

  • 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 设计理念

我们的设计遵循以下原则:

  1. 📦 模块化:邮件功能独立为公共模块(common-public)
  2. 🎨 模板化:使用 HTML 模板,内容与代码分离
  3. ⚡ 异步化:不阻塞主业务流程
  4. 🔧 配置化:所有配置从配置文件读取
  5. 🚀 易扩展:添加新邮件类型只需加模板和枚举

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服务

解决方案

  1. 检查 password 是否是授权码(不是登录密码)
  2. 检查邮箱是否开启了 SMTP 服务
  3. Gmail 用户需要开启"不太安全的应用访问权限"或使用应用专用密码

Q2: 邮件发送很慢,影响用户体验

原因:使用了同步发送

解决方案

  1. 配置文件设置 async: true
  2. 启动类添加 @EnableAsync
  3. 确保 sendEmailAsync 方法是 public

Q3: 模板变量没有被替换

原因

  1. 变量名拼写错误
  2. 变量值为 null
  3. 模板语法错误

解决方案

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: 邮件进入垃圾箱

原因

  1. 发件人声誉低
  2. 缺少 SPF/DKIM 配置
  3. 邮件内容被识别为垃圾邮件

解决方案

  1. 使用企业邮箱(不要用免费邮箱发大量邮件)
  2. 配置域名的 SPF 和 DKIM 记录
  3. 避免使用垃圾邮件常用词(免费、中奖等)
  4. 添加退订链接

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 核心要点回顾

  1. 配置化 - 所有配置从配置文件读取
  2. 模板化 - 使用 HTML 模板,内容与代码分离
  3. 异步化 - 不阻塞主业务流程
  4. 模块化 - 邮件功能独立为公共模块
  5. 易扩展 - 添加新类型只需加模板和枚举

8.2 项目结构总结

复制代码
邮件系统架构
├── 配置层 (EmailConfig + application-email.yml)
├── 枚举层 (EmailTypeEnum)
├── 模型层 (EmailRequest)
├── 服务层 (EmailService + EmailServiceImpl)
└── 模板层 (HTML 模板文件)

8.3 关键技术点

  • Spring Boot Mail
  • JavaMail API
  • SMTP 协议
  • HTML 邮件模板
  • 异步处理 (@Async)
  • 配置管理 (@ConfigurationProperties)

8.4 下一步学习

  1. 邮件队列 - 使用 RabbitMQ/Kafka 管理邮件队列
  2. 定时任务 - 定时发送邮件(如每日报告)
  3. 邮件跟踪 - 记录邮件打开率、点击率
  4. 富文本编辑器 - 在管理后台编辑邮件模板
  5. 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
QQ 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 邮件系统的设计和实现!

如有问题,欢迎查阅代码或提问。祝你在其他项目中成功实现邮件功能!

相关推荐
脚踏实地的大梦想家8 小时前
【Docker】P2 Docker 命令:从Nginx部署到镜像分享的全流程指南
java·nginx·docker
Blossom.1188 小时前
把AI“编”进草垫:1KB决策树让宠物垫自己报「如厕记录」
java·人工智能·python·算法·决策树·机器学习·宠物
报错小能手8 小时前
计算机网络自顶向下方法25——运输层 TCP流量控制 连接管理 “四次挥手”的优化
服务器·网络·计算机网络
芒克芒克8 小时前
ssm框架之Spring(上)
java·后端·spring
消失的旧时光-19438 小时前
Android ble理解
java·kotlin
晨晖28 小时前
SpringBoot的yaml配置文件,热部署
java·spring boot·spring
鬼火儿8 小时前
1.2 redis7.0.4安装与配置开机自启动
java·后端
郭源潮18 小时前
《Muduo网络库:实现TcpServer类终章》
服务器·网络·c++·网络库
小马哥编程8 小时前
【软考架构】案例分析-对比MySQL查询缓存与Memcached
java·数据库·mysql·缓存·架构·memcached