跳转到内容

输入关键词开始搜索文档。

Resend

最后更新 查看 Markdown 源文件@flue/resend

快速开始

通过 Resend 蓝图,将经过验证的入站电子邮件 webhook 和应用自有 API 行为添加到现有 Flue 项目中。在终端或你选择的编码智能体中运行以下命令:

flue add channel resend

概述

该蓝图安装 @flue/resend 和官方 resend@6.12.4 SDK。它创建一个渠道模块,包含命名的 channel 和项目自有的 client 导出。

src/channels/resend.ts(节选)
import { createResendChannel } from '@flue/resend';
import { dispatch, useModel } from '@flue/runtime';
import { Resend } from 'resend';
import { Assistant } from '../agents/assistant.ts';

const EMAIL_INSTANCE_PREFIX = 'resend-email:';

export const client = new Resend(process.env.RESEND_API_KEY!);

export const channel = createResendChannel({
  client,
  webhookSecret: process.env.RESEND_WEBHOOK_SECRET!,
  async webhook({ event, delivery }) {
    switch (event.type) {
      case 'email.received': {
        await dispatch(Assistant, {
          id: emailInstanceId(event.data.email_id),
          message: {
            kind: 'signal',
            type: 'resend.email.received',
            // webhook 仅携带信封数据;智能体通过 retrieve_resend_email 工具
            // 检索完整的电子邮件文本。
            body: event.data.subject,
            attributes: {
              deliveryId: delivery.id,
              emailId: event.data.email_id,
              messageId: event.data.message_id,
              from: event.data.from,
              to: event.data.to.join(', '),
              ...(event.data.cc.length === 0 ? {} : { cc: event.data.cc.join(', ') }),
              ...(event.data.attachments.length === 0
                ? {}
                : { attachmentCount: String(event.data.attachments.length) }),
            },
          },
        });
        return;
      }
      default:
        return;
    }
  },
});

新收到的电子邮件会被接收进绑定到该电子邮件 ID 的智能体;其他事件类型被忽略。完整的生成模块还处理其他电子邮件事件类型,并让绑定的智能体通过 Resend SDK 检索完整的入站电子邮件。

挂载渠道

渠道仅在 app.ts 挂载的位置提供 HTTP 路由。挂载模块中命名导出的 channel:

src/app.ts
import { channel as resend } from './channels/resend.ts';

app.route('/channels/resend', resend.route());

channel.route() 是一个纯路由器工厂,相对于挂载路径提供渠道声明的路由。本指南中的 webhook 路径假定使用常规的 /channels/resend 挂载;不同的挂载路径会相应调整。分发目标智能体模块携带 'use agent' 指令——该指令负责注册,因此仅用于分发的智能体不需要自己的 HTTP 挂载。

配置

变量用途
RESEND_WEBHOOK_SECRET必需 — 验证入站交付。
RESEND_API_KEY必需 — 认证出站 SDK 调用。

它安装 @flue/resend 和官方 resend@6.12.4 SDK。蓝图创建一个渠道模块,导出命名的 channel 和项目自有的 client。

将 webhook URL 配置为:

https://example.com/channels/resend/webhook

Webhook 密钥和出站 API 密钥是单独的凭据。

SDK 的公共声明引用了 Buffer 和 React 电子邮件类型。添加 @types/node 和 @types/react 作为开发依赖。两者都是仅声明性的要求,不会向 Worker 包添加任何 Node 或 React 运行时代码。

渠道模块

src/channels/resend.ts
import { createResendChannel } from '@flue/resend';
import { defineTool, dispatch, useModel } from '@flue/runtime';
import { Resend } from 'resend';
import { Assistant } from '../agents/assistant.ts';

const EMAIL_INSTANCE_PREFIX = 'resend-email:';

export const client = new Resend(process.env.RESEND_API_KEY!);

export const channel = createResendChannel({
  client,
  webhookSecret: process.env.RESEND_WEBHOOK_SECRET!,

  // 路径:/channels/resend/webhook
  async webhook({ event, delivery }) {
    switch (event.type) {
      case 'email.received': {
        await dispatch(Assistant, {
          id: emailInstanceId(event.data.email_id),
          message: {
            kind: 'signal',
            type: 'resend.email.received',
            // webhook 仅携带信封数据;智能体通过 retrieve_resend_email 工具
            // 检索完整的电子邮件文本。
            body: event.data.subject,
            attributes: {
              deliveryId: delivery.id,
              emailId: event.data.email_id,
              messageId: event.data.message_id,
              from: event.data.from,
              to: event.data.to.join(', '),
              ...(event.data.cc.length === 0 ? {} : { cc: event.data.cc.join(', ') }),
              ...(event.data.attachments.length === 0
                ? {}
                : { attachmentCount: String(event.data.attachments.length) }),
            },
          },
        });
        return;
      }
      default:
        return;
    }
  },
});

