Email Integration (Resend + React Email)
Expert guidance for sending emails from Vercel-deployed applications using Resend (native Vercel Marketplace integration) and React Email templates. Covers SDK setup, transactional sends, domain verification, batch sending, broadcasts, webhooks, idempotency, and template patterns.
When to Use
Activate this skill when any of the following are true:
- You are building an email-sending feature in a Next.js / Vercel application.
- You are installing or configuring the
resendSDK orreact-email/@react-email/componentspackages. - You need to create or modify React Email templates (files under
emails/,src/emails/, orcomponents/emails/). - You are building API routes for sending emails (paths matching
app/api/send/**,app/api/email/**,app/api/emails/**). - You need to set up Resend domain verification, webhooks, broadcasts, or batch sending.
- You see a
lib/resend.*orlib/email*file being created or modified.
Prerequisites
- A Vercel project with Next.js (App Router recommended).
- Node.js 18+ installed on the development machine.
- On Windows (primary host), use PowerShell for all CLI commands. Paths use backslashes locally (e.g.,
~\project\lib\resend.ts), but project-internal import paths use forward slashes. - A Resend account (free tier available) or Vercel Marketplace access.
- If sending from a custom domain, DNS access to your domain provider for MX, SPF, and DKIM records.
Procedure
1. Install Resend via Vercel Marketplace (Recommended)
Resend is a native Vercel Marketplace integration with auto-provisioned API keys and unified billing.
# Install Resend from Vercel Marketplace (auto-provisions env vars)
vercel integration add resend
This auto-provisions the RESEND_API_KEY environment variable on your Vercel project. Pull it locally:
vercel env pull .env.local
2. Install SDK Packages
# Install the Resend SDK
npm install resend
# Install React Email for building templates
npm install react-email @react-email/components
SDK version note: Current Resend SDK version is 6.9.x (actively maintained, ~1.6M weekly downloads). React Email latest is 5.2.9 with
@react-email/components1.0.8. React Email 5.x supports React 19.2 and Next.js 16.
3. Initialize the Resend Client
Create the client singleton so it can be imported across API routes and server actions:
// lib/resend.ts
import { Resend } from "resend";
export const resend = new Resend(process.env.RESEND_API_KEY);
4. Create a Basic Send API Route
// app/api/send/route.ts
import { NextResponse } from "next/server";
import { resend } from "@/lib/resend";
export async function POST(req: Request) {
const { to, subject, html } = await req.json();
const { data, error } = await resend.emails.send({
from: "Your App <hello@yourdomain.com>",
to,
subject,
html,
});
if (error) {
return NextResponse.json({ error }, { status: 400 });
}
return NextResponse.json({ id: data?.id });
}
5. Create React Email Templates
Organize templates in an emails/ directory at the project root:
emails/
welcome.tsx
invoice.tsx
reset-password.tsx
Example template:
// emails/welcome.tsx
import {
Body,
Container,
Head,
Heading,
Html,
Link,
Preview,
Text,
} from "@react-email/components";
interface WelcomeEmailProps {
name: string;
}
export default function WelcomeEmail({ name }: WelcomeEmailProps) {
return (
<Html>
<Head />
<Preview>Welcome to our platform</Preview>
<Body style={{ fontFamily: "sans-serif", backgroundColor: "#f6f9fc" }}>
<Container style={{ padding: "40px 20px", maxWidth: "560px" }}>
<Heading>Welcome, {name}!</Heading>
<Text>
Thanks for signing up. Get started by visiting your{" "}
<Link href="https://yourdomain.com/dashboard">dashboard</Link>.
</Text>
</Container>
</Body>
</Html>
);
}
6. Send with a React Email Template
// app/api/send/route.ts
import { NextResponse } from "next/server";
import { resend } from "@/lib/resend";
import WelcomeEmail from "@/emails/welcome";
export async function POST(req: Request) {
const { name, email } = await req.json();
const { data, error } = await resend.emails.send({
from: "Your App <hello@yourdomain.com>",
to: email,
subject: "Welcome!",
react: WelcomeEmail({ name }),
});
if (error) {
return NextResponse.json({ error }, { status: 400 });
}
return NextResponse.json({ id: data?.id });
}
7. Preview Templates Locally
# Start the React Email dev server to preview templates
npx react-email dev
This opens a browser preview at http://localhost:3000 with hot reload for iterating on email templates.
8. Upload Templates to Resend (React Email 5.0+)
# Upload templates directly from the CLI
npx react-email@latest resend setup
Paste your API key when prompted — templates are uploaded and available in the Resend dashboard.
9. Dark Mode Support (React Email 5.x)
React Email 5.x supports dark mode with a theming system tested across popular email clients. Use the Tailwind component with Tailwind CSS v4:
import { Tailwind } from "@react-email/components";
export default function MyEmail() {
return (
<Tailwind>
<div className="bg-white dark:bg-gray-900 text-black dark:text-white">
<h1>Hello</h1>
</div>
</Tailwind>
);
}
Upgrade note (v4 → v5): Replace all
renderAsyncwithrender. The Tailwind component now only supports Tailwind CSS v4.
10. Domain Verification
To send from a custom domain (not onboarding@resend.dev):
- Go to Resend Domains.
- Add your domain.
- Add the DNS records (MX, SPF, DKIM) to your domain provider.
- Wait for verification (usually under 5 minutes).
Until your domain is verified, use onboarding@resend.dev as the from address for testing.
11. Batch Sending
const { data, error } = await resend.batch.send([
{
from: "hello@yourdomain.com",
to: "user1@example.com",
subject: "Update",
html: "<p>Content for user 1</p>",
},
{
from: "hello@yourdomain.com",
to: "user2@example.com",
subject: "Update",
html: "<p>Content for user 2</p>",
},
]);
12. Server Action Pattern
"use server";
import { resend } from "@/lib/resend";
import WelcomeEmail from "@/emails/welcome";
export async function sendWelcomeEmail(name: string, email: string) {
const { error } = await resend.emails.send({
from: "Your App <hello@yourdomain.com>",
to: email,
subject: "Welcome!",
react: WelcomeEmail({ name }),
});
if (error) throw new Error("Failed to send email");
}
13. Broadcast API (February 2026)
Send emails to audiences (mailing lists) managed in Resend:
// Send a broadcast to an audience
const { data, error } = await resend.broadcasts.send({
audienceId: "aud_1234",
from: "updates@yourdomain.com",
subject: "Monthly Newsletter",
react: NewsletterEmail({ month: "March" }),
});
// Create and manage broadcasts programmatically
const broadcast = await resend.broadcasts.create({
audienceId: "aud_1234",
from: "updates@yourdomain.com",
subject: "Product Update",
react: ProductUpdateEmail(),
});
// Schedule for later
await resend.broadcasts.send({
broadcastId: broadcast.data?.id,
scheduledAt: "2026-03-15T09:00:00Z",
});
14. Idempotency Keys
Prevent duplicate sends on retries by passing an Idempotency-Key header:
const { data, error } = await resend.emails.send(
{
from: "hello@yourdomain.com",
to: "user@example.com",
subject: "Order Confirmation",
react: OrderConfirmation({ orderId: "ord_123" }),
},
{
headers: {
"Idempotency-Key": `order-confirmation-ord_123`,
},
}
);
Resend deduplicates requests with the same idempotency key within a 24-hour window. Use deterministic keys derived from your business logic (e.g., order-confirmation-${orderId}).
15. Webhook Management API
Create and manage webhooks programmatically instead of through the dashboard:
// Create a webhook endpoint
const { data } = await resend.webhooks.create({
url: "https://yourdomain.com/api/webhook/resend",
events: ["email.delivered", "email.bounced", "email.complained", "email.suppressed"],
});
// List all webhooks
const webhooks = await resend.webhooks.list();
// Delete a webhook
await resend.webhooks.remove(webhookId);
16. Webhook for Delivery Events
// app/api/webhook/resend/route.ts
import { NextResponse } from "next/server";
export async function POST(req: Request) {
const event = await req.json();
switch (event.type) {
case "email.delivered":
// Track successful delivery
break;
case "email.bounced":
// Handle bounce — remove from mailing list
break;
case "email.complained":
// Handle spam complaint — unsubscribe user
break;
}
return NextResponse.json({ received: true });
}
Environment Variables
| Variable | Scope | Description |
|---|---|---|
RESEND_API_KEY |
Server | Resend API key (starts with re_). Never expose to the client. Use YOUR_KEY as placeholder in examples. |
Pitfalls
- Never expose
RESEND_API_KEYto the client. Only use it in server-side code (API routes, server actions, server components). Do not prefix withNEXT_PUBLIC_. - Unverified domain sends will fail. Until your custom domain is verified in Resend, use
onboarding@resend.devas thefromaddress. Sending from an unverified domain returns an error. - React Email v4 → v5 breaking changes. Replace all
renderAsynccalls withrender. TheTailwindcomponent only supports Tailwind CSS v4 in v5. If you are on Tailwind v3, you must upgrade or use inline styles instead. react-email devport conflict. The preview server defaults tohttp://localhost:3000. If your Next.js dev server is already running on port 3000, specify a different port:npx react-email dev --port 3001.- Batch send limits. Resend enforces rate limits on batch sends. Check current limits in the Resend dashboard for your plan tier before sending large batches.
- Idempotency key uniqueness. Keys must be deterministic and unique per logical send. Reusing a key for different content will return the cached original response, not the new content.
"suppressed"delivery status. Resend tracks a"suppressed"status for recipients on suppression lists (previous hard bounces or spam complaints). Check for this in webhook events alongsidedelivered/bounced/complained. Suppressed recipients will not receive emails even if you send to them.- Webhook signature verification. Always verify the Resend webhook signature in production. The example route above omits verification for brevity — do not deploy to production without it.
- Windows path separators. When referencing files in PowerShell, use backslashes. Import paths in TypeScript/JavaScript always use forward slashes (e.g.,
@/lib/resend).
Verification
Verify the SDK is installed:
npm ls resendExpected output should show
resend@6.x.xinstalled.Verify environment variable is set:
# On Windows PowerShell echo $env:RESEND_API_KEYOr check
.env.localcontains a line like:RESEND_API_KEY=re_YOUR_KEYTest a send via API route:
# Start dev server npm run devThen in another terminal:
# Send a test email curl -X POST http://localhost:3000/api/send ` -H "Content-Type: application/json" ` -d '{\"to\":\"test@example.com\",\"subject\":\"Test\",\"html\":\"<p>Hello</p>\"}'Expected response:
{"id":"<email-id>"}with HTTP 200.Verify React Email preview:
npx react-email devOpen
http://localhost:3000(or your configured port) and confirm templates render correctly.Verify domain status:
Check Resend Domains — your domain should show a green "Verified" badge before sending from it in production.
Related Skills
- Marketplace install and env var provisioning →
⤳ skill: marketplace - API route patterns →
⤳ skill: routing-middleware - Environment variable management →
⤳ skill: env-vars - Serverless function config →
⤳ skill: vercel-functions