How to Send Transactional Emails with Resend
How to Send Transactional Emails with Resend
Resend is the modern developer-first email API. Built by the creators of React Email, it's designed for developers who want great deliverability without the complexity of legacy platforms. This guide covers everything from first email to production templates.
What You'll Build
- Send transactional emails (welcome, receipt, password reset)
- React Email templates with Resend
- Domain verification for production sending
- Webhook tracking for delivery events
- Batch sending and attachments
Prerequisites: Node.js 18+, Resend account (free tier: 3,000 emails/month).
1. Setup
Install
npm install resend
Initialize
// lib/resend.ts
import { Resend } from 'resend';
export const resend = new Resend(process.env.RESEND_API_KEY);
Environment Variables
# .env.local
RESEND_API_KEY=re_...
2. Send Your First Email
const { data, error } = await resend.emails.send({
from: 'Your App <hello@yourdomain.com>',
to: 'user@example.com',
subject: 'Welcome to Your App!',
html: '<h1>Welcome!</h1><p>Thanks for signing up.</p>',
});
if (error) {
console.error('Failed to send:', error);
} else {
console.log('Email sent:', data.id);
}
API Route (Next.js)
// app/api/send-email/route.ts
import { NextResponse } from 'next/server';
import { resend } from '@/lib/resend';
export async function POST(req: Request) {
const { to, name } = await req.json();
const { data, error } = await resend.emails.send({
from: 'Your App <hello@yourdomain.com>',
to,
subject: `Welcome, ${name}!`,
html: `
<h1>Welcome to Your App, ${name}!</h1>
<p>Your account is ready. Here's what to do next:</p>
<ol>
<li>Complete your profile</li>
<li>Explore the dashboard</li>
<li>Invite your team</li>
</ol>
<a href="https://yourapp.com/dashboard">Go to Dashboard →</a>
`,
});
if (error) {
return NextResponse.json({ error }, { status: 500 });
}
return NextResponse.json({ id: data!.id });
}
3. React Email Templates
React Email lets you build email templates with React components — version-controlled, testable, and type-safe.
Install React Email
npm install @react-email/components
Create a Template
// emails/WelcomeEmail.tsx
import {
Body,
Container,
Head,
Heading,
Html,
Link,
Preview,
Section,
Text,
Button,
} from '@react-email/components';
interface WelcomeEmailProps {
name: string;
dashboardUrl: string;
}
export function WelcomeEmail({ name, dashboardUrl }: WelcomeEmailProps) {
return (
<Html>
<Head />
<Preview>Welcome to Your App, {name}!</Preview>
<Body style={main}>
<Container style={container}>
<Heading style={heading}>Welcome, {name}!</Heading>
<Text style={text}>
Your account is ready. Click below to get started.
</Text>
<Section style={buttonSection}>
<Button style={button} href={dashboardUrl}>
Go to Dashboard
</Button>
</Section>
<Text style={footer}>
Questions? Reply to this email or visit our{' '}
<Link href="https://yourapp.com/help">help center</Link>.
</Text>
</Container>
</Body>
</Html>
);
}
const main = { backgroundColor: '#f6f9fc', fontFamily: 'sans-serif' };
const container = { margin: '0 auto', padding: '40px 20px', maxWidth: '560px' };
const heading = { fontSize: '24px', fontWeight: 'bold', color: '#1a1a1a' };
const text = { fontSize: '16px', color: '#4a4a4a', lineHeight: '24px' };
const buttonSection = { textAlign: 'center' as const, margin: '32px 0' };
const button = {
backgroundColor: '#2563eb',
color: '#ffffff',
padding: '12px 24px',
borderRadius: '6px',
fontSize: '16px',
textDecoration: 'none',
};
const footer = { fontSize: '14px', color: '#8a8a8a' };
Send with React Email Template
import { resend } from '@/lib/resend';
import { WelcomeEmail } from '@/emails/WelcomeEmail';
const { data, error } = await resend.emails.send({
from: 'Your App <hello@yourdomain.com>',
to: 'user@example.com',
subject: 'Welcome to Your App!',
react: WelcomeEmail({
name: 'Alex',
dashboardUrl: 'https://yourapp.com/dashboard',
}),
});
4. Domain Verification
To send from your own domain (not onboarding@resend.dev), verify your domain:
Add DNS Records
Resend requires these DNS records:
| Type | Name | Value | Purpose |
|---|---|---|---|
| TXT | @ | v=spf1 include:resend.com ~all | SPF (sender authorization) |
| CNAME | resend._domainkey | resend.domainkey... | DKIM (email signing) |
| TXT | _dmarc | v=DMARC1; p=none; | DMARC (email policy) |
Verify via API
// Check domain verification status
const domains = await resend.domains.list();
console.log(domains);
// Or add a new domain
const domain = await resend.domains.create({
name: 'yourdomain.com',
});
5. Common Email Types
Password Reset
await resend.emails.send({
from: 'Security <security@yourdomain.com>',
to: userEmail,
subject: 'Reset your password',
react: PasswordResetEmail({
resetUrl: `https://yourapp.com/reset?token=${token}`,
expiresIn: '1 hour',
}),
});
Order Confirmation
await resend.emails.send({
from: 'Orders <orders@yourdomain.com>',
to: customerEmail,
subject: `Order #${orderId} confirmed`,
react: OrderConfirmationEmail({
orderId,
items,
total,
estimatedDelivery,
}),
});
Team Invitation
await resend.emails.send({
from: 'Your App <team@yourdomain.com>',
to: inviteeEmail,
subject: `${inviterName} invited you to join ${teamName}`,
react: TeamInviteEmail({
inviterName,
teamName,
inviteUrl: `https://yourapp.com/invite?token=${token}`,
}),
});
6. Batch Sending
Send multiple emails in one API call:
const { data, error } = await resend.batch.send([
{
from: 'App <hello@yourdomain.com>',
to: 'user1@example.com',
subject: 'Your weekly summary',
react: WeeklySummaryEmail({ userId: '1' }),
},
{
from: 'App <hello@yourdomain.com>',
to: 'user2@example.com',
subject: 'Your weekly summary',
react: WeeklySummaryEmail({ userId: '2' }),
},
]);
Limits: Up to 100 emails per batch call.
7. Webhooks
Track email delivery events:
Register Webhook
// In Resend dashboard or via API
await resend.webhooks.create({
url: 'https://yourapp.com/api/webhooks/resend',
events: [
'email.sent',
'email.delivered',
'email.bounced',
'email.complained',
'email.opened',
'email.clicked',
],
});
Handle Webhook Events
// app/api/webhooks/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':
// Update delivery status in database
await markEmailDelivered(event.data.email_id);
break;
case 'email.bounced':
// Mark email as invalid, stop sending
await handleBounce(event.data.to, event.data.bounce_type);
break;
case 'email.complained':
// User marked as spam — unsubscribe immediately
await unsubscribeUser(event.data.to);
break;
}
return NextResponse.json({ received: true });
}
Pricing
| Tier | Emails/Month | Price |
|---|---|---|
| Free | 3,000 | $0 |
| Pro | 50,000 | $20/month |
| Scale | 100,000 | $90/month |
| Enterprise | Custom | Custom |
No per-email overage charges on free tier — sending simply stops at the limit.
Common Mistakes
| Mistake | Impact | Fix |
|---|---|---|
| Sending from unverified domain | Emails go to spam | Verify domain with SPF/DKIM/DMARC |
| No bounce handling | Hurts sender reputation | Handle email.bounced webhooks |
| HTML emails without plain text fallback | Some clients show blank | Provide text alongside html |
| Hardcoding "from" address | Can't change sender easily | Use environment variable |
| Not testing email rendering | Looks broken in Outlook | Preview in React Email dev server |
Choosing an email API? Compare Resend vs SendGrid vs Postmark on APIScout — pricing, deliverability, and developer experience.