SpringBoot 发送邮件

文章目录

    • [1 快速 Starter](#1 快速 Starter)
    • [2 结构](#2 结构)
    • [3 发送复杂邮件](#3 发送复杂邮件)
    • [4 延迟创建邮件消息](#4 延迟创建邮件消息)
    • [5 收件人 / 抄送 / 密送](#5 收件人 / 抄送 / 密送)
    • [6 使用模版发送邮件](#6 使用模版发送邮件)
    • [7 扩展工具类封装](#7 扩展工具类封装)
    • [8 总结](#8 总结)

在项目的维护过程中,我们通常会在应用中加入短信或者邮件预警功能,比如当应用出现异常宕机时应该及时地将预警信息发送给运维或者开发人员,本文将介绍如何在 SpringBoot 中发送邮件。

在 SpringBoot 中发送邮件使用的是 Spring 提供的 org.springframework.mail.javamail.JavaMailSender,其提供了许多简单易用的方法,可发送简单的邮件、HTML 格式的邮件、带附件的邮件,还可以创建邮件模板。

1 快速 Starter

引入依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

配置邮箱服务,以 QQ 邮箱为例:

yaml 复制代码
spring:
  application:
    name: mail
  mail:
    host: smtp.qq.com # SMTP 服务器主机
    port: 465 # SMTP 服务器端口
    username: xxxxxxxx@qq.com # SMTP 服务器的登录用户
    password: xxxxxxxxxxx # SMTP 服务器的登录密码,不是用户密码,在对应邮箱服务的安全中心中查看
    protocol: smtps # SMTP 服务器使用的协议
    properties:
      mail: # 其他 JavaMail 会话属性,传递给 JavaMail 底层实现
        smtp:
          auth: true # 启用 SMTP 身份认证
          starttls:
            enable: true #升级 TLS 握手
          ssl:
            enable: true # 开启 ssl
# mail中的属性是给 JavaMail 使用的,不是SpringBoot
  # https://javaee.github.io/javamail/docs/api/com/sun/mail/smtp/package-summary.html

发送简单邮件的示例代码

java 复制代码
@Service
public class MailService {
    @Resource
    private JavaMailSender mailSender;
    @Resource
    private MailProperties mailProperties;
    public void sendSimpleMail(String to, String subject, String content) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom(mailProperties.getUsername());
        message.setTo(to);
        message.setSubject(subject);
        message.setText(content);
        mailSender.send(message);
    }
}

测试:

java 复制代码
@SpringBootTest
class Mail2ApplicationTests {
    @Resource
    MailService mailService;
    @Test
    void contextLoads() {
        mailService.sendSimpleMail("xxxxxxx@qq.com","测试主题","这只是一个测试");
    }
}

测试结果:

2 结构

Spring Framework 中的 org.springframework.mail 包提供统一的邮件发送相关接口。

主要有两类:一类用于定义邮件消息结构、另一类用于发送邮件消息。

1_邮件消息接口

快速开始中使用的是 SimpleMailMessage(简单邮件消息实现)。

所有邮件消息类型统一实现了接口 MailMessage:

java 复制代码
public interface MailMessage {
	// 发送人
	void setFrom(String from) throws MailParseException;
	// 收件地址: 收件人点击'回复'按钮时,邮件应当发给哪个地址
	// 默认回复给 from
	void setReplyTo(String replyTo) throws MailParseException;
	// 收件人
	void setTo(String to) throws MailParseException;
	// 支持多个收件人
	void setTo(String... to) throws MailParseException;
	// 抄送人
	void setCc(String cc) throws MailParseException;
	// 支持多个抄送人
	void setCc(String... cc) throws MailParseException;
	// 密送人
	void setBcc(String bcc) throws MailParseException;
	// 支持多个密送人
	void setBcc(String... bcc) throws MailParseException;
	// 发送邮箱的时间戳
	void setSentDate(Date sentDate) throws MailParseException;
	// 主题,邮箱标题
	void setSubject(String subject) throws MailParseException;
	// 发送数据
	void setText(String text) throws MailParseException;
}

MailMessage 默认提供了两个实现类,其中 MimeMailMessage 是复杂邮件消息类型:

2_邮件发送者

邮件发送接口主要有两个:

  • MailSender 是最基础接口,可发送简单文本邮件 (SimpleMailMessage)。
  • JavaMailSender 继承自 MailSender,支持 MIME 消息(如 HTML、附件、内嵌资源),典型使用方式结合 MimeMessageHelper 构造复杂邮件结构。
  • JavaMailSender 还支持 MimeMessagePreparator 回调机制,延迟创建复杂的 MimeMessage 。

MailSender 接口:

java 复制代码
public interface MailSender {

	default void send(SimpleMailMessage simpleMessage) throws MailException {
		send(new SimpleMailMessage[] {simpleMessage});
	}
	// 可以发送多条消息
	void send(SimpleMailMessage... simpleMessages) throws MailException;

}

JavaMailSender 接口:

java 复制代码
public interface JavaMailSender extends MailSender {

	MimeMessage createMimeMessage();

	MimeMessage createMimeMessage(InputStream contentStream) throws MailException;

	default void send(MimeMessage mimeMessage) throws MailException {
		send(new MimeMessage[] {mimeMessage});
	}
	// 支持接收多个消息参数
	void send(MimeMessage... mimeMessages) throws MailException;
    .......
}

MimeMessagePreparator 接口:

java 复制代码
@FunctionalInterface
public interface MimeMessagePreparator {
	void prepare(MimeMessage mimeMessage) throws Exception;
}

3 发送复杂邮件

发送复杂邮件(如带 HTML、附件、内嵌图片、多个收件人等),需要使用 JavaMailSender 的 MimeMessage + MimeMessageHelper 方式。

发送 HTML正文 + 附件 + 内嵌图片 示例:

java 复制代码
public void sendComplexMail(String to) throws MessagingException {
    MimeMessage message = mailSender.createMimeMessage();
    // 第二个参数 multipart=true 代表该邮件是 multipart 结构
    MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
    helper.setFrom(mailProperties.getUsername());
    helper.setTo(to);
    helper.setSubject("复杂邮件示例(含HTML、附件、内嵌图片)");
    // 构造 HTML 内容,注意内嵌图片使用 cid:xxx 标识
    String html = "<html><body>" +
            "<h2 style='color:blue;'>您好,这是HTML格式邮件</h2>" +
            "<p>请查看附件,也可以看到下面的图片:</p>" +
            "<img src='cid:logoImage'/>" +
            "</body></html>";
    helper.setText(html, true); // 第二个参数为 true 表示内容为 HTML
    // 添加附件(FileSystemResource 可以从磁盘读取)
    ClassPathResource file = new ClassPathResource("test.pdf");
    helper.addAttachment(Objects.requireNonNull(file.getFilename()), file);
    // 添加内嵌图片(resourceName 要与上面的 cid:xxx 匹配)
    ClassPathResource image = new ClassPathResource("logo.png");
    helper.addInline("logoImage", image);
    mailSender.send(message);
}

在 HTML 邮件中嵌入静态资源的过程和传入附件类似,唯一的区别在于需要标识资源的 cid

最终结果如下:

cid(Content-ID)是 MIME 协议中用于标识邮件中某个内嵌资源(通常是图片)的标记,用于在 HTML 中引用该资源。

4 延迟创建邮件消息

之前提到过 JavaMailSender 有一种更灵活发送邮件的方式 ------ 使用 MimeMessagePreparator 接口。

即:允许在"需要时"才构造并配置 MimeMessage,而不是立即创建并设置,从而实现"延迟构造"和"回调式"的邮件发送逻辑。

其实就是利用了函数式接口的特性:

java 复制代码
MimeMessagePreparator preparator = mimeMessage -> {
	MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
	helper.setFrom(from);
	helper.setTo("XXXXXXX@qq.com");
	helper.setSubject("测试邮件");
	helper.setText("<html><body><h1>Hello</h1></body></html>", true);
	helper.addInline("logoImage", new ClassPathResource("logo.png"));
};
mailSender.send(preparator); // 由Spring在内部延迟构建并发送

如果不理解,可以对比一下之前发邮件的方式:

java 复制代码
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setTo("xxxxx@qq.com");
helper.setSubject("普通邮件");
helper.setText("Hello Email");
mailSender.send(message);
........

5 收件人 / 抄送 / 密送

先来理解一下这三个概念:

字段名 关键词 说明 作用 是否所有人能看到
收件人 To 正式接收邮件的人 邮件是"发给他们"的
抄送 Cc (Carbon Copy) 次要接收人 通知类收件人,"告知他们一份"
密送 Bcc (Blind Carbon Copy) 隐藏接收人 悄悄通知某人

假设下面这一场景:

你是某公司主管,发送一封工作邮件给员工和老板。

有这几个角色,张三(你想让他执行某一任务,并且知晓李四收到了邮件)、李四(你想让他知情张三收到了邮件)、老板(你不想让张三李四知道老板也收到了邮件,同时让老板知晓张三、李四收到了邮件)。

那么就可以进行如下设置:

java 复制代码
helper.setTo("zhangsan1@example.com", "zhangsan2@example.com");// 主收件人:张三
helper.setCc("lisi@example.com");// 抄送给李四
helper.setBcc("boss@example.com");// 密送给老板

6 使用模版发送邮件

这里使用 Thymeleaf 模板引擎来生成邮件内容并发送 HTML 邮件,Freemarker 类似。

首先引入依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

添加如下配置文件:

yaml 复制代码
spring:
  thymeleaf:
    prefix: classpath:/templates/
    suffix: .html
    mode: HTML
    encoding: UTF-8

编写 HTML 邮件模版:

html 复制代码
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>激活邮件</title>
</head>
<body>
    <h2>您好,<span th:text="${username}">先生/女士</span></h2>
    <p>感谢注册,请点击下方链接激活您的账号:</p>
    <p>
        <a th:href="${activationUrl}" target="_blank">点击此处激活</a>
    </p>
    <hr>
    <p>如果链接无法点击,请将以下地址复制到浏览器打开:</p>
    <p th:text="${activationUrl}">https://example.com/activate</p>
</body>
</html>

编写邮箱服务类:

java 复制代码
import jakarta.annotation.Resource;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

@Service
public class ThymeleafMailService {
    @Resource
    private JavaMailSender mailSender;
    @Resource
    private TemplateEngine templateEngine;
    @Value("${spring.mail.username}")
    private String from;
    public void sendActivationEmail(String toEmail, String username, String activationUrl) throws MessagingException {
        // 构建 MimeMessage
        MimeMessage message = mailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
        helper.setFrom(from);
        helper.setTo(toEmail);
        helper.setSubject("账号激活邮件");
        // 1. 构建模板上下文
        Context context = new Context();
        context.setVariable("username", username);
        context.setVariable("activationUrl", activationUrl);
        // 2. 渲染模板
        String htmlContent = templateEngine.process("email-template", context);
        // 3. 设置 HTML 正文
        helper.setText(htmlContent, true); // 第二参数 true 表示 HTML
        // 4. 发送邮件
        mailSender.send(message);
    }
}

测试结果:

7 扩展工具类封装

通用的 Spring Boot 邮件发送工具类封装,支持:

  • 发送文本邮件
  • 发送 HTML 邮件
  • 添加附件
  • 添加内嵌图片
  • 多个收件人、抄送、密送

不需要的参数传 null 即可:

java 复制代码
@Slf4j
@Component
public class MailUtil {
    @Resource
    private JavaMailSender mailSender;
    @Value("${spring.mail.username}")
    private String from;
    /**
     * 发送复杂邮件(支持 HTML、附件、内嵌图片)
     *
     * @param subject 邮件主题
     * @param content 邮件内容(HTML 可控)
     * @param to 收件人(多个)
     * @param cc 抄送人(可选)
     * @param bcc 密送人(可选)
     * @param attachments 附件路径 Map<显示名, 文件路径>,可为空
     * @param inlineImages 内嵌图片 Map<cid标识, 文件路径>,可为空
     * @param isHtml 是否HTML格式
     */
    public void sendMail(String subject,
                         String content,
                         List<String> to,
                         List<String> cc,
                         List<String> bcc,
                         Map<String, String> attachments,
                         Map<String, String> inlineImages,
                         boolean isHtml) {
        try {
            MimeMessage message = mailSender.createMimeMessage();
            MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
            // 发件人从配置读取
            helper.setFrom(from);
            helper.setTo(to.toArray(new String[0]));
            if (cc != null && !cc.isEmpty()) {
                helper.setCc(cc.toArray(new String[0]));
            }
            if (bcc != null && !bcc.isEmpty()) {
                helper.setBcc(bcc.toArray(new String[0]));
            }
            helper.setSubject(subject);
            helper.setText(content, isHtml);
            // 添加附件
            if (attachments != null) {
                for (Map.Entry<String, String> entry : attachments.entrySet()) {
                    File file = new File(entry.getValue());
                    if (file.exists()) {
                        helper.addAttachment(entry.getKey(), file);
                    } else {
                        log.warn("附件文件不存在: {}", file.getAbsolutePath());
                    }
                }
            }
            // 添加内嵌图片
            if (inlineImages != null) {
                for (Map.Entry<String, String> entry : inlineImages.entrySet()) {
                    File file = new File(entry.getValue());
                    if (file.exists()) {
                        helper.addInline(entry.getKey(), new FileSystemResource(file));
                    } else {
                        log.warn("内嵌图片文件不存在: {}", file.getAbsolutePath());
                    }
                }
            }
            mailSender.send(message);
            log.info("邮件发送成功 -> {}", to);
        } catch (MessagingException e) {
            log.error("发送邮件失败:{}", e.getMessage(), e);
        }
    }
}

还是弄的不太方便,你可以自己尝试实现一个,哈哈 。

8 总结

邮件功能虽非核心业务,但在注册验证、通知提醒、工单系统等系统中却非常关键。

良好的邮件架构不仅能提升用户体验,也能让系统具备更强的扩展性和稳定性。

添加邮件日志记录、异步发送等能力,可继续在此基础上构建更完善的邮件服务模块。