resend

bởi vercel

API email Resend mô phỏng cho phát triển và kiểm thử cục bộ. Sử dụng khi người dùng cần gửi email cục bộ, kiểm thử luồng email giao dịch, triển khai magic…

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

Resend Email API Emulator

Fully stateful Resend API emulation. Emails, domains, API keys, audiences, and contacts persist in memory. Sent emails are captured and viewable through the inbox UI or the REST API.

No real emails are sent. Every call to POST /emails stores the message locally so you can inspect it programmatically or in the browser.

Start

# Resend only
npx emulate --service resend

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

Or programmatically:

import { createEmulator } from 'emulate'

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

Auth

Pass tokens as Authorization: Bearer <token>. Any re_ prefixed token is accepted.

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

When no token is provided, requests fall back to the default user.

Pointing Your App at the Emulator

Environment Variable (Resend SDK)

The official Resend Node.js SDK reads RESEND_BASE_URL at module load time. Set it to the emulator URL and the SDK works without any code changes:

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: 'hello@example.com',
  to: 'user@example.com',
  subject: 'Hello',
  html: '<p>It works!</p>',
})

Embedded in Next.js (adapter-next)

When using @emulators/adapter-next, the emulator runs inside your Next.js app at /emulate/resend. Set RESEND_BASE_URL via next.config.ts:

// 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' }],
      },
    },
  },
})

Direct fetch

If you cannot use the SDK or env var, call the emulator directly:

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

Seed Config

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

Retrieving Sent Emails

This is the key differentiator of the emulator: every email sent via POST /emails is stored and queryable.

Inbox UI

Browse sent emails in the browser:

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"

Extracting Data from Emails (tests, agents)

Useful for completing magic link, verification code, or password reset flows programmatically:

# 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 Endpoints

Emails

# Send an email
curl -X POST http://localhost:4000/emails \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"from": "hello@example.com", "to": "user@example.com", "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": "a@example.com", "to": "b@example.com", "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"

Supported fields: from, to, subject, html, text, cc, bcc, reply_to, headers, tags, scheduled_at.

Idempotent Sends

POST /emails and POST /emails/batch accept the case-insensitive Idempotency-Key header. Keys must be 1 to 256 characters and remain active for 24 hours. A retry with the same key and validated payload returns the original response, email IDs, and status without creating emails or dispatching duplicate email.sent and email.delivered webhooks. Reusing a key with a different payload or endpoint returns 409 invalid_idempotent_request; invalid key lengths return 400 invalid_idempotency_key. Omitting the header preserves normal behavior.

curl -X POST http://localhost:4000/emails \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: signup-email-123" \
  -d '{"from": "hello@example.com", "to": "user@example.com", "subject": "Hello"}'

Domains

# 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 Keys

# 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"

Audiences

# 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"

Contacts

# 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": "user@example.com", "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"

Webhooks

The emulator dispatches webhook events when state changes:

  • email.sent and email.delivered on POST /emails
  • domain.created and domain.deleted on domain operations
  • contact.created and contact.deleted on contact operations

Common Patterns

Magic Link / Verification Code Flow

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": "auth@example.com", "to": "user@example.com", "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'

Send and Verify in a Test

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: 'auth@example.com',
  to: 'user@test.com',
  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"

Thêm skills từ vercel

vercel
vercel
API REST Vercel được mô phỏng để phát triển và kiểm thử cục bộ. Sử dụng khi người dùng cần tương tác với các điểm cuối API Vercel tại chỗ, kiểm thử tích hợp Vercel,…
cron-jobs
vercel
Cấu hình và các phương pháp tốt nhất cho Vercel Cron Jobs. Sử dụng khi thêm, chỉnh sửa hoặc gỡ lỗi các tác vụ đã lên lịch trong vercel.json.
codegen
vercel
Các tiện ích tạo mã cho json-render. Sử dụng khi tạo mã từ đặc tả giao diện người dùng, xây dựng trình xuất mã tùy chỉnh, duyệt qua các đặc tả hoặc tuần tự hóa các thuộc tính cho…
next-best-practice
vercel
Các phương pháp hay nhất của Next.js - quy ước tệp, ranh giới RSC, mẫu dữ liệu, API bất đồng bộ, siêu dữ liệu, xử lý lỗi, trình xử lý tuyến đường, tối ưu hóa hình ảnh/phông chữ,…
benchmark-sandbox
vercel
Chạy các kịch bản đánh giá vercel-plugin trong Vercel Sandboxes thay vì các bảng WezTerm cục bộ. Cung cấp các microVM tạm thời với Claude Code và plugin được cài đặt sẵn,…
write-guide
vercel
Tạo một hướng dẫn kỹ thuật dạy một trường hợp sử dụng thực tế thông qua các ví dụ tiến dần. Các khái niệm chỉ được giới thiệu khi người đọc cần đến chúng.
benchmark-testing
vercel
Tạo và khởi chạy các dự án kiểm thử benchmark để thực thi việc tiêm kỹ năng vercel-plugin qua các kịch bản thực tế. Thiết lập các thư mục độc lập, cài đặt…
ai-gateway
vercel
Hướng