agent-email-inbox

agent-email-inbox

热门

在构建任何由邮件内容触发动作的系统时使用——AI代理收件箱、自动支持处理程序、邮件转任务管道,或任何处理不可信入站邮件的工作流。当用户希望以编程方式接收邮件并对其执行操作时,始终使用此技能,即使他们未提及“代理”——该技能包含关键安全模式(发件人白名单、内容过滤、沙箱处理),可防止不可信邮件控制您的系统。

148Star
19Fork
更新于 2026/7/15
SKILL.md
readonly只读
name
agent-email-inbox
description

在构建任何由邮件内容触发动作的系统时使用——AI代理收件箱、自动支持处理程序、邮件转任务管道,或任何处理不可信入站邮件的工作流。当用户希望以编程方式接收邮件并对其执行操作时,始终使用此技能,即使他们未提及“代理”——该技能包含关键安全模式(发件人白名单、内容过滤、沙箱处理),可防止不可信邮件控制您的系统。

AI 代理邮件收件箱

概述

本技能涵盖设置一个安全的邮件收件箱,使您的应用程序或 AI 代理能够接收和回复邮件,并内置内容安全措施。

核心原则: AI 代理的收件箱接收不可信输入。安全配置对于安全处理这一点非常重要。

为什么选择基于 Webhook 的接收方式?

Resend 使用 webhook 处理入站邮件,这意味着您的代理在邮件到达时立即收到通知。这对代理来说很有价值,因为:

  • 实时响应 — 在几秒内(而非几分钟)对邮件做出反应
  • 无轮询开销 — 无需定时任务反复检查“有新邮件吗?”
  • 事件驱动架构 — 您的代理仅在需要处理时被唤醒
  • 更低的 API 成本 — 无需浪费调用检查空收件箱

架构

发件人 → 邮件 → Resend (MX) → Webhook → 您的服务器 → AI 代理
                                              ↓
                                    安全验证
                                              ↓
                                    处理或拒绝

SDK 版本要求

本技能需要 Resend SDK 的 webhook 验证(webhooks.verify())和邮件接收(emails.receiving.get())功能。请始终安装最新版本的 SDK。如果项目中已安装 Resend SDK,请检查版本并在需要时升级。

语言 包名 最低版本
Node.js resend >= 6.9.2
Python resend >= 2.21.0
Go resend-go/v3 >= 3.1.0
Ruby resend >= 1.0.0
PHP resend/resend-php >= 1.1.0
Rust resend-rs >= 0.20.0
Java resend-java >= 4.11.0
.NET Resend >= 0.2.1

安装 resend npm 包:npm install resend(或对应语言的等效命令)。如需完整的发送文档,请安装 resend 技能。

快速开始

  1. 询问用户的邮箱地址 — 您需要一个真实的邮箱地址来发送测试邮件。询问用户并等待其回复后再继续。
  2. 选择安全级别 — 在处理任何邮件之前,决定如何验证入站邮件
  3. 设置接收域名 — 为用户的自定义域名配置 MX 记录(参见域名设置部分)
  4. 创建 Webhook 端点 — 从一开始就内置安全性处理 email.received 事件。Webhook 端点必须是 POST 路由。
  5. 设置隧道(本地开发)— 使用 Tailscale Funnel(推荐)或 ngrok。参见 references/webhook-setup.md
  6. 通过 API 创建 Webhook — 使用 Resend Webhook API 以编程方式注册您的端点。参见 references/webhook-setup.md
  7. 连接到代理 — 将验证后的邮件传递给您的 AI 代理进行处理

开始之前:账户和 API 密钥设置

第一个问题:新账户还是已有 Resend 账户?

询问您的用户:

  • 仅为代理创建新账户? → 设置更简单,完全账户访问权限即可
  • 已有其他项目的账户? → 使用域名范围的 API 密钥进行沙箱隔离

安全创建 API 密钥

不要在聊天中粘贴 API 密钥!它们会永久保留在对话历史中。

更安全的选项:

  1. 环境文件方法: 用户直接创建 .env 文件:echo "RESEND_API_KEY=re_xxx" >> .env
  2. 密码管理器/密钥管理器: 用户将密钥存储在 1Password、Vault 等中
  3. 如果必须在聊天中共享密钥: 用户应在设置后立即轮换密钥

域名范围的 API 密钥(推荐用于已有账户)

