CI非常擅长运行代码,但它在一件产品可能依赖的事情上表现糟糕:邮件。如果你的测试涵盖注册、密码重置、魔法链接或入站通知,你仍需要一种方法来在CI中查看邮件,而无需(1)登录共享邮箱、(2)抓取HTML或(3)将敏感内容转储到作业日志中。
好消息是你可以像处理任何其他测试产物一样处理邮件:按运行分配、确定性等待、对结构化字段进行断言,并将结果附加到CI输出中。
为什么”登录邮箱”会破坏CI
传统邮箱(Gmail、Outlook、IMAP)是围绕长期身份的人性化用户体验。CI需要相反的:短期、隔离、机器可读的状态。
当你在管道中依赖真实邮箱时的常见失败模式:
- 并行运行冲突:多个作业读取同一收件箱,选择错误消息,或删除彼此的状态。
- 认证和MFA不属于CI:OAuth刷新令牌过期,MFA质询阻止运行,权限过于宽泛。
- 可观测性是反向的:你无法轻松存储”我们收到了什么邮件?“作为构建产物。
- 安全性变差:团队开始将OTP和魔法链接粘贴到日志中进行调试。
如果你希望邮件可测试,它们必须像事件一样可消费。
💡 停止在CI管道中与邮件斗争
跳过邮箱登录的麻烦和并行运行冲突。Mailhook创建一次性收件箱,表现得像合适的测试资源 - 通过API按运行分配并作为结构化JSON消费。
在CI中查看邮件的三种实用方法
有很多邮件测试技巧,但在2026年,大多数团队会根据他们要验证的内容收敛到其中一种。
选项1:本地SMTP捕获(快速,适用于开发和某些CI)
如果你的目标是”我们的应用是否尝试发送邮件?“那么本地SMTP捕获工具是最简单的方法。
工作原理:
- 你的应用通过SMTP发送到CI网络内的本地服务器。
- 服务器存储消息并通过小型API/UI暴露它们。
优点:
- 非常快
- 无外部依赖
- 适合单元测试和集成测试
缺点:
- 不是真正端到端的实际入站路由测试
- 无法捕获生产邮件基础设施中发生的可投递性和路由问题
这是开发者工作流程的良好默认选择,但对于必须像生产环境一样运行的E2E流程,它往往不够用。
选项2:一次性收件箱API(最适合E2E和验证流程)
对于需要接收真实入站邮件并提取OTP或链接的端到端测试,可编程的一次性收件箱是最可靠的模式:
- 通过API为每次运行或每次尝试创建新收件箱。
- 在测试流程中使用其邮箱地址。
- 确定性地等待到达。
- 将消息检索为结构化JSON。
- 仅断言并提取你需要的产物。
这完全避免了邮箱登录。CI只是获取数据。
Mailhook专为此模型构建:通过API创建一次性收件箱、以JSON形式传递邮件、webhook通知、轮询检索、共享域、自定义域支持、签名负载和批处理。有关规范集成详细信息,请使用已发布的合约:Mailhook llms.txt。
选项3:提供商特定的事件流(有用,但通常不是收件箱形状)
一些邮件发送提供商暴露传递事件(已接受、已退回、已传递),有时还有消息预览。当你关心可投递性遥测时,这可能很有帮助,但对于”点击此魔法链接”风格的测试,这通常还不够。
在大多数组织中,团队仍需要收件箱形状的抽象(地址、消息、检索)来进行确定性验证流程。
快速决策表
| CI中的目标 | 最佳方法 | 你实际断言的内容 |
|---|---|---|
| “我们生成了正确的邮件内容” | 本地SMTP捕获 | 主题/发件人/收件人、文本正文、模板 |
| “用户可以完成注册验证” | 一次性收件箱API | 在超时内到达、提取的OTP或链接 |
| “可投递性和提供商行为” | 提供商事件 + 针对性测试 | 传递事件、退回、投诉信号 |
如果你的管道需要以用户体验方式”查看邮件”,一次性收件箱获胜。
确定性模式:每次运行一个收件箱,等待,解析JSON
关键是停止以”搜索邮箱”的方式思考。在CI中,你需要明确的合约:
- 隔离:每次运行都有自己的收件箱。
- 传递:你的测试工具有可靠的方式等待消息(webhook优先,轮询后备)。
- 消费:你对稳定的JSON字段进行断言,而不是渲染的HTML。

