resend

작성자: vercel

로컬 개발 및 테스트를 위한 Resend 이메일 API 에뮬레이션. 사용자가 로컬에서 이메일을 보내거나, 트랜잭션 이메일 흐름을 테스트하거나, 매직 링크를 구현해야 할 때 사용합니다.

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.

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"

vercel의 다른 스킬

benchmark-sandbox
vercel
Vercel Sandbox에서 vercel-plugin eval 시나리오를 로컬 WezTerm 패널 대신 실행합니다. Claude Code와 플러그인이 사전 설치된 임시 마이크로VM을 프로비저닝합니다.
official
emil-design-eng
vercel
이 스킬은 Emil Kowalski의 UI 폴리시, 컴포넌트 디자인, 애니메이션 결정, 그리고 소프트웨어를 훌륭하게 만드는 보이지 않는 세부 사항에 대한 철학을 인코딩합니다.
official
vercel-react-best-practices
vercel
Vercel Engineering의 React 및 Next.js 성능 최적화 가이드라인입니다. 이 스킬은 React/Next.js 코드를 작성, 검토 또는 리팩토링할 때 사용해야 합니다.
official
vercel-react-best-practices
vercel
Vercel Engineering의 React 및 Next.js 성능 최적화 가이드라인입니다. 이 스킬은 React/Next.js 코드를 작성, 검토 또는 리팩토링할 때 사용해야 합니다.
official
write-guide
vercel
점진적인 예제를 통해 실제 사용 사례를 가르치는 기술 가이드를 제작합니다. 개념은 독자가 필요로 할 때만 소개됩니다.
official
release
vercel
Vercel-plugin 릴리스 — 게이트 실행, 버전 업, 아티팩트 생성, 커밋 및 푸시. "릴리스", "배포", "버전 업 및 푸시", "릴리스 생성" 요청 시 사용.
official
deepsec
vercel
dev3000에서 체크아웃한 Vercel 프로젝트에 대해 DeepSec을 실행합니다. 원클릭 DeepSec 설정, 프로젝트 컨텍스트 부트스트래핑, 제한된 1차 처리 등에 사용합니다.
official
backport-pr
vercel
병합된 Next.js 풀 리퀘스트를 canary에서 next-16-2와 같은 이전 릴리스 브랜치로 백포트합니다. 사용자가 백포트, 체리픽 또는 열기를 요청할 때 사용합니다…
official