发送邮件
本教程共 48 篇 · 第 32 篇 · 更新于 2026-08-13 · 约 6 分钟阅读
本节目标:会用 JavaMailSender 发送纯文本、HTML 和带附件的邮件,会配置 SMTP 服务器,知道超时和授权码的坑。
邮件怎么发出去
应用不直接投递邮件,而是把邮件交给 SMTP 服务器,由它转发给收件人。所以配置的核心就三样:SMTP 服务器地址、端口、账号密码。
常见邮箱的服务器信息:
| 邮箱 | 服务器 | 端口 |
|---|---|---|
| 163 | smtp.163.com | 465(SSL) |
| smtp.qq.com | 465(SSL) | |
| Gmail | smtp.gmail.com | 587(TLS) |
端口不是死的:465 走 SSL 加密,587 走 STARTTLS 升级加密。配置里 mail.smtp.ssl.enable 和 mail.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,构建 SimpleMailMessage,send() 一发,完事。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 STARTTLS | TLS 没开 | 587 端口开 starttls.enable |
Mail server connection failed | SSL 配置与端口不配套 | 465 配 ssl.enable |
| 收到垃圾箱 | 内容含链接、无退订 | 规范签名,别当营销邮件 |
报错先对表排查,多数是授权码和 TLS 两个坑。
小结
发邮件三步:配 SMTP(host/port/授权码)、注入 JavaMailSender、构建消息发送。纯文本用 SimpleMailMessage,HTML 和附件用 MimeMessageHelper,内容用模板管理。记住授权码和超时两个坑,邮件功能就稳了。
至此,数据持久化部分全部结束。下一部分进入安全:Spring Security 入门。