mcpField
R
resend

resend

github.com/vercel-labs测试与质量2.0k228 次安装1 天前更新
代码仓库

关于 resend

resend 是有状态的 Resend Email API 本地模拟器,面向测试邮件工作流的开发者。它不会发送真实邮件,而是保存发送内容,供通过收件箱界面或 REST API 检查。

主要功能

  1. 01在内存中保存发送的邮件
  2. 02通过收件箱界面或 REST 查看邮件
  3. 03模拟域名、API 密钥、受众和联系人
  4. 04支持幂等发送请求
  5. 05测试收件人允许列表和发送尝试

适用场景

  1. 01不发送真实邮件地测试邮件流程
  2. 02在测试中提取验证码或魔法链接
  3. 03检查收件人拒绝与重试行为

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/vercel-labs/emulate --skill resend

安装后在 Claude Code 中输入/resend确认已加载。

详细介绍

Resend 邮件 API 模拟器

完整模拟 Resend API 的状态管理。邮件、域名、API 密钥、受众群体和联系人都会保存在内存中。已发送的邮件会被捕获,并可通过收件箱 UI 或 REST API 查看。

不会发送真实邮件。对 POST /emails 的调用成功后,邮件会保存在本地,方便你通过程序或浏览器检查。通过验证的发送请求也会记录尝试情况,包括收件人被拒绝的情况。

启动

本地监听器默认绑定到 127.0.0.1。如需从容器或其他机器访问,请在 CLI 中传入 --host 0.0.0.0,或在 createEmulator 中设置 hostname: '0.0.0.0'。配置 --base-url 或 baseUrl,以设置可供这些客户端访问的对外 URL。

# Resend only
npx emulate --service resend

# Default port (when run alone)
# http://localhost:4000

或者以编程方式启动:

import { createEmulator } from 'emulate'

const resend = await createEmulator({ service: 'resend', port: 4000 })
// resend.url === 'http://localhost:4000'

身份验证

通过 Authorization: Bearer <token> 传递令牌。任何带有 re_ 前缀的令牌都可以使用。

curl http://localhost:4000/emails \
  -H "Authorization: Bearer re_test_key"

未提供令牌时,请求会回退到默认用户。

将应用指向模拟器

环境变量(Resend SDK)

官方 Resend Node.js SDK 会在模块加载时读取 RESEND_BASE_URL。将其设置为模拟器 URL,SDK 即可在无需更改代码的情况下正常工作:

RESEND_BASE_URL=http://localhost:4000
import { Resend } from 'resend'

// No baseUrl argument needed; the SDK reads RESEND_BASE_URL automatically.
const resend = new Resend('re_test_key')

await resend.emails.send({
  from: '[email protected]',
  to: '[email protected]',
  subject: 'Hello',
  html: '<p>It works!</p>',
})
嵌入 Next.js(adapter-next)

使用 @emulators/adapter-next 时,模拟器会在 Next.js 应用内的 /emulate/resend 路径下运行。通过 next.config.ts 设置 RESEND_BASE_URL:

// next.config.ts
import { withEmulate } from '@emulators/adapter-next'

export default withEmulate({
  env: {
    RESEND_BASE_URL: `http://localhost:${process.env.PORT ?? '3000'}/emulate/resend`,
  },
})
// app/emulate/[...path]/route.ts
import { createEmulateHandler } from '@emulators/adapter-next'
import * as resend from '@emulators/resend'

export const { GET, POST, PUT, PATCH, DELETE } = createEmulateHandler({
  services: {
    resend: {
      emulator: resend,
      seed: {
        domains: [{ name: 'example.com' }],
      },
    },
  },
})
直接使用 fetch

如果无法使用 SDK 或环境变量,请直接调用模拟器:

await fetch('http://localhost:4000/emails', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer re_test_key',
  },
  body: JSON.stringify({
    from: '[email protected]',
    to: '[email protected]',
    subject: 'Hello',
    html: '<p>It works!</p>',
  }),
})

种子配置

resend:
  domains:
    - name: example.com
      region: us-east-1
  contacts:
    - email: [email protected]
      first_name: Test
      last_name: User
      audience: Default

获取已发送的邮件

