ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

FakeSMTP 2.1.1实战:本地模拟SMTP服务器,彻底告别联调邮件骚扰

FakeSMTP 2.1.1实战:本地模拟SMTP服务器,彻底告别联调邮件骚扰 做后端开发的谁没被联调环境的邮件功能骚扰过。我在本地调试注册接口点一下提交验证码的邮件就真的发到测试邮箱里去了一天下来几十封收件箱全是垃圾还打扰到共用测试邮箱的同事。后来我把项目的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: devtest.local password: whatever注意FakeSMTP默认不校验用户名和密码但你的JavaMailSender配置里如果写了账号它也会正常接收。我在测试环境里习惯随便填一个具有业务含义的地址比如verifylocal.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 :2525Windows下用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头看里面是否标注了charsetUTF-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%以上的邮件测试需求如果你也经常跟邮件功能打交道建议把这套组合收下能少走不少弯路。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进