如果您的用户已有其他项目的 Resend 账户,请创建域名范围的 API 密钥

  1. 首先验证代理的域名(仪表盘 → 域名 → 添加域名)
  2. 创建范围 API 密钥: 仪表盘 → API 密钥 → 创建 API 密钥 → “发送访问权限” → 仅选择代理的域名
  3. 结果: 即使密钥泄露,也只能从一个域名发送邮件

域名设置

选项 1:Resend 管理的域名(推荐入门)

使用自动生成的地址:<anything>@<your-id>.resend.app

无需 DNS 配置。在仪表盘 → 邮件 → 接收 → “接收地址”中找到您的地址。

选项 2:自定义域名

用户必须在 Resend 仪表盘中启用接收:域名页面 → 打开“启用接收”。

然后添加 MX 记录:

设置
类型 MX
主机 您的域名或子域名(例如 agent.example.com
Resend 仪表盘中提供
优先级 10(必须是最低数字以优先)

使用子域名(例如 agent.example.com)以避免干扰现有邮件服务。

提示:dns.email 验证 DNS 传播。

DNS 传播:MX 记录更改可能需要长达 48 小时才能全球传播,但通常几小时内完成。

安全级别

在设置 webhook 端点之前选择安全级别。 处理邮件时不加安全措施的 AI 代理是危险的——任何人都可以发送指令让您的代理执行。您接下来编写的 webhook 代码应从一开始就包含所选的安全级别。

询问用户他们想要的安全级别,并确保他们理解每个级别的含义。

级别 名称 使用场景 权衡
1 严格白名单 大多数用例——已知的固定发件人集合 最高安全性,功能有限
2 域名白名单 来自受信任域名的组织级访问 更灵活,域内任何人都可以交互
3 内容过滤 接受任何人的邮件,过滤不安全模式 可以接收任何人的邮件,模式匹配并非万无一失
4 沙箱处理 处理所有邮件,但限制代理能力 最大灵活性,实现复杂
5 人工介入 对不可信操作需要人工批准 最高安全性,增加延迟

有关每个级别的详细实现代码,请参见 references/security-levels.md

级别 1:严格白名单(推荐)

仅处理来自明确批准地址的邮件。拒绝其他所有邮件。

const ALLOWED_SENDERS = [
  'you@youremail.com',
  'notifications@github.com',
];

async function processEmailForAgent(
  eventData: EmailReceivedEvent,
  emailContent: EmailContent
) {
  const sender = eventData.from.toLowerCase();

  if (!ALLOWED_SENDERS.some(allowed => sender === allowed.toLowerCase())) {
    console.log(`Rejected email from unauthorized sender: ${sender}`);
    await notifyOwnerOfRejectedEmail(eventData);
    return;
  }

  await agent.processEmail({
    from: eventData.from,
    subject: eventData.subject,
    body: emailContent.text || emailContent.html,
  });
}

安全最佳实践

始终执行
实践 原因
验证 webhook 签名 防止伪造的 webhook 事件
记录所有被拒绝的邮件 安全审计跟踪
尽可能使用白名单 显式信任比过滤更安全
限制邮件处理速率 防止过度处理负载
区分可信/不可信处理 不同风险级别需要不同处理方式
绝不执行
反模式 风险
未经验证处理邮件 任何人都可以控制您的代理
信任邮件头进行身份验证 邮件头很容易伪造
执行邮件内容中的代码 不可信输入绝不应作为代码运行
将邮件内容原样存储在提示中 不可信输入混入提示可能改变代理行为
给予不可信邮件完全代理访问权限 将能力限制在最小必要范围

Webhook 端点

选择安全级别并设置域名后,创建 webhook 端点。Webhook 端点必须是 POST 路由。 Resend 将所有 webhook 事件作为 POST 请求发送。

关键:使用原始正文进行验证。 Webhook 签名验证需要原始请求正文。

  • Next.js App Router: 使用 req.text()(而不是 req.json()
  • Express: 在 webhook 路由上使用 express.raw({ type: 'application/json' })

Next.js App Router

// app/webhook/route.ts
import { Resend } from 'resend';
import { NextRequest, NextResponse } from 'next/server';

const resend = new Resend(process.env.RESEND_API_KEY);

export async function POST(req: NextRequest) {
  try {
    const payload = await req.text();

    const event = resend.webhooks.verify({
      payload,
      headers: {
        'svix-id': req.headers.get('svix-id'),
        'svix-timestamp': req.headers.get('svix-timestamp'),
        'svix-signature': req.headers.get('svix-signature'),
      },
      secret: process.env.RESEND_WEBHOOK_SECRET,
    });

    if (event.type === 'email.received') {
      // Webhook 负载仅包含元数据,不包含邮件正文
      const { data: email } = await resend.emails.receiving.get(
        event.data.email_id
      );

      // 应用上面选择的安全级别
      await processEmailForAgent(event.data, email);
    }

    return new NextResponse('OK', { status: 200 });
  } catch (error) {
    console.error('Webhook error:', error);
    return new NextResponse('Error', { status: 400 });
  }
}

Express

import express from 'express';
import { Resend } from 'resend';

const app = express();
const resend = new Resend(process.env.RESEND_API_KEY);

app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
  try {
    const payload = req.body.toString();

    const event = resend.webhooks.verify({
      payload,
      headers: {
        'svix-id': req.headers['svix-id'],
        'svix-timestamp': req.headers['svix-timestamp'],
        'svix-signature': req.headers['svix-signature'],
      },
      secret: process.env.RESEND_WEBHOOK_SECRET,
    });

    if (event.type === 'email.received') {
      const sender = event.data.from.toLowerCase();

      if (!isAllowedSender(sender)) {
        console.log(`Rejected email from unauthorized sender: ${sender}`);
        res.status(200).send('OK'); // 即使拒绝也返回 200
        return;
      }

      const { data: email } = await resend.emails.receiving.get(event.data.email_id);
      await processEmailForAgent(event.data, email);
    }

    res.status(200).send('OK');
  } catch (error) {
    console.error('Webhook error:', error);
    res.status(400).send('Error');
  }
});