💡 在几分钟内让确定性邮件模式工作
当你可以立即开始测试时,为什么要重建收件箱隔离和JSON解析?Mailhook为你提供开箱即用的webhooks、轮询和结构化邮件数据 - 正是此模式需要的。
步骤1:分配一个收件箱
在测试开始时创建收件箱并保留两者:
- 邮箱地址(你的应用使用的)
- 收件箱句柄(你的测试工具用来获取消息的)
使用Mailhook,确切的请求和响应字段记录在llms.txt中。在代码审查中将该文件视为真实来源。
步骤2:触发邮件
运行测试中的操作,例如:
- 注册
- 密码重置
- 邮件登录
为了确定性,将你控制的关联值(例如,运行ID)传递到你的应用程序中,使其最终出现在邮件中。常见位置:
- 验证链接内的查询参数
- 你的邮件程序添加的自定义头
- 邮件文本中的引用令牌
步骤3:等待到达(在CI内轮询是最简单的)
当你已经有公共端点时,Webhooks很棒。在CI中,如果你实现超时和退避,轮询通常更容易且仍然确定性。
提供商无关的轮询伪代码:
async function waitForEmail({ inboxId, matcher, timeoutMs }) {
const started = Date.now();
let delay = 250;
while (Date.now() - started < timeoutMs) {
const messages = await listMessages(inboxId); // 提供商API调用
const match = messages.find(matcher);
if (match) return match;
await sleep(delay);
delay = Math.min(delay * 1.5, 2000);
}
throw new Error(`在收件箱 ${inboxId} 中等待邮件超时`);
}
重要部分是匹配器:使其足够窄,以便并行运行不能匹配彼此的邮件。
步骤4:对JSON进行断言,仅提取你需要的内容
一旦你检索到结构化的JSON表示,优先选择如下断言:
- 发件人域正确
- 收件人匹配预期地址
- 主题包含预期意图
- 文本正文包含OTP模式
避免脆弱行为:
- 抓取HTML标记
- 依赖精确格式或CSS
- 当你只需要OTP时让LLM”阅读整个邮件”
示例提取(OTP):
import re
def extract_otp(text: str) -> str:
m = re.search(r"\b(\d{6})\b", text)
if not m:
raise ValueError("未找到OTP")
return m.group(1)
步骤5:将邮件JSON作为CI产物附加(而不是打印它)
为了在不泄露秘密的情况下调试失败:
- 将接收到的消息JSON写入文件
- 将其作为产物上传
- 在适当的地方编辑或省略敏感字段
在GitHub Actions中,产物上传很直接:
- name: 保存入站邮件JSON
run: node scripts/save-email.js > email.json
- name: 上传邮件产物
uses: actions/upload-artifact@v4
with:
name: inbound-email
path: email.json
这是”手动邮箱调试”和”CI级邮件可见性”之间最大的实际差异。你得到一个与运行绑定的持久产物。
CI中的Webhooks:何时有意义
Webhooks在以下情况下是理想的:
- 你运行具有稳定公共URL的长期测试环境
- 你需要最低延迟
- 你已经操作小型接收器服务
如果你确实使用webhooks,将webhook请求视为不受信任的输入通道:
- 验证签名
- 强制时间戳容忍度
- 去重传递(会发生重试)
Mailhook支持签名负载,这是此处正确的原语。再次,确切的签名和头格式在Mailhook llms.txt中描述。
“查看邮件”的CI可靠性检查清单
这些是消除大多数不稳定性的护栏:
每次运行使用唯一收件箱(或每次尝试)
如果你共享收件箱,你最终会匹配错误的消息。隔离比调试更便宜。
优先选择确定性等待而不是固定睡眠
用”等到匹配的邮件到达或超时”替换sleep(10)。这提高了速度和稳定性。
在正确级别去重
由于重试,邮件管道可以合法地传递重复项。你的工具应该通过以下方式保持健壮:
- 选择最新的匹配消息
- 跟踪已见过的消息ID
- 使消费幂等
将邮件内容视为恶意输入
即使在测试环境中,邮件也可能包含:
- 意外的HTML
- 跟踪链接
- 附件
如果涉及代理,约束它可以对邮件做什么。提取最小产物(OTP或URL),验证它,然后继续。
保持秘密不在日志中
如果你必须记录某些内容,记录标识符:
- 运行ID
- 收件箱ID
- 消息ID
然后将完整消息内容存储在具有适当访问控制的产物中。
最小的”在CI中查看邮件”实施计划
如果你从不稳定的共享邮箱设置开始,按此顺序迁移:
阶段1:停止登录邮箱
- 用API检索替换邮箱UI步骤。
- 将接收到的消息存储为产物。
阶段2:隔离
- 每次运行创建一个收件箱(或每次尝试用于验证流程)。
- 在邮件内容中添加关联令牌。
阶段3:使其安全和可扩展
- 如果使用webhooks,添加webhook签名验证。
- 添加超时、去重和清晰的错误消息。
- 如果需要允许列表或更严格的控制,考虑自定义域。
Mailhook支持即时共享域和自定义域支持,因此你可以快速开始并稍后加强控制。
Mailhook的适用场景
如果你的目标是在CI中查看邮件而永远不登录邮箱,你需要一个表现得像测试资源的收件箱:
- 通过API按需分配
- 作为结构化JSON消费
- 通过webhooks传递或通过轮询获取
- 安全地并行运行
这是Mailhook设计的核心工作流程。使用规范集成参考来实现确切的调用和负载格式:Mailhook llms.txt。你也可以从Mailhook的产品概述开始。