电子邮件是许多系统仍然依赖的最后一个”以人为本”的界面之一。但如果你正在构建 AI 智能体、LLM 工具链或 QA 测试框架,你最终需要以编程方式打开电子邮件,仅提取有用的工件(OTP、魔法链接、发票 ID、重置 URL),然后继续进行。
困难的部分是电子邮件以一个混乱的、有数十年历史的标准堆栈的形式到达:RFC 5322 标头、MIME 多部分正文、奇怪的编码,以及从未打算被测试(或智能体)解析的 HTML。本指南介绍了”原始电子邮件”实际是什么,为什么很棘手,以及如何可靠地将其转换为你的自动化可以信任的 JSON 格式。
以编程方式”打开电子邮件”意味着什么
当人类”打开电子邮件”时,电子邮件客户端悄悄地做了很多工作:
- 解析消息格式(标头加正文)
- 解码传输编码(base64、quoted-printable)
- 选择要显示的正文(通常是 text/plain 或 HTML)
- 解压附件
- 规范化日期、地址和字符集
以编程方式,你需要决定对你的工作流程来说”打开”意味着什么。对于自动化,“打开”通常意味着:
- 定位正确的消息确定性地(没有脆弱的邮箱搜索)
- 解析和规范化为稳定的模式
- 提取一个小的、可验证的工件(OTP、链接、令牌)
- 记录足够的信息以调试失败而不泄露敏感内容
一个好的心理模型是:将电子邮件视为不受信任的入站事件,而不是文档。
原始电子邮件,你实际收到的格式
大多数系统最终将电子邮件表示为原始 RFC 5322 消息:由标头和正文组成的文本和字节块。如果你需要标准参考,从 RFC 5322(消息格式)和 MIME 系列开始,如 RFC 2045(MIME 基础)。
一个”原始”消息通常包括:
-
标头:键/值对,如
From、To、Subject、Date、Message-ID,以及许多其他 - 正文:有时是纯文本,通常是 HTML,经常是带有边界的多部分
- 附件:表示为 MIME 部分,通常是 base64 编码
MIME 是为什么”只解析正文”会失败
如果你只看过纯文本电子邮件,解析会很容易。实际上:
- 许多消息是
multipart/alternative(both text/plain 和 text/html) - 一些是
multipart/mixed(正文加附件) - 一些包含嵌套的多部分
- 正文可以被编码(quoted-printable、base64)
- 字符集各不相同(UTF-8、ISO-8859-1 等)
这就是为什么对 HTML 进行正则表达式处理或在空行上分割很快就会变得脆弱。
从原始到 JSON:在自动化中持续有效的规范化管道
一个强大的”原始到 JSON”管道有几个清晰的阶段。这与实现无关:你可以在自己的服务中用库来做,或者使用收件箱 API 产生的 JSON。

