resend
resend — a Claude Code skill from vercel-labs/emulate
Features
- ✓Local CI and sandbox services
- ✓Zero-config setup for common services
- ✓Customizable ports and seed configurations
- ✓Programmatic API for service emulation
- ✓Vitest / Jest integration support
- ✓Visit GitHub for more details
Installation Guide
npm install @emulators/adapter-next @emulators/github @emulators/googleOnly install the emulators you need. Each @emulators/* package is published independently.
Route handler
Create a catch-all route that serves emulator traffic:
// app/emulate/[...path]/route.ts
import { createEmulateHandler } from '@emulators/adapter-next'
import * as github from '@emulators/github'
import * as google from '@emulators/google'
export const { GET, POST, PUT, PATCH, DELETE } = createEmulateHandler({
services: {
github: {
emulator: github,
seed: {
users: [{ login: 'octocat', name: 'The Octocat' }],
repos: [{ owner: 'octocat', name: 'hello-world', auto_init: true }],
},
},
google: {
emulator: google,
seed: {
users: [{ email: 'test@example.com', name: 'Test User' }],
},
},
},
})Auth.js / NextAuth configuration
Point your provider at the emulator paths on the same origin:
import GitHub from 'next-auth/providers/github'
const baseUrl = process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}`
: 'http://localhost:3000'
GitHub({
clientId: 'any-value',
clientSecret: 'any-value',
authorization: { url: `${baseUrl}/emulate/github/login/oauth/authorize` },
token: { url: `${baseUrl}/emulate/github/login/oauth/access_token` },
userinfo: { url: `${baseUrl}/emulate/github/user` },
})No oauth_apps need to be seeded. When none are configured, the emulator skips client_id, client_secret, and redirect_uri validation.
Font files in serverless
Emulator UI pages use bundled fonts. Wrap your Next.js config to include them in the serverless trace:
// next.config.mjs
import { withEmulate } from '@emulators/adapter-next'
export default withEmulate({
// your normal Next.js config
})If you mount the catch-all at a custom path, pass the matching prefix:
export default withEmulate(nextConfig, { routePrefix: '/api/emulate' })Persistence
By default, emulator state is in-memory and resets on every cold start. To persist state across restarts, pass a persistence adapter:
import { createEmulateHandler } from '@emulators/adapter-next'
import * as github from '@emulators/github'
const kvAdapter = {
async load() { return await kv.get('emulate-state') },
async save(data: string) { await kv.set('emulate-state', data) },
}
export const { GET, POST, PUT, PATCH, DELETE } = createEmulateHandler({
services: { github: { emulator: github } },
persistence: kvAdapter,
})For local development, @emulators/core ships filePersistence:
import { filePersistence } from '@emulators/core'
// ...
persistence: filePersistence('.emulate/state.json'),The persistence adapter is called on cold start (load) and after every mutating request (save). Saves are serialized via an internal queue to prevent race conditions.