首页 / Spring Boot 入门教程 / 发送邮件

Spring Boot 入门教程

发送邮件

本教程共 48 篇 · 第 32 篇 · 更新于 2026-08-13 · 约 6 分钟阅读

Spring BootJavaMailSender发送邮件SMTPMimeMessage邮件附件HTML邮件

本节目标:会用 JavaMailSender 发送纯文本、HTML 和带附件的邮件,会配置 SMTP 服务器,知道超时和授权码的坑。

邮件怎么发出去

应用不直接投递邮件,而是把邮件交给 SMTP 服务器,由它转发给收件人。所以配置的核心就三样:SMTP 服务器地址、端口、账号密码。

常见邮箱的服务器信息:

邮箱服务器端口
163smtp.163.com465(SSL)
QQsmtp.qq.com465(SSL)
Gmailsmtp.gmail.com587(TLS)

端口不是死的:465 走 SSL 加密,587 走 STARTTLS 升级加密。配置里 mail.smtp.ssl.enablemail.smtp.starttls.enable 要跟端口配套。

Spring 用 JavaMailSender 接口封装了整个发送过程。你只管构建邮件内容,连接服务器、协议交互都交给它。Spring Boot 提供自动配置,引入 starter 就能注入使用。

引入依赖与配置

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

配置 SMTP 服务器信息(以 163 邮箱为例,Gmail/QQ 同理):

spring:
  mail:
    host: smtp.163.com        # SMTP 服务器地址
    port: 465                 # 465 走 SSL,587 走 TLS
    username: your_name@163.com
    password: 你的授权码       # 不是登录密码!
    properties:
      "[mail.smtp.auth]": true
      "[mail.smtp.ssl.enable]": true
Warning

邮箱密码处填的是授权码(在邮箱设置里单独开启 SMTP 服务后生成),不是网页登录密码。用登录密码连 SMTP,服务器直接拒绝。这也是初学者卡得最久的一步。

Gmail 还需要在账号设置里开启「两步验证」并生成应用专用密码,光有账号密码发不出去。

设置超时,防止邮件服务器无响应时线程被挂死:

spring:
  mail:
    properties:
      "[mail.smtp.connectiontimeout]": 5000
      "[mail.smtp.timeout]": 3000
      "[mail.smtp.writetimeout]": 5000

官方文档特意提醒:这些超时默认是无限大,强烈建议改掉。

第一封邮件:纯文本

SimpleMailMessage 是最简单的邮件对象:

import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;

@Service
public class MailService {

    private final JavaMailSender mailSender;

    public MailService(JavaMailSender mailSender) {
        this.mailSender = mailSender;
    }

    public void sendSimpleMail(String to, String subject, String text) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setTo(to);
        message.setSubject(subject);
        message.setText(text);
        mailSender.send(message);
    }
}

注入 JavaMailSender,构建 SimpleMailMessagesend() 一发,完事。setTo 支持传多个地址,群发一封搞定。

发件人和抄送、密送也在这条链上:

SimpleMailMessage message = new SimpleMailMessage();
message.setFrom("no-reply@example.com");   // 发件人,不设默认用配置的 username
message.setTo("user@example.com");
message.setCc("manager@example.com");       // 抄送
message.setBcc("archive@example.com");      // 密送
message.setSubject("欢迎注册");
message.setText("你的账号已创建。");
mailSender.send(message);

密送和抄送的区别:收件人能看到抄送名单,看不到密送名单。运营通知类邮件,密送留底很常用。

HTML 邮件:MimeMessageHelper

业务邮件基本都要排版。HTML 邮件要用 MimeMessage——它支持 MIME 格式,能承载富文本和附件。MimeMessageHelper 是它的便捷构建器:

import jakarta.mail.internet.MimeMessage;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.mail.javamail.JavaMailSender;

@Service
public class MailService {

    private final JavaMailSender mailSender;

    public MailService(JavaMailSender mailSender) {
        this.mailSender = mailSender;
    }

    public void sendHtmlMail(String to, String subject, String html) throws MessagingException {
        MimeMessage message = mailSender.createMimeMessage();
        // 第二个参数 true 表示这是 multipart 邮件,允许 HTML 和附件
        MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
        helper.setTo(to);
        helper.setSubject(subject);
        // 第二个参数 true 表示内容是 HTML,不是纯文本
        helper.setText(html, true);
        mailSender.send(message);
    }
}

调用示例:

String html = """
        <h1>欢迎注册</h1>
        <p>你的验证码是:<b>482913</b>,10 分钟内有效。</p>
        """;
mailService.sendHtmlMail("user@example.com", "注册验证码", html);

Java 15+ 的文本块写 HTML 模板很顺手。注意 helper.setText(html, true) 的第二个 true——漏了它,HTML 会被当纯文本原样展示。

带附件

附件和 HTML 可以同时发。附件可以是 classpath 里的资源,也可以是磁盘文件:

