Skip to content

feat: add resend service emulator#7

Merged
ctate merged 2 commits intovercel-labs:mainfrom
mvanhorn:feat/resend-emulator
Mar 31, 2026
Merged

feat: add resend service emulator#7
ctate merged 2 commits intovercel-labs:mainfrom
mvanhorn:feat/resend-emulator

Conversation

@mvanhorn
Copy link
Copy Markdown
Contributor

Summary

Adds a Resend email API emulator with a web inbox UI for viewing sent emails locally. Emails, domains, API keys, audiences, and contacts are all stateful and persist in memory.

Why this matters

Every Next.js SaaS app sends transactional email (signup, password reset, receipts). Resend is the standard choice in the Vercel ecosystem - it's in vercel-labs/workflow-builder-template (1,074 stars) and has deep React Email integration.

Currently developers either use real API keys in dev (costs money, sends real emails) or skip email testing entirely. The only local tool is resend-local (11 stars) which mocks only the send endpoint. Resend's SDK supports RESEND_BASE_URL for drop-in emulator redirect.

Changes

New package: @internal/resend (~500 lines across 14 files)

Endpoints:

  • POST /emails - send email, validate required fields, dispatch email.sent + email.delivered webhooks
  • POST /emails/batch - send up to 100 emails
  • GET /emails / GET /emails/:id - list and retrieve with Resend's cursor pagination format
  • POST /emails/:id/cancel - cancel scheduled emails
  • Domain CRUD with auto-generated DNS records and instant verify
  • API key CRUD with re_ prefix tokens
  • Audience + contact CRUD with webhook dispatch

Inbox UI at /inbox - the differentiator:

Inbox

Email detail

Uses only core CSS classes (zero inline styles). HTML email preview rendered in a sandboxed iframe.

Integration: All 7 points wired in start.ts + list.ts SERVICE_DESCRIPTIONS updated.

Testing

23 tests covering all endpoints, error format ({ statusCode, name, message }), batch send, cursor pagination, inbox UI rendering, and seed config.

Dogfooding

Ran the emulator, sent 3 test emails (welcome, invoice, password reset), verified inbox shows from/to/subject/status, clicked through to detail page with HTML preview. Screenshots above are from the running emulator.

This contribution was developed with AI assistance (Claude Code).

@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Mar 23, 2026

@mvanhorn is attempting to deploy a commit to the Vercel Labs Team on Vercel.

A member of the Team first needs to authorize it.

@jonmumm jonmumm mentioned this pull request Mar 24, 2026
3 tasks
Copy link
Copy Markdown
Collaborator

@ctate ctate left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this excellent contribution! The Resend emulator follows existing service patterns really well, the inbox UI is a great differentiator, and all 23 tests pass cleanly. A few small things before merging:

  1. Remove unused resendId()helpers.ts:9-11 defines resendId() as an alias for generateUuid() but it's never called anywhere. Let's remove the dead code.

  2. Type the as any castsroutes/emails.ts:137 and routes/domains.ts:93 use as any in update() calls, and formatEmail/formatDomain use (email: any) params. Would be good to use the proper entity types (ResendEmail, ResendDomain) instead.

None of these are blocking — happy to merge once addressed. Great work!

@mvanhorn
Copy link
Copy Markdown
Contributor Author

Addressed in 2764090:

  • Removed unused resendId() from helpers.ts
  • Replaced both as any casts with properly typed Partial<T> objects (removed the unnecessary as const assertions that were causing the type mismatch)

Rebased onto main and migrated from @internal to @emulators scope.
Registered resend in SERVICE_REGISTRY. All 23 resend tests + full
suite passing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@mvanhorn mvanhorn force-pushed the feat/resend-emulator branch from 2764090 to 9993e06 Compare March 27, 2026 04:06
@ctate
Copy link
Copy Markdown
Collaborator

ctate commented Mar 31, 2026

Thanks for the updates @mvanhorn

Add okta to SERVICE_NAME_LIST alongside resend. Regenerate lockfile.
@ctate ctate merged commit 6df5a8b into vercel-labs:main Mar 31, 2026
4 of 5 checks passed
ctate added a commit to mvanhorn/emulate that referenced this pull request Mar 31, 2026
Merge upstream/main into feat/stripe-emulator, incorporating the
resend emulator (vercel-labs#7) and microsoft v1 OAuth updates (vercel-labs#30) alongside
the stripe emulator in the service registry and package dependencies.
@mvanhorn
Copy link
Copy Markdown
Contributor Author

yay

This was referenced Apr 1, 2026
@mvanhorn
Copy link
Copy Markdown
Contributor Author

mvanhorn commented Apr 4, 2026

Thanks for merging both emulators!

@mvanhorn
Copy link
Copy Markdown
Contributor Author

mvanhorn commented Apr 6, 2026

Thanks for the quick merge!

@mvanhorn
Copy link
Copy Markdown
Contributor Author

mvanhorn commented Apr 7, 2026

Thanks for merging both, @ctate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants