做后端开发的,谁没被联调环境的邮件功能骚扰过。我在本地调试注册接口,点一下提交,验证码的邮件就真的发到测试邮箱里去了,一天下来几十封,收件箱全是垃圾,还打扰到共用测试邮箱的同事。后来我把项目的SMTP地址指到FakeSMTP 2.1.1上,邮件不真正投递出去,但内容照样看得到,测试流程一下子干净了。这篇文章就围绕FakeSMTP 2.1.1的使用展开,聊聊它是什么、怎么启动、怎么把项目切到本地、以及我踩过的那些坑。
FakeSMTP是一套纯Java实现的模拟SMTP服务器,核心价值就是在本地接收邮件、展示邮件内容,不往真实收件人那里发送。适合本地开发、单元测试、联调环境和CI回归,也适合刚入门的后端同学快速理解一次SMTP会话发生了什么。它不需要数据库,不需要配置,一条java命令就能跑起来,对小白和资深开发都很友好。
1. 为什么用FakeSMTP,而不是真的发信?
1.1 先搞清楚FakeSMTP到底是什么
FakeSMTP本质上是一个跑在本地端口上的SMTP服务器实现。它监听某个端口,比如2525,你的应用把邮件交给它之后,它会完整接收这封邮件,然后在界面上展示出来。关键点是,邮件的投递链条在它这里就终止了,它不会把邮件转发给MX记录对应的真实邮件服务器,也不会把信送到任何一个真实收件箱。
这种设计听起来很“不完整”,但正是这种不完整,让开发环境变得非常干净。试想一下,你每次跑测试都给真实邮箱发几封验证码,邮箱迟早被塞满,而且没人想每秒钟都收到垃圾邮件。如果邮件根本不发出去,你就不用担心泄露用户隐私、污染收件箱、触发邮件服务商的限流,同时又保留了检查邮件内容的完整能力。
从实现上讲,FakeSMTP就是一个可运行的Jar包,依赖Java运行环境。它完全按照SMTP协议和邮件格式来接收和解析邮件,所以你的应用层代码不需要做任何特殊适配,只要把SMTP的host和port改一下就行。
1.2 什么场景下会用到它
最典型的是用户注册、密码找回、验证码登录、订单通知、发票推送这一类功能。这些功能在开发阶段往往还接不到真正的邮件通道,或者邮件通道有配额限制,频繁测试很容易触发风控。把FakeSMTP挂上,就能用一个完全本地、零成本的方案把逻辑链路测通。
其次是单元测试和集成测试。比如你有一个发送欢迎邮件的服务,你想断言它确实调用了邮件发送器、确实拼了正确的HTML内容、确实给PDF附件设置了文件名。如果真发,测试结果会因为网络波动、邮箱容量、垃圾邮件拦截等原因变得不稳定。用FakeSMTP之后,测试可以本地运行、瞬间完成、结果确定,再也不用担心某个邮件服务商把测试邮件吞掉。
第三个场景是联调环境。多个服务之间互相调用,A服务触发邮件,B服务需要读取这封邮件的内容来继续流程。这时候在公共测试服务器上起一个FakeSMTP,所有服务都把SMTP指向它,大家就能在一个共享面板上看到全部邮件内容,排查问题非常方便。
1.3 用之前先看这些限制
FakeSMTP毕竟是个开发辅助工具,不是生产级的邮件服务。它只实现了SMTP这一条链路,不负责投递,不维护黑名单、退信、域名密钥等生产所需能力,所以不适合压测、不适合验证真实送达率、也不适合多机集群部署。如果你在线上环境误用了它,用户会收不到任何邮件,那是事故。
还需要注意,FakeSMTP默认的SMTP监听端口是25。在Linux和macOS上,1024以下的端口通常需要超级用户权限,直接用默认端口很容易启动失败。所以我一般建议换到1025或者2525这样的端口,省去权限问题和被占用的问题。
2. FakeSMTP 2.1.1的下载、启动与界面操作
2.1 版本选择与环境要求
先从版本说起。FakeSMTP 2.1.1是挺经典的一个release,界面简洁,功能稳定,网上能查到的资料也大多以这个版本为例。下载地址不用记,直接从官方GitHub Releases页面拿对应版本的Jar包就行,搜FakeSMTP就能找到。下载完是一个单文件Jar包,比如FakeSMTP-2.1.1.jar。
运行环境要求Java 8及以上,这一点2.1.1和更高版本基本一致。如果你机器上同时装了多个Java版本,建议先用java -version确认一下默认版本。我遇到过同事用Java 6去启动,直接报UnsupportedClassVersionError,换到Java 8就正常了。这类问题排查起来其实很快,看到版本号不匹配,就知道该切换JDK或者调整IDE的运行时环境了。
启动之前也可以先校验一下包完整性,用java -jar FakeSMTP-2.1.1.jar --help看一眼帮助信息。如果这段命令能正常打印参数说明,说明Jar包没损坏,Java环境也没问题,接下来就能正式跑了。
2.2 图形界面启动与邮件查看
图形界面是FakeSMTP最直观的用法。在命令行里进入Jar包所在目录,执行:
java -jar FakeSMTP-2.1.1.jar启动后界面会弹出,窗口分成左右两块区域。左边是收到的邮件列表,每一封显示发件人、收件人、主题和接收时间;右边选中一封邮件后,会展示邮件正文。正文支持HTML渲染,也支持切到“原始消息”模式看完整的RFC 822内容,这个功能对排查问题特别有用。
界面顶部有一个端口输入框,用来设置监听端口。我习惯填2525,因为开发环境里8080、3306、6379这些端口太容易起冲突,25又需要权限,2525基本不会撞车。填好端口后,点Start Server按钮,底部状态栏会变成running状态,托盘区也会出现一个小图标,说明服务已经在监听了。
这时候你去应用的配置里把SMTP端口改成2525,触发一次发送,再切回FakeSMTP窗口,就会看到新邮件进入列表。点开邮件,正文、发件人、收件人、时间都在,甚至附件也可以下载保存。这里有个小技巧:接受列表里右键某封邮件,可以单独保存为.eml文件,后续用任何邮件客户端都能打开分析。
2.3 命令行参数速查
图形界面适合人肉查看,但如果要在服务器上跑,或者在启动脚本里用,还是得靠命令行参数。FakeSMTP 2.1.1支持一些常用参数,我整理下来大概是这样的:
| 参数 | 作用 | 示例 |
|---|---|---|
-p, --port <port> | 指定监听端口 | -p 2525 |
-b, --background | 后台模式运行,不显示界面 | -b |
-s, --save-message | 把收到的邮件保存到磁盘 | -s |
-o, --output-dir <dir> | 配合-s指定保存目录 | -o ./mails |
-t, --start-server | 启动后立即开始监听 | -t |
-h, --help | 显示帮助信息 | -h |
我在服务器上最常用的一条命令是:
java -jar FakeSMTP-2.1.1.jar -p 2525 -b -s -o ./mails这条命令的意思是:监听2525端口,后台运行,不弹界面,收到的邮件全部保存到当前目录下的mails文件夹里。对于无图形界面的Linux服务器来说,这是最干净的使用方式。保存下来的邮件会以.eml或者类似格式落盘,文件名通常包含时间戳,方便后续用脚本分析。
如果你用的是图形界面方式,又希望邮件同时落盘,也可以在界面上勾选保存邮件的选项。两种方式并不冲突,一个偏交互,一个偏自动,按需选就好。
3. 把项目和测试环境切到FakeSMTP上
3.1 Spring Boot邮件配置改成本地SMTP
很多项目用Spring Boot发邮件,核心配置在application.yml里。原本的配置可能指向公司内部的邮件中继服务器,或者阿里云、腾讯云的邮件服务,现在要切到FakeSMTP,只需要把host和port换掉:
spring: mail: host: localhost port: 2525 username: dev@test.local password: whatever注意,FakeSMTP默认不校验用户名和密码,但你的JavaMailSender配置里如果写了账号,它也会正常接收。我在测试环境里习惯随便填一个具有业务含义的地址,比如verify@local.dev,这样在看邮件列表时能一眼认出是哪套流程发出来的。
改完配置后重启应用,触发邮件发送接口。如果之前的配置是连接不到远程邮件服务,现在切到本地,速度会快非常多。邮件内容在FakeSMTP面板里能看到,如果发现发送失败,优先看控制台异常里的连接信息,确认是不是端口配错或者服务没启动。
3.2 在JUnit测试里内嵌FakeSMTP
脱离界面、直接在测试代码里启动FakeSMTP,是最干净的自动化测试方案。FakeSMTP本身提供了可编程的入口,用起来也比较简单。我自己一般会在单元测试的@BeforeEach方法里启动一个实例,在@AfterEach里关闭它,不用依赖外部进程。
import com.github.fakeSMTP.SmtpServer; SmtpServer server = new SmtpServer(2525); server.start(); // 测试逻辑 server.stop();这里要注意端口管理。同一台机器上如果多个测试类同时跑,各自都指定固定的2525会让后启动的实例报端口占用。建议引入随机端口策略,比如:
int port = 25000 + new Random().nextInt(1000); SmtpServer server = new SmtpServer(port); server.start();然后在被测应用的配置里动态注入这个端口。如果项目用Spring Boot,配合@DynamicPropertySource把端口写进环境变量,就能让每个测试类拥有独立隔离的邮件环境,互不干扰。
测试断言也很直接。FakeSMTP提供的模型对象包含邮件的发件人、收件人列表、主题和正文。你可以在触发业务代码后,检查服务器收到的邮件总数是否增加,再提取最新一封邮件,断言正文中是否包含某个关键词。这样就把“邮件是否发送成功”从模糊的外部行为变成了可验证的确定性断言。
3.3 在CI/CD里作为固定服务运行
到了流水线阶段,我更倾向于让FakeSMTP作为一个常驻的固定服务存在,而不是每个测试都临时拉起一个进程。原因很简单,流水线跑得慢,反复启动JVM开销很大,多个测试模块共用同一个实例反而方便排查。
在CI脚本里,可以先执行后台启动命令:
java -jar FakeSMTP-2.1.1.jar -p 2500 -b -s -o "$WORKSPACE/build/test-mails"然后让测试阶段的所有服务都把SMTP配置指向localhost:2500。等到流水线跑完,如果邮件相关测试失败,直接去构建产物的test-mails目录里翻邮件原文,比看堆栈日志直观多了。
如果项目用的是Docker方式部署到测试环境,也可以把FakeSMTP放到一个简单的Docker容器里,固定暴露2525端口,把保存邮件的目录挂载到宿主机。这样整个团队都能在一个公共面板上看到邮件,适合需要多人共同排查问题的场景。唯一要注意的是容器内时间时区,最好显式挂载/etc/localtime,否则邮件的Received时间会偏好几个小时。
4. FakeSMTP典型问题与避坑指南
4.1 端口占用与切换方案
FakeSMTP最常遇到的报错就是端口被占。要么是之前的实例没退出,要么是其他服务占了同一个端口。报错信息一般会包含BindException或者Address already in use,看到这两个关键词,第一反应不是去看FakeSMTP的更多日志,而是直接确认端口。
Linux和macOS下用lsof -i :2525,Windows下用netstat -ano | findstr 2525,找到占用进程的PID,要么杀掉,要么换端口。我个人的习惯是测试环境固定用2525,一旦出现端口冲突,先查是不是上次的java进程没退干净,因为在IDE里点红色停止按钮并不一定能杀掉所有子进程。
如果确认没有残留进程,那就换端口。换了端口之后容易忽略的是,项目的配置文件和测试代码里可能同时写死了旧端口。FakeSMTP这边怎么改都可以,关键是应用那边也要同步,否则连接失败时会绕一大圈。
4.2 连接被拒与邮件看不到怎么排查
明明FakeSMTP启动了,应用也配置了localhost,但邮件就是发不出去,控制台报Connection refused。这种问题九成是端口没对上,或者FakeSMTP实际没有在监听。先别急着改代码,打开终端手动连一下:
telnet localhost 2525如果连接成功,Telnet会输出SMTP服务器的欢迎信息,类似220开头的行。如果连接失败,说明FakeSMTP进程确实没有起来,或者防火墙把端口挡了。有了这个快速验证方法,排查速度会快很多。
还有一种情况是应用里配置的是localhost,但应用跑在Docker容器里,容器里的localhost是容器自己,不是宿主机。这时候要改用host.docker.internal,或者在容器启动时加--network host才能访问到宿主机上的FakeSMTP。这个问题我踩过,当时排查了很久才想到是网络命名空间隔离的原因。
邮件列表里迟迟不出现新邮件,还有一个常见原因是代码里的SMTP配置被其他优先级更高的配置覆盖了。比如Spring Boot里同时存在application.yml和application-test.yml,测试环境激活了test配置,但你在默认配置里改了端口,实际生效的却是test配置里的旧值。建议发送数据后,到FakeSMTP的原始消息里看一眼Received头,确认是不是经由本机端口收到的,一锤定音。
4.3 中文乱码、附件和编码问题
邮件正文在GUI里显示乱码,这是被问得比较多的一个问题。很多情况下不是FakeSMTP的锅,而是邮件本身编码声明和实际内容不一致。JavaMail发送时,MimeMessage如果没显式指定Charset,中文字符可能按照平台默认编码打包,导致收到的邮件看起来全是问号。
解决办法是在发送邮件时显式配置编码,比如设置mail.smtp.allow8bitmime或者使用MimeUtility.encodeText处理主题。在FakeSMTP界面里,切到原始消息模式查看Content-Type头,看里面是否标注了charset=UTF-8。如果没有,回到应用的邮件发送逻辑里强制指定编码即可。
附件问题也类似。如果附件名包含中文,且没有做RFC 2231编码,收到的附件名会显示成乱码或者省略号。这种问题在真实邮箱里容易被邮箱客户端自动纠正,但在FakeSMTP这种严谨解析的本地工具里就会暴露出来。从另一面看,这反而是好事,因为它能帮你提前发现编码隐患。
4.4 我的常用组合:一条命令跑起来
最后分享一下我自己用下来的固定套路。在本地开发的机器上,我不用图形界面,而是维护一个启动脚本,内容就一行:
java -jar FakeSMTP-2.1.1.jar -p 2525 -b -s -o ~/dev-mail配合IntelliJ IDEA的启动配置或者一个简单的Shell脚本,每次开机跑一次就行。所有的邮件都会落到~/dev-mail目录,我会用VS Code打开这个目录,偶尔直接改后缀名用邮件客户端预览。等需要图形化查看时,再手动不带参数启动一个GUI实例,两条链路互不冲突。
对于自动化测试,我会确保每个测试类用随机端口启动SmtpServer,并把端口通过动态属性注入Spring上下文。对于CI流水线,则使用固定的2500端口常驻服务,配合--output-dir落盘方案,让失败案例的邮件直接沉淀成构建产物的一部分。这三层用法覆盖了我日常80%以上的邮件测试需求,如果你也经常跟邮件功能打交道,建议把这套组合收下,能少走不少弯路。