Resend Transactional Email Done Right in 2026
Production guide to Resend API v2: setup, error handling, templates, and patterns every indie hacker needs for reliable email delivery.
TL;DR
Resend ([official docs](https://resend.com/docs)) is a developer-first transactional email service built for modern applications. Verify current pricing and API version in official docs before deploying. This guide covers v2 API patterns, common errors, and production-ready code.
Why Resend Beats Traditional SMTP
If you've debugged SMTP authentication at 3 AM, you understand the appeal. Resend eliminates:
You get a REST API, event webhooks, and dashboard analytics. The tradeoff: you're trusting an external service. For most indie projects, this tradeoff wins.
Getting Started: Installation & Auth
```bash npm install resend ```
Verify you're installing the latest version (check npm for current release):
```bash npm view resend version ```
Authenticate via environment variable:
```bash
.env.local
RESEND_API_KEY=re_xxxxxxxxxxxxx ```Production-Ready Patterns
Pattern 1: Basic Email with Error Handling
```typescript import { Resend } from 'resend';
const resend = new Resend(process.env.RESEND_API_KEY);
export async function sendWelcomeEmail(userEmail: string, userName: string) {
try {
const response = await resend.emails.send({
from: 'onboarding@yourdomain.com',
to: userEmail,
subject: Welcome, ${userName}!,
html: <h1>Hi ${userName}</h1><p>Thanks for joining.</p>,
});
if (response.error) {
console.error('Resend API error:', response.error);
throw new Error(Email send failed: ${response.error.message});
}
return { success: true, id: response.data?.id }; } catch (error) { console.error('Email error:', error); // Log to your error tracking service (Sentry, etc) throw error; } } ```
Pattern 2: Template-Based Emails
Resend supports both inline HTML and template IDs. For maintainability, use templates:
```typescript
export async function sendPasswordReset(
email: string,
resetToken: string
) {
const resetUrl = ${process.env.APP_URL}/reset?token=${resetToken};
const response = await resend.emails.send({ from: 'noreply@yourdomain.com', to: email, subject: 'Password Reset Request', html: ` <h2>Reset Your Password</h2> <p>Click the link below within 1 hour:</p> <a href="${resetUrl}" style="background:blue;color:white;padding:10px;text-decoration:none;border-radius:4px;"> Reset Password </a> <p style="color:#999;font-size:12px;margin-top:20px;"> If you didn't request this, ignore this email. </p> `, });
return response; } ```
Pattern 3: Batch Sending with Rate Limiting
Sending to multiple users? Implement backpressure:
```typescript export async function sendBulkEmails( recipients: Array<{ email: string; name: string }> ) { const BATCH_SIZE = 10; const DELAY_MS = 100; const results = [];
for (let i = 0; i < recipients.length; i += BATCH_SIZE) {
const batch = recipients.slice(i, i + BATCH_SIZE);
const promises = batch.map(({ email, name }) =>
resend.emails.send({
from: 'newsletter@yourdomain.com',
to: email,
subject: Monthly Update for ${name},
html: <p>Hi ${name}, here's what's new...</p>,
})
);
const batchResults = await Promise.all(promises); results.push(...batchResults);
// Delay between batches to avoid rate limits if (i + BATCH_SIZE < recipients.length) { await new Promise(resolve => setTimeout(resolve, DELAY_MS)); } }
return results; } ```
Common Errors & Solutions
Error 1: Missing or Invalid API Key
``` Error: Unauthorized - Invalid API Key ```
Solution: Verify your RESEND_API_KEY environment variable is set and correct. Check the [Resend dashboard](https://resend.com) for your actual key. Never commit keys to version control.
Error 2: Invalid 'from' Domain
``` Error: Email not verified - the 'from' address domain is not verified ```
Solution: You must verify your sending domain in the Resend dashboard. Follow their DNS setup guide. Verify current verification process in official docs as this changes between releases.
Error 3: Rate Limiting
``` Error: Too many requests - rate limit exceeded (429) ```
Solution: Implement exponential backoff:
```typescript async function sendWithRetry( email: string, maxAttempts = 3 ) { for (let attempt = 0; attempt < maxAttempts; attempt++) { try { return await resend.emails.send(email); } catch (error: any) { if (error.status === 429 && attempt < maxAttempts - 1) { const delayMs = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s await new Promise(resolve => setTimeout(resolve, delayMs)); continue; } throw error; } } } ```
Webhook Events for Delivery Tracking
Monitor email lifecycle events:
```typescript // Your API endpoint export async function POST(req: Request) { const event = await req.json();
switch (event.type) {
case 'email.delivered':
console.log(Email delivered: ${event.data.email_id});
// Update your database
break;
case 'email.bounced':
console.warn(Email bounced: ${event.data.email});
// Flag user or remove from list
break;
case 'email.complained':
console.warn(User complained about email: ${event.data.email});
// Unsubscribe user immediately
break;
}
return new Response('OK', { status: 200 }); } ```
Configure webhook URL in your Resend dashboard settings.
Related Guides
Before deploying email in production, understand [email authentication basics](/?guide=spf-dkim-dmarc) and [user preference management](/?guide=email-unsubscribe).
Pricing & Scale Considerations
Verify current pricing in [official docs](https://resend.com/pricing). Resend's model typically offers:
Calculate your annual email volume before choosing a service. For indie projects sending <100k emails/month, Resend is often cheaper than maintaining your own SMTP infrastructure.
Production Deployment Checklist
from addresses and templatesWhat am I missing?
Have you hit different errors? Built something interesting with Resend? Using alternative services and want to share trade-offs? Drop corrections and additions in the commentsβthis guide will be updated as the ecosystem evolves.