关于 e2a — email for AI agents
e2a 是面向 AI Agent 的邮件网关,为每个 Agent 提供独立收件箱,支持收发、回复以及可选的人工审核外发邮件,并通过 SPF/DKIM 验证发件身份。项目提供托管的 MCP 服务,适合需要让 Agent 自主处理邮件的开发者。
主要功能
- 01为每个 Agent 分配独立邮箱
- 02发送、接收和回复邮件
- 03可选的人工审核外发邮件(HITL)
- 04支持 OAuth 2.1 与 Bearer API 密钥认证
- 05按凭证范围区分可用工具
- 06Webhook 推送,支持签名验证
适用场景
- 01让 AI Agent 使用独立邮箱收发邮件
- 02在外发前由人工审核 Agent 生成的邮件
- 03在 Claude Desktop 或 Cursor 中接入邮件能力
MCP 相关
该项目是一个托管的远程 MCP 服务,地址为 https://api.e2a.dev/mcp,通过 Streamable HTTP 向 MCP 客户端暴露邮件、审核、域名和联系人等工具,并根据凭证范围显示不同的工具集合。
配置
远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。
https://api.e2a.dev/mcpclaude mcp add --transport http dev-e2a https://api.e2a.dev/mcp{"mcpServers":{"dev-e2a":{"type":"http","url":"https://api.e2a.dev/mcp"}}}在 Cursor 中打开{"servers":{"dev-e2a":{"type":"http","url":"https://api.e2a.dev/mcp"}}}在 VS Code 中打开{"mcpServers":{"dev-e2a":{"type":"http","url":"https://api.e2a.dev/mcp"}}}{"mcpServers":{"dev-e2a":{"serverUrl":"https://api.e2a.dev/mcp"}}}命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。
https://api.e2a.dev/mcp需要授权工具
详细介绍
@e2a/mcp-server
适用于 e2a 的模型上下文协议服务 — 让任何支持 MCP 的 AI Agent 都拥有自己的电子邮件收件箱,用于发送、接收和回复邮件,并可选择在待发邮件发送前审核。
兼容 Google ADK、LangChain、OpenAI Agents SDK、Claude Desktop、Cursor、Cline 以及任何其他 MCP 主机。
连接
e2a 的 MCP 服务托管在云端。将 MCP 主机指向 Streamable HTTP 端点:
https://api.e2a.dev/mcp
有两种身份验证方式:
- OAuth 2.1(推荐用于交互式主机) — 将 e2a 添加为连接器,并在浏览器中授权。无需将密钥粘贴到配置中。
- Bearer API 密钥(用于编程或自托管) — 在
Authorization: Bearer <e2a API key>标头中发送你的 e2a 控制面板 API 密钥。
Agent 范围的凭证会在服务端解析其对应的 Agent。账户范围的调用方需在每次工具调用时传入 Agent 的 email。
快速开始
Google ADK(Python)
import os
from google.adk.agents import Agent
from google.adk.tools.mcp_tool.mcp_toolset import McpToolset
from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams
root_agent = Agent(
model="gemini-flash-latest",
name="e2a_agent",
instruction="Help the user manage their email. Reply to threads with `reply_to_message` to preserve threading headers.",
tools=[
McpToolset(
connection_params=StreamableHTTPConnectionParams(
url="https://api.e2a.dev/mcp",
headers={
"Authorization": f"Bearer {os.environ['E2A_API_KEY']}",
},
timeout=30,
),
),
],
)
LangChain(Python)
import os
from langchain.agents import create_agent
from langchain_mcp_adapters.client import MultiServerMCPClient
client = MultiServerMCPClient({
"e2a": {
"transport": "http",
"url": os.getenv("E2A_MCP_URL", "https://api.e2a.dev/mcp"),
"headers": {
"Authorization": f"Bearer {os.environ['E2A_API_KEY']}",
},
},
})
tools = await client.get_tools()
agent = create_agent(
"anthropic:claude-sonnet-4-6",
tools=tools,
system_prompt="Reply with reply_to_message; pending_review is accepted, so do not retry it.",
)
OpenAI Agents SDK(Python)
import os
from agents import Agent, Runner
from agents.mcp import MCPServerStreamableHttp
async with MCPServerStreamableHttp(
name="e2a",
params={
"url": os.getenv("E2A_MCP_URL", "https://api.e2a.dev/mcp"),
"headers": {
"Authorization": f"Bearer {os.environ['E2A_API_KEY']}",
},
"timeout": 30,
},
cache_tools_list=True,
max_retry_attempts=3,
) as e2a:
agent = Agent(name="e2a_agent", mcp_servers=[e2a])
result = await Runner.run(agent, "Reply to the latest unread email politely.")
Claude Desktop / Cline / Cursor
在主机的配置中将 e2a 添加为远程 MCP 服务(claude_desktop_config.json、cline_mcp_settings.json 等):
{
"mcpServers": {
"e2a": {
"url": "https://api.e2a.dev/mcp",
"headers": {
"Authorization": "Bearer YOUR_E2A_API_KEY"
}
}
}
}
支持 OAuth 连接器的主机也可以将 https://api.e2a.dev/mcp 添加为连接器,并在浏览器中授权 — 无需粘贴密钥。
工具
该服务最多提供 80 个工具,涵盖 Agent、邮件、人机协同审核、附件、域名、事件、Webhook、API 密钥、联系人/外联、邮件模板(测试版)、抑制列表和外部发送权限申请(测试版)。
可见的工具集取决于凭证的范围: Agent 范围的凭证可见 21 个运行时/收件箱工具(读取、发送、回复、恢复邮件,以及按 Agent 进行外联);账户 范围的凭证还可见 59 个管理/设置工具(Agent/域名/Webhook/事件/模板/API 密钥/联系人/抑制列表/发送权限管理 — 以及 HITL 审核发现和批准/拒绝功能,这些操作仅限账户所有者执行,Agent 不得自行批准)— 共 80 个工具。
每个工具都带有 MCP 注释(readOnlyHint/destructiveHint/idempotentHint),以便主机自动批准读取操作,并标记破坏性操作。
下表列出了最常用的工具 — MCP 主机的工具列表会显示当前范围允许使用的工具集及各工具说明。
身份
| 工具 | 说明 |
|---|---|
whoami | 获取经过身份验证的账户身份信息 — 用户、范围、套餐/限额;对于 Agent 范围的凭证,还会返回绑定的 Agent 地址。在会报告此信息的部署中,也会包含附加的测试版 sending_access 对象(参见下方的“外部发送权限”)。 |
list_agents | 列出 Agent 收件箱;传入 deleted:true 可列出 30 天内已删除的内容。(管理员/账户范围。) |
get_agent | 按完整电子邮件地址获取一个 Agent 收件箱。 |
create_agent | 使用完整电子邮件地址注册新的 Agent — 可使用你拥有的已验证域名,也可使用部署提供的共享域名。没有邮件投递“模式”:始终可以通过 list_messages(轮询)或 create_webhook 订阅来接收邮件。(管理员/账户范围。) |
restore_agent | 恢复软删除的 Agent 及其配置。(管理员/账户范围。) |
Webhook 投递经过签名 — 请验证签名。 推送投递是顶层资源(
create_webhook),并非按 Agent 设置的模式。e2a 会使用 Webhook 的签名密钥对每次 Webhook 投递进行 HMAC 签名(该密钥由create_webhook/rotate_webhook_secret返回,且仅返回一次)。你的处理程序必须验证每个请求的签名 — e2a SDK 提供constructEvent(rawBody, signatureHeader, secret),可通过一次调用验证签名并返回类型化事件(签名无效时会抛出错误)。也可以完全不使用 Webhook,改用list_messages进行轮询。
消息
通过 send_at / scheduled_at 定时发送的功能目前处于 beta 阶段,在正式稳定之前可能会发生变化。
外部发送权限(beta)。 在对此加以限制的部署中,账号可能只能通过共享身份向其已验证的账号邮箱以及同一账号下的 Agent 收件箱发送邮件。如果
send_message/reply_to_message/forward_message(或已弃用的send_email)调用中包含其他任何 To/Cc/Bcc 收件人,整个发送操作都会被拒绝,返回external_sending_not_enabled(403)——不会将任何邮件加入队列,重试完全相同的调用也不会成功。whoami的sending_access对象会报告当前状态(enforcement_applies、shared_external_approved、paid_external_sending_entitled、owner_recipient_verified)以及available_unlocks——即此部署可用于解除限制的方式:operator_approval始终可用;仅在列出的部署中,verified_domain(使用你自己的已验证域名发送)和paid_entitlement(付费方案)才可用。托管服务仅列出operator_approval。如需申请批准,请调用一次request_sending_access(账号范围;也可在错误信息中的recovery_url所指向的控制面板中申请):若申请仍在处理中,再次提交会返回同一申请;若返回rate_limited(每 30 天 3 次申请),则不得重试。由运营人员作出决定,结果会通过电子邮件发送给账号所有者;get_sending_access_request可显示当前状态。没有任何 MCP 工具可以授予批准。
工具 描述 request_sending_accessBeta。提交一项申请,由运营人员审核外部发送权限( use_case、recipients、expected_daily_volume)。会修改数据:冻结账号会返回account_read_only。 (管理员/账号范围。)get_sending_access_requestBeta。获取账号最新的申请及其 state(pending/approved/declined)。只读。(管理员/账号范围。)
| 工具 | 描述 |
|---|---|
send_message | 发送新邮件。立即加入队列时返回 status: accepted;未来的 send_at 会返回 status: scheduled 和 scheduled_at;进入审核保留状态时返回 status: pending_review。这三种结果都表示操作已持久成功——不要重新发送。 |
reply_to_message | 回复一条消息——可以是 Agent 收到的消息(回复其发件人),也可以是 Agent 发出的消息(继续与原收件人的邮件线程)。保留 In-Reply-To / References,以维持线程连续性;接受与 send_message 相同的可选 send_at。 |
forward_message | 将一条消息转发到新线程,默认携带其现有附件。接受与 send_message 相同的可选 send_at。 |
list_messages | 列出邮件;传入 deleted:true 可列出垃圾邮件。可按 read_status(unread / read / all)和发件人筛选;发件人筛选使用可避免保留字冲突的 from_。采用游标分页(传入 cursor + limit,返回 next_cursor)。 |
delete_message | 将消息移至垃圾箱(约 30 天内可恢复)。需要 confirm: true。MCP 不提供按消息永久删除的功能——请使用 REST API/SDK。(账号范围可通过 delete_agent 的 permanent: true 清空整个收件箱。) |
restore_message | 恢复软删除的消息,并重新开始计算其保留期限。 |
get_message | 获取单条消息的完整正文、标头和附件元数据。 |
get_attachment | 获取单个附件的元数据和短时有效的 download_url(需在带外获取字节);对于较小的文件(≤256 KB),传入 inline: true 会返回 base64 编码的 data。 |
update_message_labels | 添加或移除消息标签。 |
MCP 消息输出会有意省略 REST 邮件读取 beta 中的 thread_id 字段,也不添加线程筛选器或端点。list_conversations / get_conversation 仍是由调用方管理的 conversation_id 应用视图,而不是 RFC 邮件线程枚举。使用原始消息 ID 调用 reply_to_message,以保留传输层拓扑结构。
人工参与审批
list_reviews/get_review 会显示账号范围内所有待处理项:等待发送审批的外发草稿,以及被筛查门控拦截的入站消息。email.review_requested webhook 还会针对任一方向发送推送通知,并携带相同的 message_id。approve_review/reject_review 工具会根据方向采取不同操作:批准入站拦截项会将其释放到 Agent 的收件箱(不发送、不创建草稿,忽略覆盖字段);拒绝入站拦截项会将其丢弃,使其永远不会到达 Agent。
| 工具 | 描述 |
|---|---|
list_reviews | 列出账号中所有等待人工审核的入站和外发消息,并按最早到期时间排序。 |
get_review | 获取完整的拦截消息(正文、收件人,以及适用时的筛查上下文)。 |
approve_review | 外发:发送拦截的消息,可选择应用审核者的修改(主题/正文/收件人)。入站:解除筛查拦截,将消息释放到 Agent 的收件箱(忽略覆盖内容)。仅限账号范围 — Agent 不能自行审批。 |
reject_review | 外发:丢弃拦截的消息。入站:解除筛查拦截,使消息永远不会到达 Agent。可选的 reason 会保存以供审计。仅限账号范围。 |
域名
| 工具 | 描述 |
|---|---|
register_domain | 注册自定义发信域名;返回需要发布的 MX + TXT DNS 记录。(管理员/账号范围。) |
API 密钥
所有操作均限管理员/账号范围。create_api_key 仅创建 Agent 范围的密钥 —
该密钥绑定到一个收件箱,且只能以该 Agent 的身份执行操作。无法通过 MCP 创建账号范围(工作区管理员)密钥;请通过控制面板或原始 API 创建,并由人工参与操作。
| 工具 | 描述 |
|---|---|
list_api_keys | 列出账号的 API 密钥 — 仅显示元数据(密钥仅在创建时显示一次)。 |
create_api_key | 创建绑定到收件箱的新 Agent 范围密钥;明文密钥仅返回一次 — 请立即保存。 |
delete_api_key | 永久撤销密钥(需要 confirm: true)。 |
联系人(测试版)
账号往来的联系人(list_contacts / get_contact /
create_contact / update_contact / delete_contact,管理员/账号范围)
以及某个 Agent 针对各联系人的外联状态 — 阶段、下一步操作、回复/
抑制事实(list_outreach_contacts / get_outreach_contact /
set_outreach_contact / delete_outreach_contact,账号范围或绑定到相应收件箱的
Agent 范围凭据均可使用)。导入不会触发任何操作 — 它只记录身份信息并加入外联流程,不会发送任何内容。
| 工具 | 描述 |
|---|---|
list_contacts / get_contact | 列出账号联系人(可按来源、导入批次/创建时间范围筛选),或按地址获取单个联系人。 |
create_contact / update_contact / delete_contact | 创建、部分更新(通过 if_match 进行 etag 校验),或删除联系人的身份信息及其所有按 Agent 区分的外联记录。删除联系人不会移除抑制项。 |
import_contacts | 从数据行批量导入联系人,可选择将其加入某个 Agent 的外联流程。 |
delete_contact_import | 撤销导入批次,移除未更改的联系人及其创建的加入记录。 |
list_outreach_contacts / get_outreach_contact | 列出或获取某个 Agent 正在跟进的联系人,以及由服务端生成的回复/投递事实。 |
set_outreach_contact | 将联系人加入某个 Agent 的外联流程,或更新由该 Agent 管理的字段(阶段、下一步操作)。 |
delete_outreach_contact | 将联系人从某个 Agent 的外联流程中移除;联系人及其抑制项仍会保留。 |
抑制名单
收件人阻止名单有两个范围,均仅限管理员/账户级(服务会拒绝所有使用 Agent 级凭据的请求——Agent 不得读取或编辑阻止名单)。账户名单会自动添加硬退信和投诉,并适用于所有 Agent 的发送;Agent 名单(测试版)则保存针对某个确切发送 Agent 的退订/手动阻止记录。
| 工具 | 描述 |
|---|---|
list_suppressions | 列出账户级抑制名单(地址、来源 bounce/complaint/manual、原因、来源消息 ID)。 |
delete_suppression | 取消对收件人的账户级抑制。需要 confirm: true;仅用于已知可送达的地址——移除真实退信地址会损害发件人信誉。 |
list_agent_suppressions | 列出某个 Agent 的被阻止收件人(unsubscribe/manual)。 |
create_agent_suppression | 为某个 Agent 幂等地添加手动阻止;其他 Agent 仍可向该地址发送邮件。 |
delete_agent_suppression | 仅移除确切的 Agent 级阻止记录。需要 confirm: true,因为该阻止可能代表退订。 |
模板(测试版)
可重复使用的邮件模板支持 {{variable}} 插值(Mustache 的简化子集——不支持循环/区段;缺失变量会渲染为空字符串),并提供预构建模板的只读目录(welcome、verify-code、password-reset、receipt、agent-status、daily-digest、approval-request)。通过 send_message 的 template_id/template_alias + template_data 使用模板发送(不能与字面量主题/正文同时使用)。所有模板管理工具均为管理员/账户级。测试版——在模板正式稳定前,结构可能会变更。
| 工具 | 描述 |
|---|---|
list_templates / get_template | 列出账户中已存储的模板(摘要行);get_template 会返回完整正文源文本。 |
create_template | 从字面量源文本创建模板,或通过 from_starter 原样复制起始模板。 |
update_template / delete_template | 编辑模板(会重新解析已更改部分)或删除模板。 |
validate_template | 对源文本进行试运行:返回解析错误、针对 test_data 渲染的预览,以及 suggestedData 占位符。 |
list_starter_templates / get_starter_template | 浏览起始模板目录;详情视图包含完整正文源文本和每个变量的元数据。 |
错误
每次工具调用失败时,都会返回一个 isError: true 的 MCP 结果,其中包含两种错误表示形式:
-
structuredContent— 可供机器分支处理的形式。应依据此字段分支处理,而不是依据文本:{ "code": "domain_not_verified", "retryable": false, "status": 403, "request_id": "req_abc123", "retry_after_seconds": 30, "details": { "field": "…" } }字段 是否始终存在 含义 code始终存在 API 错误信封中的稳定 snake_case 标记(例如 domain_not_verified、rate_limited、message_not_pending)。MCP 层自身引发的错误(参数错误/缺失、confirm:true守卫)会使用invalid_request。retryable始终存在 如果重试有可能成功(速率限制、5xx、连接失败),则为 true。status仅限 API 错误 API 响应的 HTTP 状态( 0= 连接级故障)。未发出请求时(MCP 层验证)不包含此字段。request_id有时存在 服务端请求 ID——提交支持请求时请附上。 retry_after_seconds有时存在 可重试错误的退避提示。 details有时存在 错误信封中的结构化字段级详情(过大时会省略)。 -
content(文本) — 面向用户且保持稳定的可读形式: API 错误的格式为e2a error [<code>] (retryable)?: <message>,MCP 层错误的格式为e2a error: <message>。现有解析此文本的 Agent 可继续正常工作,但新集成应改为读取structuredContent。
成功结果不受影响(文本块中的 JSON;当仍有后续页时,列表工具会返回一个以领域命名的数组以及 next_cursor)。
运行 HTTP 服务
自行托管的用户通过 mcp/Dockerfile 或仓库的 docker-compose.yaml 中的 mcp
服务运行 HTTP 传输层。第 2 天运维(SLO、故障类别、关联)详见
docs/runbooks/mcp-server.md;本节是配置和接口的参考。
环境变量
| 变量 | 默认值 | 说明 |
|---|---|---|
PORT | 3000 | 监听端口。 |
E2A_API_URL | 必填 | 此传输层将持有者令牌转发到的 e2a API 服务的基础 URL(并通过该 URL 检查就绪状态)。规范名称;仍接受 E2A_URL 和 E2A_BASE_URL 这两个旧别名,但会发出弃用警告。无默认值——如果这三个变量均未设置,进程将拒绝启动(ConfigError,退出码 2),而不会悄悄将用户的持有者令牌转发到运营方的 api.e2a.dev。 |
MCP_ALLOWED_HOSTS | 从 MCP_PUBLIC_URL 推导,否则为必填 | 以逗号分隔的 Host 允许列表(防 DNS 重绑定)。Host 不在列表中的请求访问 /mcp 和发现文档时,将收到 421。比较前会移除端口。无固定默认值;未设置时回退为 MCP_PUBLIC_URL 的主机名,如果两者均未设置,则拒绝启动。 |
MCP_PUBLIC_URL | 未设置 | 此 MCP 服务可从外部访问的 URL,会原样用于 RFC 9728 受保护资源元数据和 401 WWW-Authenticate 质询;设置后也会用作 MCP_ALLOWED_HOSTS 的默认值。当前置代理看到的 URL 与入站 Host 不同时应设置此项(本地开发:http://localhost:8765)。 |
MCP_AUTHORIZATION_SERVER_URL | $E2A_API_URL | 在受保护资源元数据中公布的授权服务器 URL。当转发持有者令牌的 URL 仅供容器内部访问,而 OAuth 客户端必须从主机访问 AS 时,可覆盖此项。 |
E2A_TRUST_PROXY | loopback | Express 的 trust proxy 设置——指定采纳哪些跃点的 X-Forwarded-* 标头来生成发现 URL。可设为 true/false、跃点数,或预设值/子网列表。 |
MCP_RESOLVE_TIMEOUT_MS | 5000 | 解析持有者令牌对应主体的 whoami 探测的正整数时限(毫秒),最多重试 1 次。超时或后端返回 5xx 时,请求将以失败关闭方式按最小权限 Agent 范围处理,不缓存,并记录为 auth_resolution result=fallback。 |
MCP_SESSION_IDLE_MS | 300000 | **旧名称——设置的是解析缓存大小,不是会话。**已缓存的持有者令牌→主体解析的 TTL(毫秒)。此传输层无状态;没有需要超时的会话。 |
MCP_MAX_SESSIONS | 500 | **旧名称——设置的是解析缓存大小,不是会话。**已缓存的持有者令牌→主体条目的最大数量;超过上限时淘汰最旧条目。缓存未命中会执行一次 whoami,绝不会导致断开连接。 |
健康检查端点
这三个端点均无需身份验证:
-
GET /healthz— 仅检查进程存活状态,绝不访问后端:{ "ok": true }。将其用于存活探针。 -
GET /readyz— 就绪检查:探测{E2A_API_URL}/api/health(超时 2s, 结果缓存 10s,避免抓取指标的集群引发探测风暴)。可访问时返回 200{ "ok": true, "checks": { "api": "ok" } };不可访问时返回 503{ "ok": false, "checks": { "api": "unreachable" }, "request_id": "…" }Retry-After: 10(失败缓存 TTL)。将其用于就绪探针。
-
GET /metrics— Prometheus 文本格式指标。示例行:mcp_http_requests_total{route="mcp",status_class="2xx"} 1523 mcp_http_request_duration_seconds_bucket{route="mcp",le="0.5"} 1480 mcp_auth_resolutions_total{result="cache_hit"} 1490 mcp_tool_executions_total{tool="list_messages",outcome="ok"} 412 mcp_readyz_checks_total{result="ok"} 60 mcp_resolve_cache_entries 7路由标签取值范围有限:
mcp | healthz | readyz | metrics | discovery | other。
结构化日志
在 stderr 上输出单行 JSON,采用 GCE 风格的 severity / event / message
字段及其他字段。请求范围内的事件包含 request_id(响应中的
X-Request-Id):auth_resolution(result、duration_ms、scope?;
invalid / fallback 以 WARNING 级别记录)、http_request(route、method、
status、duration_ms;成功的探测/抓取流量(healthz / readyz / metrics)
会计量,但不会记录日志)、tool_execution(tool、outcome、
duration_ms、error_code?)、terminal_error。绝不记录 Bearer 令牌;解析缓存以 SHA-256 指纹为键。符合 ^[A-Za-z0-9_-]{1,64}$ 的传入
X-Request-Id 会被采用;否则,服务会生成 mcpreq_<12hex>。该 ID 会在每个响应中回显,但
不会转发到后端 API。
客户端重试策略
工具调用失败时会返回 structuredContent(参见错误):
根据 code / retryable / retry_after_seconds 分支处理。仅对
retryable: true 的错误进行重试,采用指数退避 + 抖动,最多约 3 次。
绝不重试 4xx(401 → 重新认证;421 → 修正 URL)。
发送工具返回的 accepted、scheduled 和 pending_review 属于成功结果
——不要重试。若将来的 send_at 指向发送 Agent 自己的地址,且适用直接回环规则,则会返回
400 invalid_request;审核暂缓优先级更高,并会丢弃计划发送时间。传输是无状态的,因此中断后“重新连接”
只需再次 POST;MCP 客户端会自动执行此操作。
链接
许可证
Apache-2.0
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。