这是模拟器的主要特点:通过 POST /emails 发送的每封邮件都会被存储并可供查询。

收件箱 UI

在浏览器中查看已发送的邮件:

http://localhost:4000/inbox
REST API
# List all sent emails
curl http://localhost:4000/emails \
  -H "Authorization: Bearer re_test_key"

# Get a single email by ID
curl http://localhost:4000/emails/<id> \
  -H "Authorization: Bearer re_test_key"
从邮件中提取数据(测试、Agent)

可用于以编程方式完成魔法链接、验证码或密码重置流程:

# Get the latest email ID
EMAIL_ID=$(curl -s http://localhost:4000/emails \
  -H "Authorization: Bearer re_test_key" | jq -r '.data[0].id')

# Extract a 6-digit code from the HTML body
CODE=$(curl -s http://localhost:4000/emails/$EMAIL_ID \
  -H "Authorization: Bearer re_test_key" | jq -r '.html' | grep -oE '[0-9]{6}')

echo "Verification code: $CODE"

API 端点

邮件
# Send an email
curl -X POST http://localhost:4000/emails \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"from": "[email protected]", "to": "[email protected]", "subject": "Hello", "html": "<p>Hi</p>"}'

# Send batch (up to 100)
curl -X POST http://localhost:4000/emails/batch \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '[{"from": "[email protected]", "to": "[email protected]", "subject": "One", "html": "<p>1</p>"}]'

# List all emails
curl http://localhost:4000/emails \
  -H "Authorization: Bearer $TOKEN"

# Get email by ID
curl http://localhost:4000/emails/<id> \
  -H "Authorization: Bearer $TOKEN"

# Cancel a scheduled email
curl -X POST http://localhost:4000/emails/<id>/cancel \
  -H "Authorization: Bearer $TOKEN"

支持的字段:from、to、subject、html、text、cc、bcc、reply_to、headers、tags、scheduled_at。

幂等发送

POST /emails 和 POST /emails/batch 接受不区分大小写的 Idempotency-Key 标头。密钥长度必须为 1 到 256 个字符,有效期为 24 小时。使用相同密钥和通过验证的负载重试时,会返回原始响应、邮件 ID 和状态,不会创建邮件,也不会重复派发 email.sent 和 email.delivered Webhook。使用同一密钥但负载或端点不同,会返回 409 invalid_idempotent_request;密钥长度无效时,会返回 400 invalid_idempotency_key。省略该标头则保持正常行为。

curl -X POST http://localhost:4000/emails \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: signup-email-123" \
  -d '{"from": "[email protected]", "to": "[email protected]", "subject": "Hello"}'
收件人限制和发送尝试

在种子配置中设置 resend.allowlist,以测试收件人拒绝行为:

resend:
  allowlist:
    - [email protected]
    - [email protected]

省略 allowlist 时接受所有收件人;allowlist: [] 则拒绝所有收件人。to、cc 和 bcc 中的每个地址都必须列在允许列表中。匹配时会忽略首尾空格和字母大小写;列表项必须是单个地址,不支持通配符或域名匹配。这是一项本地测试控制,与 Resend 的域名验证和账户限制相互独立。

发送被拒绝时,会返回 403 validation_error,并指出被拒绝的地址和配置的允许列表。不会创建邮件 ID、邮件记录、幂等记录或 webhook。如果批次中有任何收件人不被允许,整个批次都会被拒绝。重试被拒绝的请求(即使使用相同的幂等键),也会创建新的尝试。重放已接受的幂等请求时,即使允许列表之后发生了变化,也会返回原始响应。

通过验证的发送会在 resend.send_attempts 存储集合中为每封邮件追加一条记录,包括被拒绝批次中的邮件。每条记录包含 from、to、cc、bcc、subject、idempotency_key(或 null),以及存储中的 id、created_at 和 updated_at。不会将邮件正文复制到尝试记录中。格式错误的请求、无效或冲突的幂等键以及幂等请求重放,都不会追加尝试记录。

使用 @emulators/resend 中的 getResendStore(store).sendAttempts.all() 读取尝试记录,或检查持久化状态。共享适配器快照使用键 resend:resend.send_attempts。尝试记录和收件人限制都会在恢复已保存状态后保留,包括被拒绝的发送。Resend 没有用于查询尝试记录的额外 API 端点。被拒绝的发送有一条尝试记录,但没有邮件;从未发生的发送则两者都没有。