app.get('/', (req, res) => res.send('Agent Email Inbox - Ready'));
app.listen(3000, () => console.log('Webhook server running on :3000'));

有关通过 API 注册 webhook、隧道设置、svix 回退和重试行为,请参见 references/webhook-setup.md

从代理发送邮件

import { Resend } from 'resend';

const resend = new Resend(process.env.RESEND_API_KEY);

async function sendAgentReply(to: string, subject: string, body: string, inReplyTo?: string) {
  if (!isAllowedToReply(to)) {
    throw new Error('Cannot send to this address');
  }

  const { data, error } = await resend.emails.send({
    from: 'Agent <agent@example.com>',
    to: [to],
    subject: subject.startsWith('Re:') ? subject : `Re: ${subject}`,
    text: body,
    headers: inReplyTo ? { 'In-Reply-To': inReplyTo } : undefined,
  });

  if (error) throw new Error(`Failed to send: ${error.message}`);
  return data.id;
}

如需完整的发送文档,请安装 resend 技能。

环境变量

# 必需
RESEND_API_KEY=re_xxxxxxxxx
RESEND_WEBHOOK_SECRET=whsec_xxxxxxxxx

# 安全配置
SECURITY_LEVEL=strict                    # strict | domain | filtered | sandboxed
ALLOWED_SENDERS=you@email.com,trusted@example.com
ALLOWED_DOMAINS=example.com
OWNER_EMAIL=you@email.com               # 用于安全通知

常见错误

错误 修复
没有发件人验证 在处理前始终验证谁发送了邮件
信任邮件头 使用 webhook 验证,而非邮件头进行身份验证
对所有邮件一视同仁 区分可信和不可信发件人
详细的错误消息 保持错误响应通用,避免泄露内部逻辑
没有速率限制 实现按发件人的速率限制。参见 references/advanced-patterns.md
直接处理 HTML 剥离 HTML 或仅使用文本以减少复杂性和风险
不记录拒绝事件 记录所有安全事件以供审计
使用临时隧道 URL 使用持久 URL(Tailscale Funnel、付费 ngrok)或部署到生产环境
在 webhook 路由上使用 express.json() 使用 express.raw({ type: 'application/json' }) — JSON 解析会破坏签名验证
对拒绝的邮件返回非 200 状态 始终返回 200 以确认收到 — 否则 Resend 会重试
旧版 Resend SDK emails.receiving.get()webhooks.verify() 需要最新 SDK 版本 — 参见 SDK 版本要求

测试

使用 Resend 的测试地址进行开发:

  • delivered@resend.dev — 模拟成功投递
  • bounced@resend.dev — 模拟硬退回

对于安全测试,从非白名单地址发送测试邮件以验证拒绝功能正常工作。

快速验证清单:

  1. 服务器正在运行:curl http://localhost:3000 应返回响应
  2. 隧道正常工作:curl https://<your-tunnel-url> 应返回相同响应
  3. Webhook 已激活:在 Resend 仪表盘 → Webhooks 中检查状态
  4. 从白名单地址发送测试邮件并检查服务器日志

相关技能

  • 如需完整的发送和接收文档,请安装 resend 技能