🚀 快速安装

复制以下命令并运行,立即安装此 Skill:

npx skills add https://skills.sh/resend/resend-skills/agent-email-inbox

💡 提示:需要 Node.js 和 NPM

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 账户?

询问用户:

  • 为智能体创建新账户? → 设置更简单,拥有完整账户权限是可以的
  • 已有包含其他项目的 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.yourdomain.com
在 Resend 控制台中提供
优先级 10(必须是优先级别最低的数字以确保优先使用)

使用子域名(例如,agent.yourdomain.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(`拒绝了来自未授权发件人的邮件:${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);
    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(`拒绝了来自未授权发件人的邮件:${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);
    res.status(400).send('Error');
  }
});

app.get('/', (req, res) => res.send('Agent Email Inbox - Ready'));
app.listen(3000, () => console.log('Webhook 服务器运行在 :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('不能发送到此地址');
  }

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

  if (error) throw new Error(`发送失败:${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=yourcompany.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 技能

📄 原始文档

完整文档(英文):

https://skills.sh/resend/resend-skills/agent-email-inbox

💡 提示:点击上方链接查看 skills.sh 原始英文文档,方便对照翻译。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。