域名
# Create domain
curl -X POST http://localhost:4000/domains \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "example.com", "region": "us-east-1"}'

# List domains
curl http://localhost:4000/domains \
  -H "Authorization: Bearer $TOKEN"

# Get domain
curl http://localhost:4000/domains/<id> \
  -H "Authorization: Bearer $TOKEN"

# Verify domain (instantly marks all records as verified)
curl -X POST http://localhost:4000/domains/<id>/verify \
  -H "Authorization: Bearer $TOKEN"

# Delete domain
curl -X DELETE http://localhost:4000/domains/<id> \
  -H "Authorization: Bearer $TOKEN"
API 密钥
# Create API key
curl -X POST http://localhost:4000/api-keys \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Production"}'

# List API keys
curl http://localhost:4000/api-keys \
  -H "Authorization: Bearer $TOKEN"

# Delete API key
curl -X DELETE http://localhost:4000/api-keys/<id> \
  -H "Authorization: Bearer $TOKEN"
受众
# Create audience
curl -X POST http://localhost:4000/audiences \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Newsletter"}'

# List audiences
curl http://localhost:4000/audiences \
  -H "Authorization: Bearer $TOKEN"

# Delete audience
curl -X DELETE http://localhost:4000/audiences/<id> \
  -H "Authorization: Bearer $TOKEN"
联系人
# Create contact in an audience
curl -X POST http://localhost:4000/audiences/<audience_id>/contacts \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"email": "[email protected]", "first_name": "Jane", "last_name": "Doe"}'

# List contacts in an audience
curl http://localhost:4000/audiences/<audience_id>/contacts \
  -H "Authorization: Bearer $TOKEN"

# Delete contact
curl -X DELETE http://localhost:4000/audiences/<audience_id>/contacts/<id> \
  -H "Authorization: Bearer $TOKEN"

Webhook

状态变更时,模拟器会分发 webhook 事件:

  • 对 POST /emails 分发 email.sent 和 email.delivered
  • 对域名操作分发 domain.created 和 domain.deleted
  • 对联系人操作分发 contact.created 和 contact.deleted

常见模式

魔法链接 / 验证码流程
TOKEN="re_test_key"
BASE="http://localhost:4000"

# 1. Send verification email
curl -X POST $BASE/emails \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"from": "[email protected]", "to": "[email protected]", "subject": "Your code", "html": "<p>Code: <strong>482910</strong></p>"}'

# 2. Retrieve the email
EMAIL_ID=$(curl -s $BASE/emails -H "Authorization: Bearer $TOKEN" | jq -r '.data[0].id')

# 3. Read the HTML body
curl -s $BASE/emails/$EMAIL_ID -H "Authorization: Bearer $TOKEN" | jq -r '.html'
在测试中发送和验证
import { createEmulator } from 'emulate'
import { Resend } from 'resend'

const emu = await createEmulator({ service: 'resend', port: 4000 })

process.env.RESEND_BASE_URL = emu.url
const resend = new Resend('re_test_key')

// Send
await resend.emails.send({
  from: '[email protected]',
  to: '[email protected]',
  subject: 'Verify',
  html: '<p>Code: <strong>123456</strong></p>',
})

// Retrieve
const res = await fetch(`${emu.url}/emails`, {
  headers: { Authorization: 'Bearer re_test_key' },
})
const { data: emails } = await res.json()
console.log(emails[0].html) // contains "123456"

与内置模拟器一起使用自定义模拟器

使用 npx emulate init --custom inventory 搭建第三方 API 模拟器并进行测试。使用 emulate 中的 defineConfig,将其注册到 emulate.config.ts,与 { emulator: "resend" } 等内置条目并列。运行 npx emulate start --watch 以重新加载导入内容,并在打印出的 /_emulate URL 中检查自定义状态。成功重新加载后,会将本次运行重置为种子状态。现有的扁平种子配置仍可使用;--config 可指定显式配置文件。有关编写和测试第三方 API 模拟器的信息,请参阅 https://emulate.dev/docs/custom-emulators。

评论

支持 Markdown