MailHog vs Mailpit:使用 Casdoor 测试验证和密码重置邮件
MailHog 和 Mailpit 都是用于开发的伪 SMTP 服务器:它们接受每条消息,保留它,并在 Web UI 中显示而不是投递它。两者都使用相同的默认端口,SMTP 为 1025,Web UI 和 API 为 8025,因此对于任何一个,SMTP 地址都是 smtp://127.0.0.1:1025,收件箱位于 http://127.0.0.1:8025。区别在于维护情况:MailHog 的最后一次发布 v1.0.1 发布于 2020 年 8 月,而 Mailpit 于 2026 年 10 月 31 日发布了 v1.33.0。对于新项目,请使用 Mailpit。Casdoor 通过普通的 SMTP 提供商向两者发送消息,因此您可以在没有真实邮件账户的情况下测试注册代码、密码重置邮件和魔法链接。
MailHog vs Mailpit 概览
| MailHog | Mailpit | |
|---|---|---|
| 最新发布版 | v1.0.1, 2020-08-11 | v1.33.0, 2026-10-31 |
| 默认分支上的最后一次提交 | 2022-08-02 | 2026-10-31 |
| License | MIT | MIT |
| Default SMTP port | 1025 | 1025 |
| Default web UI and API port | 8025 | 8025 |
| Docker image | mailhog/mailhog, amd64 only | axllent/mailpit, amd64, arm64 and 386 |
| HTTP API | v1 and v2: list, get, delete, search by from, to or containing | v1: list, get, delete, search with filters such as to: and subject:, latest message shortcut |
| Default storage | In memory | Temporary SQLite database, deleted on exit |
| Persistent storage | MongoDB or Maildir | SQLite file (MP_DATABASE) or rqlite |
| Message limit | None documented | Keeps the newest 500 by default (MP_MAX_MESSAGES) |
| HTML check, link check, spam check | No | Yes |
| Webhook on new mail | No | Yes (MP_WEBHOOK_URL) |
| SMTP TLS | Not offered | Optional STARTTLS or SSL/TLS |
| Failure injection | Jim | Chaos |
| Release a message to a real SMTP server | Yes | Yes |
| POP3 server | No | Yes, optional |
| Basic auth for the web UI | Yes | Yes |
Docker Hub counts show that many people still pull MailHog. That reflects old tutorials and CI files, not activity: an open issue on the MailHog repository titled "THIS PROJECT IS DEAD!" has a 2023 reply from the author saying he no longer has time to maintain it. Mailpit's README describes itself as inspired by MailHog and points to that same comment.
The image architecture matters more than it looks. On an Apple Silicon laptop or an ARM CI runner, mailhog/mailhog runs only under emulation, while axllent/mailpit has a native arm64 image.
使用 Docker 运行任一工具
MailHog:
docker run -d --name mailhog -p 1025:1025 -p 8025:8025 mailhog/mailhog
Mailpit:
docker run -d --name mailpit -p 1025:1025 -p 8025:8025 axllent/mailpit
Mailpit 将邮件保存在临时数据库中,容器停止时数据即消失。若要在重启后保留数据,请挂载卷并设置 MP_DATABASE:
docker run -d --name mailpit \
-v "$PWD/mailpit-data:/data" \
-e MP_DATABASE=/data/mailpit.db \
-p 1025:1025 -p 8025:8025 \
axllent/mailpit
如果不使用 Docker,在 macOS 上 brew install mailhog 和 brew install mailpit 均可正常工作,且 Mailpit 在其发布页面提供了适用于 Windows、Linux 和 macOS 的静态二进制文件。
在 GitHub Actions 中,将其作为服务容器与你的测试任务一起运行:
services:
mailpit:
image: axllent/mailpit
ports:
- 1025:1025
- 8025:8025
配置 Casdoor 以连接 MailHog 或 Mailpit
Casdoor 没有专门针对 MailHog 或 Mailpit 的提供商类型,也不需要。Default 电子邮件类型通过 SMTP 发送邮件,而这两个工具都是 SMTP 服务器。
- 作为全局管理员,在
admin或built-in组织中添加一个提供商。 - 将 Category 设置为
Email,将 Type 设置为Default。 - 将 Host 设置为该工具运行的地址,并将 Port 设置为
1025。 - Username 和 Password 留空。
- 点击 Test SMTP Connection,然后点击 Send Testing Email,并在端口 8025 上检查 Web UI。
包含截图的分步指南页面为 Test email with MailHog 和 Test email with Mailpit。
以下细节决定了这是否能一次性成功:
保持 SSL 模式为 Auto,或者将其设置为 Disable。 在 Auto 模式下,Casdoor 仅在端口 465 上使用隐式 TLS,并且仅当服务器提供时才使用 STARTTLS 进行升级。MailHog 和默认配置的 Mailpit 都不在端口 1025 上提供 STARTTLS,因此连接保持明文状态。不要选择 Enable:这会强制使用隐式 TLS,导致握手在与明文服务器通信时失败。完整的SSL 模式表描述了具体行为。
凭证留空。 仅当设置了 Username 时,Casdoor 才会登录到 SMTP 服务器。如果你填写了它,除非主机是 localhost,否则 Go 的 PLAIN 身份验证会拒绝在未加密的连接上发送密码,因此位于 192.168.x.x 的 MailHog 即使接受任何登录也会因身份验证错误而失败。Mailpit 默认根本不要求身份验证。
使用正确的主机。 只有当 Casdoor 和邮件捕获器在同一台机器上运行时,127.0.0.1 才有效。如果两者都在 Docker Compose 中运行,请使用服务名称(例如 mailpit)作为主机。
使用全局提供商。 普通组织拥有的提供商可能只能访问互联网地址,因此无法连接到 127.0.0.1 或私有 IP。admin 或 built-in 中的提供商不受此限制。请参阅 提供商可以访问哪些地址。
为链接设置 origin。 验证码可以立即工作。密码重置链接和魔术链接需要 Casdoor 知道其公共 URL,可以通过 conf/app.conf 中的 origin 或在回环主机上的请求来实现。否则,重置链接将从电子邮件中被丢弃,且魔术链接将失败。请参阅 模板。
最后,在 向应用添加提供商 中将提供商添加到你的应用,以便注册和密码重置真正使用它。
在自动化测试中读取验证码
Casdoor 的验证码是六位数字,默认模板显示 "Here is your code: " 后跟验证码。端到端测试会注册一个用户,要求 Casdoor 发送验证码,然后从邮件捕获器的 API 读取它。
对于 Mailpit,按收件人搜索并打开最新的匹配项。GET /api/v1/search 返回按最新顺序排列的匹配项,GET /api/v1/message/{ID} 返回解析后的 Text 和 HTML 正文:
import { request } from "@playwright/test";
const MAILPIT = process.env.MAILPIT_URL ?? "http://127.0.0.1:8025";
export async function latestCode(to: string): Promise<string> {
const api = await request.newContext({ baseURL: MAILPIT });
for (let i = 0; i < 20; i++) {
const res = await api.get("/api/v1/search", {
params: { query: `to:"${to}"`, limit: "1" },
});
const { messages } = await res.json();
if (messages.length > 0) {
const msg = await (await api.get(`/api/v1/message/${messages[0].ID}`)).json();
const match = (msg.Text || msg.HTML).match(/code: (\d{6})/);
if (match) return match[1];
}
await new Promise((r) => setTimeout(r, 500));
}
throw new Error(`no verification email for ${to}`);
}
如果每次只有一个测试发送邮件,GET /api/v1/message/latest 会跳过搜索步骤。在每个测试之前,使用不带请求体的 DELETE /api/v1/messages 来清空收件箱。
对于 MailHog,最接近的调用是 GET /api/v2/search?kind=to&query=<address>。消息在 items 中按最新优先顺序返回,正文位于 items[0].Content.Body:
const res = await api.get("/api/v2/search", {
params: { kind: "to", query: to, limit: "1" },
});
const { items } = await res.json();
const body: string = items[0]?.Content.Body ?? "";
Content.Body 是原始的 MIME 正文。Casdoor 发送的是 quoted-printable 编码,因此 = 会变成 =3D,长行会在末尾以 = 结束进行软换行。请先解码它,或者将代码保持在短模板的开头附近。使用 DELETE /api/v1/messages 清除 MailHog 中的邮件。
从 MailHog 迁移到 Mailpit
由于端口匹配,大多数迁移只需在 docker-compose.yml 中进行一行更改:将 mailhog/mailhog 替换为 axllent/mailpit。Casdoor 的提供商保持相同的主机和端口。发生变化的内容包括:
- 设置。 MailHog 读取
MH_*变量;Mailpit 读取MP_*。如果你使用了MH_STORAGE=maildir或 MongoDB,请改用MP_DATABASE。 - 测试辅助函数。 Mailpit 没有
/api/v2端点。将对/api/v2/messages和/api/v2/search的调用重写为/api/v1/messages和/api/v1/search,并读取messages[].ID以及Text或HTML,而不是items[].Content.Body。quoted-printable 解码不再需要。 - 搜索语法。
kind=to&query=a@example.com变为query=to:"a@example.com"。 - 故障注入。 Jim 在 Mailpit 中没有标志位;请使用 Chaos。
- 身份验证。 如果某个应用坚持通过明文 SMTP 登录,请使用
MP_SMTP_AUTH_ACCEPT_ANY=1和MP_SMTP_AUTH_ALLOW_INSECURE=1启动 Mailpit。对于 Casdoor,留空凭据更简单。 - 保留策略。 Mailpit 默认修剪至最新的 500 条消息。如果长时间运行的测试需要更多空间,请提高
MP_MAX_MESSAGES。
转向生产环境
除非手动释放消息,否则 MailHog 和 Mailpit 永远不会将邮件投递到真实收件箱,因此生产环境需要一个真实的提供商。在 Casdoor 中,你更换的是提供商,而不是模板。电子邮件模板、%s 代码占位符和 <reset-link> 块保持不变:
- 任何类型为
Default的 SMTP 服务器,详见 Email providers - SendGrid 和 Resend,通过其 API
- Brevo,通过其 SMTP 中继
- Azure Communication Services
- 你自己的 HTTP 端点,配合 Custom HTTP Email
常见的设置是在开发和 CI 环境中保留 Mailpit,在生产环境中使用真实提供商。每个环境将其应用指向不同的提供商,因此你测试的注册流程就是即将发布的流程。
参考资料
- MailHog 仓库和 README:https://github.com/mailhog/MailHog
- MailHog 配置和 API v1/v2 文档:https://github.com/mailhog/MailHog/tree/master/docs
- MailHog 维护评论:https://github.com/mailhog/MailHog/issues/442#issuecomment-1493415258
- Mailpit 仓库和 README:https://github.com/axllent/mailpit
- Mailpit 文档(Docker、存储、SMTP、webhook、搜索过滤器):https://mailpit.axllent.org/docs/
- Mailpit API v1 参考:https://mailpit.axllent.org/docs/api-v1/
- Casdoor 电子邮件提供商代码:https://github.com/casdoor/casdoor/tree/master/email