export function retrieveReceivedEmail(emailId: string) {
  return defineTool({
    name: 'retrieve_resend_email',
    description: '检索已绑定到此智能体的完整入站电子邮件。',
    async run() {
      const result = await client.emails.receiving.get(emailId);
      if (result.error) throw new Error(result.error.message);
      return { output: result.data };
    },
  });
}

export function emailInstanceId(emailId: string): string {
  if (!emailId) throw new TypeError('Resend email id must be non-empty.');
  return `${EMAIL_INSTANCE_PREFIX}${encodeURIComponent(emailId)}`;
}

export function emailIdFromInstanceId(id: string): string {
  if (!id.startsWith(EMAIL_INSTANCE_PREFIX)) {
    throw new TypeError('Expected a local Resend email instance id.');
  }
  const emailId = decodeURIComponent(id.slice(EMAIL_INSTANCE_PREFIX.length));
  if (!emailId) throw new TypeError('Expected a local Resend email instance id.');
  return emailId;
}

@flue/resend 在调用 webhook 之前,将精确的请求体和签名的 svix-id、svix-timestamp 和 svix-signature 值提供给 client.webhooks.verify()。不返回任何内容会产生空的 200。JSON 兼容的值会成为响应体,普通的 Hono 或 Fetch Response 会原样传递。Resend 会重试除 200 以外的每个状态,因此仅当重发是故意的时才返回非 200 响应。

每个经过验证的交付都是官方的 WebhookEventPayload 联合类型,原样转发。每个事件保留其提供者原生的 event.type、created_at 和 data 字段,包括比你安装的 resend 版本更新的事件类型。该渠道永远不会将事件包装在 type: 'unknown' 信封中,因此 switch (event.type) 可以缩窄建模的变体,default 分支处理你的 SDK 之前的任何内容。

检索消息内容

email.received webhook 包含路由元数据和附件描述符。稍后通过项目自有的客户端检索完整的正文、头部和当前附件元数据:

const email = await client.emails.receiving.get(emailId);

当需要附件内容时,使用 client.emails.receiving.attachments 获取签名的下载 URL。仅获取当前应用操作授权的内容,并单独决定什么可以进入模型上下文或持久存储。

绑定工具

src/agents/assistant.ts
'use agent';
import { type AgentProps, useModel, useTool } from '@flue/runtime';
import { emailIdFromInstanceId, retrieveReceivedEmail } from '../channels/resend.ts';

export function Assistant({ id }: AgentProps) {
  useModel('anthropic/claude-haiku-4-5');
  const emailId = emailIdFromInstanceId(id);
  useTool(retrieveReceivedEmail(emailId));
  return '审查入站的支持电子邮件。在需要正文或头部时检索完整的电子邮件。';
}

模型只能检索已被可信应用代码绑定的电子邮件。出站发送、转发或回复工具同样应该在模型选择的参数之外绑定凭据、发件人身份、收件人和消息策略。

resend-email: ID 是单个入站消息的应用约定。该包不暴露对话辅助函数,因为 Resend 的 message_id 标识单个消息而非稳定的线程根。在应用代码中定义和持久化任何回复分组策略。

交付行为

Resend 交付是至少一次的,且不保证顺序。delivery.id 来自 Resend 文档的 svix-id,用于去重。当不允许重复接收时,在分发之前在应用自有的持久存储中声明它。

该渠道是无状态的。它不注册 webhook、管理接收域名或 MX 记录、存储凭据、去重交付、恢复排序、持久化消息、自动检索正文或附件,或发送回复。

Cloudflare Workers

官方 resend@6.12.4 客户端和 webhook 验证器在 Node 和 workerd 中使用 Flue 所需的 nodejs_compat 配置执行。Cloudflare 项目可以通过 process.env 或类型化的 Worker 绑定初始化密钥,然后应验证其完整的 Worker 构建。

使用原始合成正文和本地生成的 Svix 格式 HMAC 签名对精确字节进行入口测试。使用本地假 baseUrl 和拒绝意外目标的 Fetch 存根测试真实客户端。在 Node 和 workerd 中练习两个路径;测试永远不应联系 Resend。

接收域名配置、webhook 注册、API 密钥、签名密钥轮换、去重、持久化、出站邮件和回复行为仍然是应用自有的。

参阅 @flue/resend README。