一、技术选型与依赖配置
1.1 maven 依赖配置(jdk 1.8 兼容)
<?xml version="1.0" encoding="utf-8"?>
<project xmlns="http://maven.apache.org/pom/4.0.0"
xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"
xsi:schemalocation="http://maven.apache.org/pom/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelversion>4.0.0</modelversion>
<groupid>com.xxx</groupid>
<artifactid>springboot-email-demo</artifactid>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>springboot-email-demo</name>
<description>spring boot 邮件发送完整解决方案</description>
<parent>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-parent</artifactid>
<version>2.7.18</version>
<relativepath/>
</parent>
<properties>
<project.build.sourceencoding>utf-8</project.build.sourceencoding>
<project.reporting.outputencoding>utf-8</project.reporting.outputencoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- spring boot mail starter(jakarta mail 1.6.2,jdk 1.8 兼容) -->
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-mail</artifactid>
</dependency>
<!-- spring boot web(可选,用于提供接口) -->
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-web</artifactid>
</dependency>
<!-- lombok(简化代码,可选) -->
<dependency>
<groupid>org.projectlombok</groupid>
<artifactid>lombok</artifactid>
<optional>true</optional>
</dependency>
<!-- spring boot test -->
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-test</artifactid>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-maven-plugin</artifactid>
</plugin>
</plugins>
</build>
</project>
重要说明:
- spring boot 2.7.18 内置 jakarta mail 1.6.2,完美兼容 jdk 1.8
- 如果使用 spring boot 3.x,需要 jdk 17+,且邮件api包名从
javax.mail变为jakarta.mail
1.2 application.yml 配置
spring:
mail:
# smtp服务器配置
host: smtp.qq.com
port: 587
username: your_email@qq.com
password: your_smtp_authorization_code # 注意:是授权码,不是邮箱密码
# 编码配置(根治中文乱码)
default-encoding: utf-8
# 协议配置
protocol: smtp
# tls加密配置
properties:
mail:
smtp:
auth: true
starttls:
enable: true
required: true
ssl:
protocols: tlsv1.2
connectiontimeout: 10000
timeout: 10000
writetimeout: 10000
# 服务器端口
server:
port: 8080
常用smtp服务器配置参考:
| 邮箱服务商 | smtp地址 | 端口 | 认证方式 |
|---|---|---|---|
| qq邮箱 | smtp.qq.com | 587 (tls) / 465 (ssl) | 授权码 |
| 163邮箱 | smtp.163.com | 465 (ssl) / 25 | 授权码 |
| gmail | smtp.gmail.com | 587 (tls) | 应用专用密码 |
| outlook | smtp.office365.com | 587 (tls) | 邮箱密码或应用密码 |
二、核心工具类实现
2.1 邮件发送工具类
package com.xxx.email.util;
import lombok.extern.slf4j.slf4j;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.mail.simplemailmessage;
import org.springframework.mail.javamail.javamailsender;
import org.springframework.mail.javamail.mimemessagehelper;
import org.springframework.stereotype.component;
import javax.activation.datasource;
import javax.activation.filedatasource;
import javax.mail.messagingexception;
import javax.mail.internet.mimemessage;
import javax.mail.internet.mimeutility;
import java.io.file;
import java.io.unsupportedencodingexception;
import java.util.arraylist;
import java.util.list;
/**
* 邮件发送工具类
* <p>
* 支持功能:
* 1. 纯文本邮件发送
* 2. html邮件发送
* 3. 带附件邮件发送(支持中文附件名)
* 4. 内嵌图片邮件发送(图片显示在正文中)
* 5. 混合场景:同时包含内嵌图片和附件
* </p>
*
* @author auth
* @since 2026-02-13
*/
@slf4j
@component
public class emailutil {
@autowired
private javamailsender mailsender;
/**
* 发送纯文本邮件
*
* @param from 发件人邮箱
* @param to 收件人邮箱(支持多个,用逗号分隔)
* @param subject 邮件主题
* @param content 邮件内容
*/
public void sendsimpleemail(string from, string to, string subject, string content) {
try {
simplemailmessage message = new simplemailmessage();
message.setfrom(from);
message.setto(to.split(","));
message.setsubject(subject);
message.settext(content);
mailsender.send(message);
log.info("纯文本邮件发送成功!收件人:{}, 主题:{}", to, subject);
} catch (exception e) {
log.error("纯文本邮件发送失败!收件人:{}, 主题:{}, 错误信息:{}", to, subject, e.getmessage(), e);
throw new runtimeexception("邮件发送失败:" + e.getmessage(), e);
}
}
/**
* 发送纯文本带附件邮件
* @param from 发件人邮箱
* @param to 收件人邮箱(支持多个,用逗号分隔)
* @param subject 邮件主题
* @param content 邮件主题
* @param attachments 附件文件列表
*/
public void sendsimpleemailattachments(string from, string to, string subject, string content, list<file> attachments) {
try {
mimemessage message = mailsender.createmimemessage();
// 第二个参数true表示创建multipart消息
mimemessagehelper helper = new mimemessagehelper(message, true, "utf-8");
helper.setfrom(from);
helper.setto(to.split(","));
helper.setsubject(subject);
helper.settext(content);
// 添加附件(自动处理中文文件名)
if (attachments != null && !attachments.isempty()) {
for (file file : attachments) {
if (file != null && file.exists()) {
helper.addattachment(encodefilename(file.getname()), file);
log.debug("添加附件:{}", file.getname());
} else {
log.warn("附件文件不存在,跳过:{}", file != null ? file.getname() : "null");
}
}
}
mailsender.send(message);
log.info("纯文本带附件邮件发送成功!收件人:{}, 主题:{}, 附件数量:{}", to, subject,
attachments != null ? attachments.size() : 0);
} catch (exception e) {
log.error("纯文本带附件邮件发送失败!收件人:{}, 主题:{}, 错误信息:{}", to, subject, e.getmessage(), e);
throw new runtimeexception("邮件发送失败:" + e.getmessage(), e);
}
}
/**
* 发送html邮件
*
* @param from 发件人邮箱
* @param to 收件人邮箱
* @param subject 邮件主题
* @param htmlcontent html格式的邮件内容
*/
public void sendhtmlemail(string from, string to, string subject, string htmlcontent) {
try {
mimemessage message = mailsender.createmimemessage();
mimemessagehelper helper = new mimemessagehelper(message, true, "utf-8");
helper.setfrom(from);
helper.setto(to.split(","));
helper.setsubject(encodetext(subject));
helper.settext(htmlcontent, true);
mailsender.send(message);
log.info("html邮件发送成功!收件人:{}, 主题:{}", to, subject);
} catch (exception e) {
log.error("html邮件发送失败!收件人:{}, 主题:{}, 错误信息:{}", to, subject, e.getmessage(), e);
throw new runtimeexception("邮件发送失败:" + e.getmessage(), e);
}
}
/**
* 发送带附件的邮件(支持中文附件名)
*
* @param from 发件人邮箱
* @param to 收件人邮箱
* @param subject 邮件主题
* @param htmlcontent html格式的邮件内容
* @param attachments 附件文件列表
*/
public void sendemailwithattachments(string from, string to, string subject,
string htmlcontent, list<file> attachments) {
try {
mimemessage message = mailsender.createmimemessage();
// 第二个参数true表示创建multipart消息
mimemessagehelper helper = new mimemessagehelper(message, true, "utf-8");
helper.setfrom(from);
helper.setto(to.split(","));
helper.setsubject(encodetext(subject));
helper.settext(htmlcontent, true);
// 添加附件(自动处理中文文件名)
if (attachments != null && !attachments.isempty()) {
for (file file : attachments) {
if (file != null && file.exists()) {
helper.addattachment(encodefilename(file.getname()), file);
log.debug("添加附件:{}", file.getname());
} else {
log.warn("附件文件不存在,跳过:{}", file != null ? file.getname() : "null");
}
}
}
mailsender.send(message);
log.info("带附件邮件发送成功!收件人:{}, 主题:{}, 附件数量:{}", to, subject,
attachments != null ? attachments.size() : 0);
} catch (exception e) {
log.error("带附件邮件发送失败!收件人:{}, 主题:{}, 错误信息:{}", to, subject, e.getmessage(), e);
throw new runtimeexception("邮件发送失败:" + e.getmessage(), e);
}
}
/**
* 发送带内嵌图片的邮件(图片显示在正文中)
*
* @param from 发件人邮箱
* @param to 收件人邮箱
* @param subject 邮件主题
* @param htmlcontent html格式的邮件内容(通过cid:xxx引用图片)
* @param inlineimages 内嵌图片列表
*/
public void sendemailwithinlineimages(string from, string to, string subject,
string htmlcontent, list<inlineimage> inlineimages) {
try {
mimemessage message = mailsender.createmimemessage();
// 必须使用true创建multipart消息
mimemessagehelper helper = new mimemessagehelper(message, true, "utf-8");
helper.setfrom(from);
helper.setto(to.split(","));
helper.setsubject(encodetext(subject));
helper.settext(htmlcontent, true);
// 添加内嵌图片
if (inlineimages != null && !inlineimages.isempty()) {
for (inlineimage inlineimage : inlineimages) {
if (inlineimage != null && inlineimage.getfile() != null && inlineimage.getfile().exists()) {
datasource datasource = new filedatasource(inlineimage.getfile());
helper.addinline(inlineimage.getcontentid(), datasource);
log.debug("添加内嵌图片:cid={}, 文件名:{}",
inlineimage.getcontentid(), inlineimage.getfile().getname());
} else {
log.warn("内嵌图片文件不存在,跳过:cid={}",
inlineimage != null ? inlineimage.getcontentid() : "null");
}
}
}
mailsender.send(message);
log.info("内嵌图片邮件发送成功!收件人:{}, 主题:{}, 图片数量:{}", to, subject,
inlineimages != null ? inlineimages.size() : 0);
} catch (exception e) {
log.error("内嵌图片邮件发送失败!收件人:{}, 主题:{}, 错误信息:{}", to, subject, e.getmessage(), e);
throw new runtimeexception("邮件发送失败:" + e.getmessage(), e);
}
}
/**
* 发送复杂邮件(同时包含内嵌图片和附件)
*
* @param from 发件人邮箱
* @param to 收件人邮箱
* @param subject 邮件主题
* @param htmlcontent html格式的邮件内容
* @param inlineimages 内嵌图片列表
* @param attachments 附件文件列表
*/
public void sendcomplexemail(string from, string to, string subject,
string htmlcontent, list<inlineimage> inlineimages,
list<file> attachments) {
try {
mimemessage message = mailsender.createmimemessage();
mimemessagehelper helper = new mimemessagehelper(message, true, "utf-8");
helper.setfrom(from);
helper.setto(to.split(","));
helper.setsubject(encodetext(subject));
helper.settext(htmlcontent, true);
// 添加内嵌图片
if (inlineimages != null && !inlineimages.isempty()) {
for (inlineimage inlineimage : inlineimages) {
if (inlineimage != null && inlineimage.getfile() != null && inlineimage.getfile().exists()) {
datasource datasource = new filedatasource(inlineimage.getfile());
helper.addinline(inlineimage.getcontentid(), datasource);
log.debug("添加内嵌图片:cid={}, 文件名:{}",
inlineimage.getcontentid(), inlineimage.getfile().getname());
}
}
}
// 添加附件
if (attachments != null && !attachments.isempty()) {
for (file file : attachments) {
if (file != null && file.exists()) {
helper.addattachment(encodefilename(file.getname()), file);
log.debug("添加附件:{}", file.getname());
}
}
}
mailsender.send(message);
log.info("复杂邮件发送成功!收件人:{}, 主题:{}, 图片数量:{}, 附件数量:{}",
to, subject, inlineimages != null ? inlineimages.size() : 0,
attachments != null ? attachments.size() : 0);
} catch (exception e) {
log.error("复杂邮件发送失败!收件人:{}, 主题:{}, 错误信息:{}", to, subject, e.getmessage(), e);
throw new runtimeexception("邮件发送失败:" + e.getmessage(), e);
}
}
/**
* 编码文本(处理中文乱码)
* 使用rfc 2047标准编码
*
* @param text 原始文本
* @return 编码后的文本
*/
private string encodetext(string text) {
if (text == null || text.isempty()) {
return text;
}
try {
// 使用base64编码(b编码),utf-8字符集
return mimeutility.encodetext(text, "utf-8", "b");
} catch (unsupportedencodingexception e) {
log.warn("文本编码失败,返回原始文本:{}", text, e);
return text;
}
}
/**
* 编码文件名(处理中文附件名乱码)
*
* @param filename 原始文件名
* @return 编码后的文件名
*/
private string encodefilename(string filename) {
if (filename == null || filename.isempty()) {
return filename;
}
try {
// 使用base64编码(b编码),utf-8字符集
return mimeutility.encodetext(filename, "utf-8", "b");
} catch (unsupportedencodingexception e) {
log.warn("文件名编码失败,返回原始文件名:{}", filename, e);
return filename;
}
}
/**
* 内嵌图片包装类
*/
public static class inlineimage {
/**
* 图片文件
*/
private file file;
/**
* content-id(用于html中通过cid:引用)
*/
private string contentid;
public inlineimage(file file, string contentid) {
this.file = file;
this.contentid = contentid;
}
public file getfile() {
return file;
}
public void setfile(file file) {
this.file = file;
}
public string getcontentid() {
return contentid;
}
public void setcontentid(string contentid) {
this.contentid = contentid;
}
}
}
工具类设计亮点:
- 完整的日志记录:成功/失败均有详细日志,便于排查问题
- 异常处理:所有异常统一捕获并记录,重新抛出为运行时异常
- 中文乱码根治:通过
mimeutility.encodetext()统一处理主题和附件名 - 参数校验:对文件存在性、null值进行校验
- 支持多种场景:纯文本、html、附件、内嵌图片、混合场景
2.2 spring boot 启动类
package com.xxx.email;
import org.springframework.boot.springapplication;
import org.springframework.boot.autoconfigure.springbootapplication;
@springbootapplication
public class emailapplication {
public static void main(string[] args) {
springapplication.run(emailapplication.class, args);
system.out.println("========================================");
system.out.println("邮件服务启动成功!");
system.out.println("========================================");
}
}
三、使用示例
3.1 测试控制器
package com.xxx.email.controller;
import com.example.email.util.emailutil;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.beans.factory.annotation.value;
import org.springframework.web.bind.annotation.getmapping;
import org.springframework.web.bind.annotation.requestmapping;
import org.springframework.web.bind.annotation.restcontroller;
import java.io.file;
import java.util.arraylist;
import java.util.list;
/**
* 邮件发送测试控制器
*/
@restcontroller
@requestmapping("/email")
public class emailcontroller {
@autowired
private emailutil emailutil;
@value("${spring.mail.username}")
private string fromemail;
/**
* 测试1:发送纯文本邮件
*/
@getmapping("/sendsimple")
public string sendsimpleemail() {
try {
string to = "recipient@example.com";
string subject = "测试纯文本邮件";
string content = "你好!\n\n这是一封测试邮件。\n\n祝好!";
emailutil.sendsimpleemail(fromemail, to, subject, content);
return "纯文本邮件发送成功!";
} catch (exception e) {
return "发送失败:" + e.getmessage();
}
}
/**
* 测试2:发送html邮件
*/
@getmapping("/sendhtml")
public string sendhtmlemail() {
try {
string to = "recipient@example.com";
string subject = "测试html邮件";
string htmlcontent =
"<html>" +
"<head>" +
" <style>" +
" body { font-family: arial, sans-serif; padding: 20px; }" +
" .header { background: #4caf50; color: white; padding: 20px; text-align: center; }" +
" .content { margin-top: 20px; line-height: 1.6; }" +
" </style>" +
"</head>" +
"<body>" +
" <div class='header'>" +
" <h1>欢迎使用我们的服务</h1>" +
" </div>" +
" <div class='content'>" +
" <p>亲爱的用户:</p>" +
" <p>这是一封<b>html格式</b>的测试邮件。</p>" +
" <p>感谢您的使用!</p>" +
" </div>" +
"</body>" +
"</html>";
emailutil.sendhtmlemail(fromemail, to, subject, htmlcontent);
return "html邮件发送成功!";
} catch (exception e) {
return "发送失败:" + e.getmessage();
}
}
/**
* 测试3:发送带附件的邮件(支持中文附件名)
*/
@getmapping("/sendwithattachments")
public string sendemailwithattachments() {
try {
string to = "recipient@example.com";
string subject = "测试带附件的邮件";
string htmlcontent =
"<html>" +
"<body>" +
" <h2>附件测试邮件</h2>" +
" <p>你好!</p>" +
" <p>这是一封带附件的测试邮件,附件包含中文名称。</p>" +
" <p>请查收附件。</p>" +
"</body>" +
"</html>";
// 准备附件(包含中文文件名)
list<file> attachments = new arraylist<>();
attachments.add(new file("d:/test/中文文档.docx"));
attachments.add(new file("d:/test/数据报表.xlsx"));
emailutil.sendemailwithattachments(fromemail, to, subject, htmlcontent, attachments);
return "带附件邮件发送成功!";
} catch (exception e) {
return "发送失败:" + e.getmessage();
}
}
/**
* 测试4:发送带内嵌图片的邮件(图片显示在正文中)
*/
@getmapping("/sendwithinlineimages")
public string sendemailwithinlineimages() {
try {
string to = "recipient@example.com";
string subject = "测试内嵌图片邮件";
// html正文:通过cid:引用图片
string htmlcontent =
"<html>" +
"<head>" +
" <style>" +
" body { font-family: arial, sans-serif; padding: 20px; }" +
" .header { background: #4caf50; color: white; padding: 20px; text-align: center; }" +
" .content { margin-top: 20px; line-height: 1.6; }" +
" img { max-width: 100%; height: auto; display: block; margin: 20px auto; }" +
" </style>" +
"</head>" +
"<body>" +
" <div class='header'>" +
" <h1>内嵌图片测试</h1>" +
" </div>" +
" <div class='content'>" +
" <p>亲爱的用户:</p>" +
" <p>这是一封带内嵌图片的测试邮件。</p>" +
" <p>以下是公司logo:</p>" +
" <!-- 通过cid:logo引用内嵌图片 -->" +
" <img src='cid:logo' alt='logo'>" +
" <p>以下是产品示意图:</p>" +
" <!-- 通过cid:product引用内嵌图片 -->" +
" <img src='cid:product' alt='产品示意图'>" +
" <p>图片直接显示在邮件正文中,无需下载。</p>" +
" </div>" +
"</body>" +
"</html>";
// 准备内嵌图片(file + content-id)
list<emailutil.inlineimage> inlineimages = new arraylist<>();
inlineimages.add(new emailutil.inlineimage(new file("d:/test/logo.png"), "logo"));
inlineimages.add(new emailutil.inlineimage(new file("d:/test/product.png"), "product"));
emailutil.sendemailwithinlineimages(fromemail, to, subject, htmlcontent, inlineimages);
return "内嵌图片邮件发送成功!";
} catch (exception e) {
return "发送失败:" + e.getmessage();
}
}
/**
* 测试5:发送复杂邮件(同时包含内嵌图片和附件)
*/
@getmapping("/sendcomplex")
public string sendcomplexemail() {
try {
string to = "recipient@example.com";
string subject = "测试复杂邮件(内嵌图片 + 附件)";
// html正文
string htmlcontent =
"<html>" +
"<head>" +
" <style>" +
" body { font-family: arial, sans-serif; padding: 20px; }" +
" .header { background: #4caf50; color: white; padding: 20px; text-align: center; }" +
" .content { margin-top: 20px; line-height: 1.6; }" +
" img { max-width: 100%; height: auto; display: block; margin: 20px auto; }" +
" </style>" +
"</head>" +
"<body>" +
" <div class='header'>" +
" <h1>复杂邮件测试</h1>" +
" </div>" +
" <div class='content'>" +
" <p>亲爱的用户:</p>" +
" <p>这封邮件同时包含:</p>" +
" <ul>" +
" <li>内嵌图片(直接显示在正文中)</li>" +
" <li>附件(需要下载查看)</li>" +
" </ul>" +
" <p>以下是内嵌图片:</p>" +
" <img src='cid:logo' alt='logo'>" +
" <p>请查收附件文件。</p>" +
" </div>" +
"</body>" +
"</html>";
// 内嵌图片
list<emailutil.inlineimage> inlineimages = new arraylist<>();
inlineimages.add(new emailutil.inlineimage(new file("d:/test/logo.png"), "logo"));
// 附件
list<file> attachments = new arraylist<>();
attachments.add(new file("d:/test/中文文档.docx"));
attachments.add(new file("d:/test/数据报表.xlsx"));
emailutil.sendcomplexemail(fromemail, to, subject, htmlcontent, inlineimages, attachments);
return "复杂邮件发送成功!";
} catch (exception e) {
return "发送失败:" + e.getmessage();
}
}
}
3.2 单元测试
package com.xxx.email;
import com.example.email.util.emailutil;
import org.junit.jupiter.api.test;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.beans.factory.annotation.value;
import org.springframework.boot.test.context.springboottest;
import java.io.file;
import java.util.arraylist;
import java.util.list;
@springboottest
class emailapplicationtests {
@autowired
private emailutil emailutil;
@value("${spring.mail.username}")
private string fromemail;
/**
* 测试纯文本邮件
*/
@test
void testsendsimpleemail() {
string to = "recipient@example.com";
string subject = "【测试】纯文本邮件";
string content = "你好!\n\n这是一封测试邮件。\n\n祝好!";
emailutil.sendsimpleemail(fromemail, to, subject, content);
}
/**
* 测试html邮件
*/
@test
void testsendhtmlemail() {
string to = "recipient@example.com";
string subject = "【测试】html邮件";
string htmlcontent =
"<html>" +
"<body>" +
" <h2>html邮件测试</h2>" +
" <p>这是一封<b>html格式</b>的测试邮件。</p>" +
"</body>" +
"</html>";
emailutil.sendhtmlemail(fromemail, to, subject, htmlcontent);
}
/**
* 测试带附件的邮件
*/
@test
void testsendemailwithattachments() {
string to = "recipient@example.com";
string subject = "【测试】带附件的邮件";
string htmlcontent =
"<html>" +
"<body>" +
" <h2>附件测试</h2>" +
" <p>请查收附件。</p>" +
"</body>" +
"</html>";
list<file> attachments = new arraylist<>();
attachments.add(new file("d:/test/测试文档.txt"));
emailutil.sendemailwithattachments(fromemail, to, subject, htmlcontent, attachments);
}
/**
* 测试内嵌图片邮件
*/
@test
void testsendemailwithinlineimages() {
string to = "recipient@example.com";
string subject = "【测试】内嵌图片邮件";
string htmlcontent =
"<html>" +
"<body>" +
" <h2>内嵌图片测试</h2>" +
" <img src='cid:test' alt='测试图片'>" +
"</body>" +
"</html>";
list<emailutil.inlineimage> inlineimages = new arraylist<>();
inlineimages.add(new emailutil.inlineimage(new file("d:/test/test.png"), "test"));
emailutil.sendemailwithinlineimages(fromemail, to, subject, htmlcontent, inlineimages);
}
}
四、关键技术与原理
4.1 中文乱码的根源与解决方案
乱码产生的原因
邮件协议(smtp)基于文本传输,只支持7位ascii字符。当传输中文等非ascii字符时,必须进行编码。
解决方案
| 乱码类型 | 根本原因 | 解决方案 |
|---|---|---|
| 附件文件名乱码 | 文件名未按mime标准编码(rfc 2047) | 使用 mimeutility.encodetext(filename, "utf-8", "b") |
| 邮件正文乱码 | 正文未指定charset或使用了非utf-8编码 | 在 mimemessagehelper 构造函数中指定 utf-8 |
| 主题乱码 | 主题未进行mime编码 | 使用 mimeutility.encodetext(subject, "utf-8", "b") |
编码方式说明
mimeutility.encodetext(text, charset, encoding)
- charset: 必须使用
"utf-8",确保能处理所有unicode字符 - encoding:
"b"- base64编码(推荐,兼容性最好)"q"- quoted-printable编码(适用于ascii字符较多的场景)
生产环境建议始终使用 “b”(base64)编码,因为各邮件客户端对base64的支持最稳定。
4.2 内嵌图片的技术原理
multipart 模式选择
// 错误:使用默认的mixed模式(附件模式)
mimemultipart multipart = new mimemultipart();
// 正确:使用related模式(内嵌资源模式)
mimemultipart multipart = new mimemultipart("related");
| multipart类型 | 用途 | 说明 |
|---|---|---|
mixed | 混合模式 | 用于独立的附件(如word、pdf) |
related | 相关模式 | 用于内嵌资源(图片、css、字体) |
alternative | 替代模式 | 用于同一内容的多版本(纯文本 + html) |
content-id(cid)设置规则
// 1. 在java代码中设置content-id
helper.addinline("logo", datasource);
// 2. 在html中引用(注意:必须加上cid:前缀)
<img src="cid:logo" alt="logo">
重要规则:
- content-id 建议使用字母、数字、下划线,避免使用中文或特殊字符
- html引用时使用
cid:前缀 - 同一邮件中的cid必须唯一,否则会显示错误或无法加载
4.3 spring boot mail 自动配置原理
spring boot 通过 org.springframework.boot.autoconfigure.mail.mailautoconfiguration 自动配置 javamailsender:
- 自动检测配置:从
application.yml中读取spring.mail.*配置 - 创建 session:根据配置创建
javamailsender - 注入 bean:将
javamailsender注入到spring容器中
开发者只需:
- 配置
spring.mail.*属性 - 注入
javamailsender或javamailsenderimpl - 使用
mimemessagehelper简化邮件构建
五、常见问题与解决方案
5.1 认证失败
问题:
authenticationfailedexception: 535 error: authentication failed
原因:
- 使用了邮箱密码而非授权码
- 授权码已过期或被重置
- smtp服务未开启
解决方案:
- 登录邮箱设置,开启smtp服务
- 生成新的授权码
- 在
application.yml中使用授权码
获取授权码示例(qq邮箱):
- 登录qq邮箱 → 设置 → 账户
- 找到"pop3/imap/smtp/exchange/carddav/caldav服务"
- 开启"imap/smtp服务"
- 按提示发送短信获取授权码
5.2 连接超时
问题:
mailconnectexception: couldn't connect to host, port: smtp.qq.com
原因:
- 端口配置错误
- 网络问题
- 防火墙拦截
解决方案:
- 检查端口配置:
- tls加密:端口 587
- ssl加密:端口 465
- 检查网络连接
- 配置超时时间:
spring:
mail:
properties:
mail:
smtp:
connectiontimeout: 10000
timeout: 10000
writetimeout: 10000
5.3 附件文件名乱码
问题:附件文件名显示为乱码
解决方案:
使用 mimeutility.encodetext() 编码文件名:
private string encodefilename(string filename) {
try {
return mimeutility.encodetext(filename, "utf-8", "b");
} catch (unsupportedencodingexception e) {
return filename;
}
}
5.4 内嵌图片显示为红叉
问题:图片显示为红叉或无法加载
可能原因及解决方案:
| 可能原因 | 解决方案 |
|---|---|
| cid不匹配 | 检查html中的 cid: 与java中的 addinline() 的参数是否一致 |
| 图片文件不存在 | 使用 file.exists() 验证文件是否存在 |
| 图片格式不支持 | 转换为png、jpg或gif |
| 图片过大 | 压缩图片(建议单张不超过500kb) |
5.5 outlook 中内嵌图片显示为附件
问题:内嵌图片在outlook中显示为附件而非正文
解决方案:
- 确保设置了正确的multipart模式
- 设置文件名:
helper.addinline("logo", datasource);
5.6 邮件发送慢
问题:邮件发送耗时较长
解决方案:
- 使用异步发送(推荐):
@service
public class asyncemailservice {
@autowired
private emailutil emailutil;
@async("emailtaskexecutor")
public void sendemailasync(string from, string to, string subject, string content) {
emailutil.sendhtmlemail(from, to, subject, content);
}
}
- 配置线程池:
@configuration
@enableasync
public class asyncconfig {
@bean("emailtaskexecutor")
public taskexecutor emailtaskexecutor() {
threadpooltaskexecutor executor = new threadpooltaskexecutor();
executor.setcorepoolsize(5);
executor.setmaxpoolsize(10);
executor.setqueuecapacity(100);
executor.setthreadnameprefix("email-async-");
executor.initialize();
return executor;
}
}
六、最佳实践与优化建议
6.1 邮件发送建议
- 控制附件大小:单个附件不超过10mb,总大小不超过25mb(大多数邮箱的限制)
- 图片优化:
- 使用png或jpg格式
- 控制单张图片大小在500kb以内
- 使用合适的分辨率(72-150 dpi)
- html规范:
- 使用内联css样式
- 避免使用javascript
- 使用table布局提升兼容性
- 多客户端测试:在gmail、outlook、qq邮箱等主流客户端中测试显示效果
6.2 异常处理建议
- 统一异常处理:
@controlleradvice
public class globalexceptionhandler {
@exceptionhandler(exception.class)
public responseentity<string> handleexception(exception e) {
log.error("系统异常", e);
return responseentity.status(500).body("系统异常:" + e.getmessage());
}
}
- 重试机制:对于网络异常,可以添加重试逻辑
6.3 安全建议
- 敏感信息加密:使用
application-{profile}.yml区分环境,敏感信息加密存储 - 限制发送频率:防止被滥用
- 添加dkim签名:提升邮件信誉度(可选)
6.4 监控建议
- 记录发送日志:记录发送状态、耗时、失败原因
- 监控指标:
- 发送成功率
- 平均发送耗时
- 失败原因分布
- 告警机制:发送失败率超过阈值时触发告警
6.5 生产环境配置示例
spring:
profiles:
active: prod
mail:
host: ${smtp_host:smtp.qq.com}
port: ${smtp_port:587}
username: ${smtp_username}
password: ${smtp_password}
default-encoding: utf-8
properties:
mail:
smtp:
auth: true
starttls:
enable: true
connectiontimeout: 15000
timeout: 15000
writetimeout: 15000
# 日志配置
logging:
level:
org.springframework.mail: debug
file:
name: logs/email-service.log
七、总结
本文提供了一个完整的、生产级可用的 spring boot 邮件发送解决方案,核心要点:
- 技术选型:spring boot 2.7.18 + jakarta mail 1.6.2,兼容 jdk 1.8
- 核心工具类:
emailutil提供了完整的邮件发送功能,包括:- 纯文本邮件
- html邮件
- 带附件邮件(支持中文附件名)
- 内嵌图片邮件
- 复杂邮件(内嵌图片 + 附件)
- 中文乱码根治:使用
mimeutility.encodetext()统一处理 - 内嵌图片原理:通过 content-id 建立 html 与图片的映射关系
- 异常处理:完整的日志记录和异常处理机制
- 最佳实践:异步发送、监控告警、安全配置等
以上就是springboot实现邮件发送的完整解决方案(附件发送、内嵌图片与中文乱码处理)的详细内容,更多关于springboot邮件发送的资料请关注代码网其它相关文章!
发表评论