import org.springframework.core.io.ClassPathResource;
import org.springframework.mail.javamail.MimeMessageHelper;

public void sendMailWithAttachment(String to, String subject, String html) throws MessagingException {
    MimeMessage message = mailSender.createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
    helper.setTo(to);
    helper.setSubject(subject);
    helper.setText(html, true);

    // 参数一:收件人看到的文件名;参数二:资源
    helper.addAttachment("使用说明.pdf", new ClassPathResource("docs/manual.pdf"));
    helper.addAttachment("宣传图.png", new ClassPathResource("images/banner.png"));

    mailSender.send(message);
}
Tip

addAttachment 第一个参数是展示名,可以跟实际文件名不一样。附件路径写 classpath 资源时,文件要放在 src/main/resources/ 对应的目录下。

附件里还有一种特殊形式:内嵌图片。它不挂在附件列表里,而是嵌在 HTML 正文中,用 cid: 引用:

helper.setText("<html><body>欢迎 <img src='cid:logo'/></body></html>", true);
helper.addInline("logo", new ClassPathResource("images/logo.png"));

收件人打开邮件直接看到图片,不用下载附件。区别一句话:addAttachment 是附件,addInline 是正文里的图。

大附件注意:单封邮件体积受服务器限制(通常 25MB 上下),超大文件走对象存储链接,别硬塞邮件。

模板邮件:内容别硬编码

邮件内容拼在代码里,改一次文案要动代码。简单做法是模板文件 + 占位符替换:

src/main/resources/templates/welcome-mail.html:

<h1>欢迎,{name}!</h1>
<p>你的账号 {account} 已激活。</p>

读取并替换占位符:

import org.springframework.core.io.ClassPathResource;

public String renderTemplate(String templatePath, Map<String, String> params) throws IOException {
    String content = new String(
            new ClassPathResource(templatePath).getInputStream().readAllBytes(),
            StandardCharsets.UTF_8);
    for (Map.Entry<String, String> entry : params.entrySet()) {
        content = content.replace("{" + entry.getKey() + "}", entry.getValue());
    }
    return content;
}

复杂模板可以上 Thymeleaf 渲染(第 24 章讲过),把模型数据直接套进模板。占位符替换适合简单场景,两个都够用。

邮件要异步发

发邮件是网络 IO,慢的话几百毫秒。用户在注册接口里干等邮件发送,体验很差。正确做法是丢到后台线程:

import org.springframework.scheduling.annotation.Async;

@Service
public class MailService {

    @Async   // 异步执行,调用方立即返回
    public void sendSimpleMail(String to, String subject, String text) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setTo(to);
        message.setSubject(subject);
        message.setText(text);
        mailSender.send(message);
    }
}

在配置类上加 @EnableAsync 开启异步支持(同样别加在主类上)。两种做法(加在主类或独立配置类)都可行,本书建议放独立配置类。之后调用 sendSimpleMail 立即返回,邮件在后台线程发送。

Note

异步的代价是发信失败不能当场感知。重试、失败补偿这些可靠性设计,等业务量上来了再考虑,入门先保证接口不卡。

测试环境:别真发

写测试时不要真的连 SMTP。两个选择:

  • 配一个假的邮件服务器(比如 GreenMail),本地起服务收邮件。
  • JavaMailSender 用 Mockito mock 掉,只验证调用参数。
import org.springframework.test.context.bean.override.mockito.MockitoBean;

@MockitoBean
private JavaMailSender mailSender;

@Test
void sendMail_shouldCallSender() {
    mailService.sendSimpleMail("a@b.com", "标题", "内容");
    verify(mailSender).send(any(SimpleMailMessage.class));
}

测试的重点是「我们的代码正确调用了发送逻辑」,而不是真的把邮件发出去。

邮件主题和内容也讲点规范:主题带上业务标识(比如「【订单】您的商品已发货」),收件人一眼能认,也不容易进垃圾箱。

真发的时候还有一个坑:mailSender.send() 失败抛 MailSendException,且可能是部分收件人成功、部分失败。批量群发时,要么容忍部分失败,要么对失败地址做补偿重发,别假定「要么全成要么全败」。

常见报错速查

报错原因处理
535 Authentication Failed密码填错或用了登录密码换授权码/应用专用密码
Connection timed out服务器地址或端口不通核对 host/port,检查网络
530 Must issue STARTTLSTLS 没开587 端口开 starttls.enable
Mail server connection failedSSL 配置与端口不配套465 配 ssl.enable
收到垃圾箱内容含链接、无退订规范签名,别当营销邮件

报错先对表排查,多数是授权码和 TLS 两个坑。

小结

发邮件三步:配 SMTP(host/port/授权码)、注入 JavaMailSender、构建消息发送。纯文本用 SimpleMailMessage,HTML 和附件用 MimeMessageHelper,内容用模板管理。记住授权码和超时两个坑,邮件功能就稳了。

至此,数据持久化部分全部结束。下一部分进入安全:Spring Security 入门。