当 AI Agent 需要与人类用户或其他 Agent 进行异步通信时,邮件依然是唯一具备原生寻址能力且无需安装任何客户端的协议。然而,裸 SMTP 并未解决身份验证、消息完整性验证、本地 Agent 接收以及人工审批等核心问题。E2a(Authenticated Email Gateway for AI Agents)正是针对这一缺口设计的开源解决方案,它将邮件的通用性与 Agent 架构的可编程性桥接起来。
核心定位:为什么 Agent 需要专用邮件网关
传统的邮件收发方案(如 SendGrid、Resend)仅处理出站与入站解析,无法覆盖 Agent 场景下的四个关键需求。其一是本地 Agent 接收 —— 运行在开发者笔记本或企业防火墙后的 Agent 没有公网 Webhook URL,而主流邮件服务商均为 Webhook-only 设计。其二是对话线程管理 —— 当人类用户从 Gmail 回复时,Agent 需要一个稳定的 conversation_id 来维护上下文,而不是每次重新解析 In-Reply-To。其三是共享域名下的即时 Agent 配发 —— 管理员配置 shared_domain 后,用户只需提交 slug 即可获得 my-bot@agents.e2a.dev,无需配置 DNS MX 记录。其四是出站审批流 —— 企业场景下 Agent 发出的每封邮件可能需要人工确认,裸 SMTP 无法提供这一能力。
E2a 在传输层之上构建了完整的身份层、消息抽象层与审批层。其入站流程为:SMTP 接收 → SPF/DKIM 校验 → Agent 查找 → HMAC 签名注入 → Webhook 或 WebSocket 分发。出站流程为:API 调用 → 可选 HITL 挂起 → SMTP 转发(Agent 间)或上游 SMTP(至人类用户)。
认证体系:HMAC 签名与重放防护
每封通过 E2a 投递的邮件都携带一组 X-E2A-Auth-* 头字段,其中 X-E2A-Auth-Signature 是整个认证体系的核心。该签名使用 HMAC-SHA256 对规范字符串进行计算,规范字符串按以下顺序拼接各字段并以换行符分隔:verified、sender、entity_type、domain_check、delegation、timestamp、message_id、body_hash。
这一设计的关键在于 body_hash 字段 —— 它将签名绑定到了邮件原始字节的 SHA-256 哈希值,而非仅绑定发送者声称的身份。攻击者即使截获了一组合法的 (headers, signature),也无法将这组凭证移植到另一封不同的邮件或被篡改过的邮件体上,因为 body_hash 不匹配会导致签名验证失败。
时间戳字段(X-E2A-Auth-Timestamp)则提供重放攻击防护。SDK 在验证签名时默认检查时间戳是否在最近 5 分钟窗口内,超出该窗口的消息会被直接拒绝。这一阈值可通过环境变量 E2A_TIMESTAMP_TOLERANCE 调整,但生产环境中通常不建议扩大,因为扩大窗口实质上削弱了重放防护能力。
SDK 的 parse_webhook 方法将解析与验证合并为一次调用。以 Python SDK 为例:
from e2a.v1 import E2AClient
client = E2AClient() # 读取 E2A_API_KEY
email = client.parse_webhook(request_body) # 读取 E2A_WEBHOOK_SECRET;签名失败则抛出异常
# 此处 email.sender、email.subject 等字段已通过验证,可安全使用
TypeScript SDK 的行为一致:
import { E2AClient } from "@e2a/sdk";
const email = await client.parseWebhook(req.body); // 签名不匹配时抛出错误
SDK 的设计哲学是 “安全默认”—— 在签名验证通过之前,任何可由攻击者控制的头部字段(如 sender、subject)均不可访问。未验证的 payload 直接调用这些字段会触发 UnverifiedEmailError,这种编译期 / 运行时防护有效避免了开发阶段因疏忽导致的身份伪造风险。
Agent 运行模式:Cloud 与 Local 的选择
注册 Agent 时通过 agent_mode 参数指定运行模式,该参数直接影响消息投递机制与基础设施要求。
Cloud 模式(默认值)要求 Agent 具备公网可达的 Webhook URL。E2a 在接收到入站邮件后,通过 HTTPS POST 将消息体与认证头一同推送至该 URL。此模式适合部署在云服务器或有固定公网 IP 的基础设施中的 Agent。优势在于延迟最低(消息到达后即时推送),劣势在于需要维护 Webhook 端点的可用性 ——E2a 会在投递失败时执行指数退避重试,但若 Webhook 服务长时间不可达,消息会保留在队列中直至重试耗尽。
Local 模式专为无公网可达性的场景设计,包括开发者本地机器、企业内网环境和边缘设备。Agent 不提供 Webhook URL,而是通过 WebSocket 主动连接到 E2a 服务器的 /api/v1/agents/{email}/ws 端点。E2a 在入站邮件到达后向已连接的 WebSocket 推送轻量级元数据通知,Agent 接收到通知后再通过 REST API(client.get_message)拉取完整邮件内容。这种拉取模式使得消息在 Agent 离线期间会累积在服务器端,重连后按序分发,实现了可靠的离线感知。
对于 Local 模式下的快速原型验证,可使用 CLI 的 e2a listen 命令建立 WebSocket 监听:
npm install -g @e2a/cli
e2a login
e2a listen --json # 输出每条消息的完整 JSON
e2a listen --forward http://localhost:18789/v1/responses --forward-token <token> # 转发至 OpenAI Responses API
Human-in-the-Loop 审批流的工程配置
HITL(Human-in-the-Loop)机制允许管理员对特定 Agent 的出站邮件启用人工审批关卡。当 Agent 调用 send 或 reply API 时,如果该 Agent 的 hitl_enabled 为 true,消息不会立即投递,而是进入 pending_approval 状态,API 返回 HTTP 202 Accepted。审批者可通过三种渠道处理待审批消息:Dashboard 页面操作、访问邮件中自动发送的魔法链接(GET /api/v1/approve?token=...),以及 CLI 的 e2a pending 命令。
超时控制通过 hitl_expiration_action 参数管理。该参数可选 expired_approved(超时后自动发送)或 expired_rejected(超时后自动丢弃)。超时时长通过 hitl_expiration_ttl 配置,默认为 24 小时。配置示例:
# 启用 HITL,超时 48 小时后自动发送
curl -X PUT http://localhost:8080/api/v1/agents/agent@your-domain.com \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"hitl_enabled": true, "hitl_expiration_action": "expired_approved", "hitl_expiration_ttl": "48h"}'
魔法链接采用 HMAC 编码,包含消息 ID、审批操作与有效期,无需会话存储即可验证。生产环境中需正确配置 E2A_PUBLIC_URL 和出站 SMTP,否则魔法链接邮件无法发送。
SDK 参数与环境变量参考
Python SDK 的完整初始化参数:
from e2a.v1 import E2AClient, AsyncE2AClient
# 同步客户端
client = E2AClient(
api_key="e2a_...", # 必填,优先读取 E2A_API_KEY 环境变量
webhook_secret="secret", # 验证 webhook 时必填,优先读取 E2A_WEBHOOK_SECRET
base_url="https://..." # 可选,连接自托管实例
)
# 异步客户端(Local 模式 WebSocket)
async with AsyncE2AClient(api_key="e2a_...") as client:
async for notif in client.listen("agent@your-domain.com"):
email = await client.get_message(notif.message_id)
await email.reply("确认收到。")
TypeScript SDK 对应参数:
import { E2AClient } from "@e2a/sdk";
const client = new E2AClient({
apiKey: process.env.E2A_API_KEY,
webhookSecret: process.env.E2A_WEBHOOK_SECRET,
baseUrl: "https://your-e2a-instance.com",
});
关键环境变量对照表:
| 变量名 | 用途 | 必需 |
|---|---|---|
| E2A_API_KEY | SDK 与 CLI 的认证令牌 | 是 |
| E2A_WEBHOOK_SECRET | 验证入站 webhook 签名的共享密钥 | Cloud 模式 |
| E2A_URL | 连接到自托管实例的地址 | 自托管时 |
| E2A_ENV | 设置为 production 启用严格安全检查 | 生产部署 |
部署拓扑与多副本扩展
E2a 的服务端由 Go 语言实现,存储依赖 PostgreSQL 14+,出站邮件通过标准 SMTP 协议投递。这一技术栈选择使其可以运行在任意容器化环境中。docker-compose up -d 启动后默认暴露三个端口:8080(HTTP API)、2525(SMTP 中继)和 3000(Dashboard,含 Caddy 反向代理)。
多副本部署时,WebSocket 推送采用内存广播模型,每个节点仅能推送至当前连接在该节点的客户端。因此需要在负载均衡层启用 WebSocket 亲和性(sticky session),或者使用 Redis Pub/Sub 将推送事件汇聚至统一节点。API 层面的消息处理使用 SELECT ... FOR UPDATE SKIP LOCKED 实现工作队列的分布式协调,多副本水平扩展是安全的。
数据保留策略为入站消息体默认存储 30 天,可通过环境变量 E2A_MESSAGE_RETENTION_DAYS 调整。API Key 以哈希形式存储,HMAC 签名密钥在内存中持有,泄露后可通过 Dashboard 轮换。应用日志记录发送方 / 接收方地址但不记录邮件正文、附件与密钥材料。
资料来源
- GitHub: https://github.com/mnexa-ai/e2a
- 官网: https://e2a.dev
内容声明:本文无广告投放、无付费植入。
如有事实性问题,欢迎发送勘误至 i@hotdrydog.com。