WorkflowsTutorialMCPAI agents

用 MCP 连接的智能体搭建邮件自动回复器

用 MCP 连接的 AI 智能体搭建邮件自动回复器:轮询未读邮件、起草并安全回复,配合权限、速率限制与护栏。

用 MCP 连接的智能体搭建邮件自动回复器 — MCPEmails

基于 MCP Emails 搭建的邮件自动回复器是一个循环,而不是 webhook。这里没有服务端主动触发的事件,所以你的智能体按计划用 email_read(动作 list轮询未读邮件,用 email_read(动作 read)读取每一封消息,起草回复,然后调用 email_compose(动作 reply)。本文带你走完整个搭建过程:轮询循环、你需要的权限、如何熬过速率限制,以及那些能防止智能体用胡编的回复轰炸你联系人的护栏。

这是给你的 AI 智能体邮件访问权限这条线索里偏进阶的一端。如果你只想做分拣和晨间摘要、不想发送任何东西,先读AI 智能体的收件箱分拣与摘要——那是更安全的起点。等你真的想让智能体按下发送键时,再回到这里。

实话实说:根本没有 webhook

大多数"自动回复器"教程都假设邮件一落地就会触发一个推送事件。MCP Emails 不是这么工作的,据我所知没有哪个 MCP 服务器是这么工作的。MCP 是一个请求/响应式的工具协议。服务器只在你的智能体发起请求时作答;它从不主动呼叫你。

所以一个真正的自动回复器就是一个调度器加一次轮询。你每隔 N 秒或 N 分钟跑一个周期(tick),请求未读消息,处理所有新到的内容。就这样。它不如 webhook 优雅,但它可预测,而且这意味着由你来掌控节奏和影响范围。

挑一个轮询间隔,在你能容忍的延迟和你的速率预算之间取得平衡。对于客服类回复,每 60 秒一次就够了。对大多数收件箱来说,每 5 分钟一次绰绰有余,几乎碰不到你的限额。别每秒轮询一次——你会无谓地烧掉配额,而收件箱很少变化得那么快。

你需要什么:一把带发送权限的 API 密钥

自动回复器几乎总是作为脚本或定时任务(cron job)运行,而不是在聊天客户端里。这意味着走的是 API 密钥这条路,而不是 OAuth

在控制台里,进入 API Keys,创建一把密钥,并授予它你确实需要的权限:

  • read:email——用于通过 email_read 轮询、列出和读取消息。
  • send:email——用于调用 email_compose(动作 reply)。

密钥只显示一次。把它复制下来。它看起来像 mcpe_live_...,你要在每次请求时带上它:

Authorization: Bearer mcpe_live_YOUR_KEY

MCP 端点是一个单独的 URL:

https://www.mcpemails.com/api/mcp

授予这个任务用得到的权限。如果将来某个版本的回复器需要转发或标记邮件,到时候再加上那些能力。一把只能读取和回复的密钥,即便泄露,也只能读取和回复。(关于密钥处理与轮换的完整说明,参见文档。)

这个循环

下面是一个能跑的回复器的大致样子。我用 MCP 工具上的伪代码写出来,这样能清楚地看出哪些调用按什么顺序发生。具体的 MCP 客户端接线取决于你的技术栈,但顺序才是重点。

# Tools used: inbox_list, email_read, email_compose

inbox = inbox_list()[0]              # discover, never hardcode the UUID

while True:
    unread = email_read(
        action="list",
        inbox_id=inbox["inbox_id"],
        unread_only=True,
        limit=20,
    )

    for summary in unread["messages"]:
        if not should_handle(summary):   # allowlist + filters, see below
            continue

        msg = email_read(
            action="read",
            inbox_id=inbox["inbox_id"],
            message_id=summary["message_id"],
        )

        draft = generate_reply(msg)      # your LLM call

        if not passes_guardrails(draft, msg):
            queue_for_human(msg, draft)  # don't send; flag it
            continue

        send_with_backoff(
            inbox_id=inbox["inbox_id"],
            message_id=summary["message_id"],
            body=draft,
        )

    sleep(60)

有三点值得留意。第一,智能体调用 inbox_list 来发现 inbox_id,而不是把一个 UUID 粘进代码里——这就是"先发现"的模式,它能让脚本在你重新连接收件箱后依然可用。第二,unread_only: true 才是让这成为一个新邮件回复器、而不是一台对所有人反复回复的机器的关键。第三,每一封消息在发送任何东西之前都要经过护栏。

把消息标记为已处理

如果你想让一封消息在你回复之后不再显示为未读,读取时把 mark_as_read 设上,或者显式调用 mark_read。这里要慎重:如果你的循环回了复却从不把消息标为已读,下一个周期就会再回一次。把消息标记为已处理,正是循环保持幂等的方式。

速率限制,以及为什么你永远不能盲目重试发送

每一把 API 密钥都被限制在每分钟 100 次请求、每小时 1,000 次、每天 10,000 次,与套餐无关。你的工作区还有一个按层级划分的突发上限——Free 为 60 req/min,Solo 为 300,Team 为 1,000(见定价)。一个礼貌的轮询并回复的循环会舒舒服服地待在这个范围内,但一个行为不当的循环可能会撞上去。

当你触到限制时,服务器会返回一个可重试的错误(代码 -32029),其中带有以秒为单位的 data.retry_after。尊重它。睡那么久,然后继续。

下面这条规则最要紧:永远不要盲目地自动重试 email_compose 的发送或回复。发送不是幂等的。如果一次发送调用超时或返回结果含糊,天真的重试可能会把同一封回复发送两次。读取尽管放心重试;把发送当作一次性操作,只在遇到明确的可重试错误时才重试,配合退避和一个硬上限。

def send_with_backoff(**kwargs):
    delay = 2
    for attempt in range(3):
        try:
            return email_compose(action="reply", **kwargs)
        except RateLimited as e:
            sleep(e.retry_after or delay)
            delay *= 2
        except RetryableError:
            sleep(delay)
            delay *= 2
        # any other error: do NOT retry a send. log it, move on.
        else:
            break
    raise SendFailed(kwargs)

注意它只在限速或明确可重试的错误上重试,最多重试三次,而且从不无限循环。一次连续失败三次的发送,是该交给人去处理的问题,而不是循环的问题。

护栏:工具与隐患之间的分水岭

一个不经监督就发送的自动回复器,离给你的 CEO 发一封信心满满却完全错误的回答只差一个糟糕的提示词。先把刹车造好,再去造引擎。

用白名单限定谁能收到自动回复

别回复所有人。从一个很窄的白名单开始——一个客服别名、一个特定的发件人域名、匹配某种主题模式的消息。其他一切都排队交给人处理或直接忽略。白名单是你能加上的最高杠杆的单项护栏,因为它把故障限定在一个你自己选定的人群里。

让人留在闭环里,至少一开始要这样

这套东西最安全的版本根本不是自动回复器——而是一个自动起草器。智能体读取、起草并把回复暂存好,但由人来批准发送。MCP Emails 支持草稿,所以你可以让循环调用 draft(动作 create),而不是调用 email_compose,然后由你自己把好的那些发出去。先在草稿模式下跑一周。看看它本来会发出什么。只有到那时,再把你信得过的那些切到自动发送。

阻断循环和自我回复

两种经典的故障模式:

  • 自动回复来回弹。如果你回复了一封休假自动回复邮件,而它又回复了你的回复,你就和别人的机器人一起搭出了一个无限循环。把看起来像自动发出的消息过滤掉(检查 Auto-Submitted 头、no-reply 发件人,或者已知的自动回复主题),并且永远不要回复你自己的地址。
  • 对同一邮件线程反复回复。处理完后把消息标为已读,并保留一份你已经回答过的消息 ID 的小记录。一旦涉及发送,幂等性就不是可选项。

约束模型能说什么

给起草模型紧凑的指令和一个长度上限。对于客服回复,一个带占位槽的模板胜过自由发挥的生成。并且在发送前跑一个廉价的检查:这份草稿里有没有退款承诺、一个价格、一项法律承诺,或者一个你没料到的链接?如果有,就把它转给人。这就是上面循环里的 passes_guardrails 检查,也是你大部分工程精力的去处。

一个现实可行的初版

如果明天就要为一个真实收件箱上线这套东西,我会从小而无聊的地方起步:

  1. 一把带 read:emailsend:email 的 API 密钥,限定到一个收件箱。
  2. 每两分钟轮询一次 email_read(action: "list", unread_only: true)
  3. 白名单里只放恰好一个客服别名。
  4. 只用草稿模式——创建草稿,不自动发送任何东西。
  5. 读了一周草稿之后,对那些显而易见的情形开启自动发送,其余的继续起草。

这是一套你真的能信任的系统,而且它能举一反三。同一个循环换个提示词,就变成一个分拣机器人、一个线索路由器,或者一个通知器。想看看智能体接入之后能做的更广菜单,参见 AI 智能体在收件箱访问下能做的 7 件事。如果你还在纠结到底要不要给智能体发送权限,给 AI 智能体邮件访问权限安全吗值得你在上线前花点时间读一读。

准备好动手了吗?创建一把带权限范围的 API 密钥,并在文档里阅读工具参考,或者免费开始——每个套餐都包含无限次工具调用,所以你的回复器唯一的成本,就是先把护栏加上的那份自律。

Asgeir Albretsen
作者
Asgeir Albretsen

Asgeir builds MCPEmails — the bridge that lets AI agents read, search, and send real email over the Model Context Protocol. He writes about agents, email infrastructure, and developer experience.

@mcpemails

为你的智能体接入收件箱

几分钟内将 Gmail、Fastmail 或任意 IMAP 账户连接到你的 AI 智能体。