阶段 1:解析结构(标头、MIME 树)
在这个阶段,你想要:
- 安全地解析标头(处理折叠标头、重复)
- 构建部分的 MIME 树
- 识别候选正文(text/plain、text/html)
- 识别附件(文件名、内容类型、大小)
阶段 2:解码和规范化
规范化是大多数自动化可靠性的来源:
- 解码传输编码(quoted-printable、base64)
- 规范化行尾
- 将文本转换为一致的 Unicode 表示
- 将
Date解析为 ISO 时间戳(但保留原始值以进行调试) - 将地址字段规范化为结构化对象(姓名、地址)
阶段 3:选择和清理内容
对于自动化和智能体,优选可预测的内容:
- 在可用时优选 text/plain
- 保留 HTML,但将其视为次要(适合渲染,解析有风险)
- 删除或忽略危险元素(脚本、奇怪的重定向)
阶段 4:提取自动化工件
而不是”理解整个电子邮件”,提取你的工作流程需要的内容:
- 验证链接(以及最终目标主机允许列表)
- OTP 候选(带有紧密模式和上下文检查)
- 关键标识符(订单 ID、票据 ID)
阶段 5:发出带有稳定字段的 JSON
你的 JSON 输出应该支持:
- 确定性匹配(message_id、inbox_id、相关 ID)
- 简单断言(主题包含、来自域等于)
- 最小工件提取(otp、verification_url)
- 可调试性(原始标头快照、接收时间戳)
这里有一种有用的方法来思考将原始电子邮件映射到 JSON 字段。
| 原始电子邮件元素 | 它看起来的样子 | 自动化所需的 JSON | 为什么重要 |
|---|---|---|---|
| Message-ID 标头 | Message-ID: <abc@domain> |
message_id |
去重和幂等性 |
| Date 标头 | Date: Tue, 30 Jan... |
received_at(ISO)、date_raw
|
时间断言,调试延迟 |
| From/To | RFC 5322 地址格式 |
from: {name, address}、to: [...]
|
可靠的发送者检查 |
| MIME 部分 | 多部分边界 |
text、html、attachments[]
|
避免解析错误的部分 |
| 传输编码 | base64、quoted-printable | 解码的字符串和字节 | 防止垃圾输出 |
| 正文中的链接 | HTML 锚点、纯 URL |
links[](规范化) |
更安全的魔法链接处理 |
破坏天真”打开电子邮件”实现的陷阱
即使是成熟的团队也会被相同的电子邮件边缘情况所困扰。如果你正在构建程序化的”打开电子邮件”路径,请预先为这些设计。
重复和折叠的标头
标头可以合法重复,并且可以跨行折叠。如果你天真地将标头映射到字典中,你可能会丢失数据或解析错误。
选择错误的正文
很多系统意外地解析:
- HTML 跟踪像素部分而不是用户可见的内容
- 页脚而不是 OTP 行
- 电子邮件中的转发消息
在可能时优选 text/plain,并明确说明你如何选择”主要”正文。
编码和字符集
如果你没有一致地解码传输编码和字符集,你会看到:
- 破损的 Unicode
- 缺失的标点符号,这可能会破坏 OTP 提取
- 测试中的错误比较
时间不是单一字段
电子邮件时间戳是混乱的。Date 标头是发送方提供的,并不总是值得信赖的。你的接收系统的时间戳对于延迟和超时通常更有用。
HTML 解析是一个安全边界
如果你对电子邮件内容运行智能体,将 HTML 视为对抗性输入。一个安全的策略是:
- 提取候选链接,然后对允许列表验证它们
- 避免在自动化中”点击”未知 URL
- 保留原始内容用于审计,但默认情况下不要将完整的 HTML 馈送给 LLM
对于解析像 Message-ID 和相关字段这样的标识符的更深入可靠性指导,Mailhook 有一个专门关注标头解析的单独帖子:Headers Email Guide: What to Parse for Reliability。
为 LLM 智能体提供的实用 JSON 合约
智能体最适合小的、结构化的输入。而不是给 LLM 整个电子邮件(特别是 HTML),提供一个紧凑的 JSON 对象,它是:
- 确定性的
- 最小的
- 可追溯到原始消息
一个示例”智能体安全”的形状可能看起来像这样:
{
"message_id": "<...>",
"received_at": "2026-02-01T20:12:33Z",
"from": {"address": "[email protected]", "name": "Example"},
"to": [{"address": "[email protected]", "name": null}],
"subject": "Your login code",
"text": "Your code is 123456",
"links": ["https://example.com/verify?token=..."],
"attachments": [{"filename": "invoice.pdf", "content_type": "application/pdf", "size": 48211}]
}
然后你可以添加第二层:你的测试或智能体工具实际消费的小提取对象(例如 { "otp": "123456" })。这保持你的工作流程简单并减少 LLM 对敌对内容的暴露。
自己构建 vs 从收件箱 API 消费 JSON
你有两种广泛的方法:
- 自己解析原始电子邮件(通过 IMAP/POP、直接 SMTP 接收或提供商 API)
- 使用提供结构化 JSON 和确定性检索的可编程收件箱服务
这里有一个决策表,倾向于匹配现实世界的工程权衡。
| 方法 | 最适合 | 常见痛点 | 典型结果 |
|---|---|---|---|
| IMAP 邮箱爬取 | 快速原型 | 不稳定的搜索,并发冲突,慢轮询 | 在 CI 和并行运行中中断 |
| 提供商 API(Gmail/Graph) | 带有账户的内部工具 | OAuth、配额、长期身份 | 有效,但操作上很重 |
| 运行你自己的 SMTP 捕获 | 本地集成测试 | 与真实电子邮件的可投递性差异 | 本地很好,在分阶段时不完整 |
| 带有 JSON 输出的可编程收件箱 API | QA 自动化、LLM 智能体、验证流程 | 需要集成另一个 API | 对自动化最确定性 |
如果你的核心需求是”以编程方式打开电子邮件并获得 JSON”,关键属性是不需要 HTML 抓取的机器可读输出。
使用 Mailhook 将电子邮件打开为 JSON(Webhook 优先,轮询后备)
Mailhook 围绕可编程一次性收件箱构建。而不是创建完整的电子邮件账户,你通过 API 创建收件箱,在你的工作流程中使用生成的地址,然后接收消息作为结构化 JSON。
相关 Mailhook 功能(来自产品描述):
- 通过 API 创建一次性收件箱
- 结构化 JSON 电子邮件输出
- RESTful API 访问
- 实时 Webhook 通知
- 电子邮件轮询 API
- 用于安全的签名有效负载
- 批处理电子邮件处理
- 共享域和自定义域支持
因为 API 在发展,端点和有效负载的权威来源是 Mailhook 的实现参考。在你连接智能体工具或测试之前,确保查看 llms.txt:
参考流程(概念性)
一个可靠的自动化流程看起来像这样:
- 为运行(或智能体会话)创建新收件箱
- 触发被测系统向该地址发送电子邮件
- 等待投递(优选 Webhook,使用轮询作为后备)
- 消费 JSON 有效负载
- 仅提取你需要的内容(OTP/链接)
这里是说明集成形状而不假设任何特定端点名称的伪代码:
# 伪代码:查阅 https://mailhook.co/llms.txt 获取确切的 API 字段和路由。
inbox = mailhook.create_inbox(
webhook_url="https://your-service.example/mailhook/webhook"
)
email_address = inbox["address"]
inbox_id = inbox["inbox_id"]
app.trigger_signup(email=email_address)
# Webhook 优先:你的 Webhook 处理程序存储由 inbox_id 键控的 JSON 消息。
# 轮询后备:等待超时和退避。
message = mailhook.wait_for_message(inbox_id=inbox_id, timeout_seconds=60)
otp = extract_otp(message["text"])
verify_url = extract_allowed_link(message.get("links", []))
assert otp is not None or verify_url is not None
验证 Webhook 签名
如果你接受入站 Webhook,将它们视为任何其他外部请求:
- 验证签名(Mailhook 支持签名有效负载)
- 使用幂等性来处理重试
- 仅存储你需要的内容,只要你需要它
同样,确切的签名方案和标头应该来自 llms.txt 中的合约。
使电子邮件自动化变得无聊(在好的方面)的设计技巧
目标不是”完美解析电子邮件”,而是使你的自动化可预测。
优选隔离和相关性
如果多个测试运行或智能体会话共享收件箱,你会重新引入最难的问题:找出哪个消息属于哪个运行。隔离的收件箱完全避免了邮箱搜索。
在意图上断言,而不是在呈现上
HTML 经常变化。你的断言应该针对稳定的属性:
- 发送者域
- 主题意图
- 单个 OTP 的存在
- 其主机在允许列表中的验证链接
保持原始消息可用于调试
当某些东西失败时,你想知道:
- 消息到达了吗?
- 它有什么标头?
- 你解析了正确的 MIME 部分吗?
这就是”原始加规范化 JSON”有用的地方。自动化在规范化字段上运行,而工程师使用原始上下文进行调试。
这让你处于何处
要在 2026 年以编程方式打开电子邮件,你有两个现实的选择:
- 成为电子邮件解析专家(RFC 5322、MIME 边缘情况、编码怪癖、安全陷阱)
- 使用已经进行规范化并为你提供测试和智能体可以消费的 JSON 的收件箱抽象
如果你的主要需求是智能体工作流程和 QA 可靠性,获胜策略通常是:将电子邮件视为事件流,每次运行隔离收件箱,并消费结构化 JSON。
如果你想使用 Mailhook 实现这一点,从 Mailhook llms.txt 中的合约开始,并围绕确定性等待(Webhook 优先,轮询后备)和最小工件提取设计你的工具。