Resend: Transactional Email Done Right in 2026
Production-ready guide to Resend's email API. Error patterns, code examples, and why indie hackers are switching from SendGrid.
TL;DR
Resend is a modern transactional email service built for developers. We cover setup, common errors, production patterns, and why it's gaining traction in the indie hacker community.---
What Changed in 2026?
Resend has solidified its position as the "developer-first" email solution. While SendGrid still dominates enterprise, Resend's focus on simplicity and reasonable pricing ($20-200/mo depending on volume) makes it the default choice for bootstrapped products.
Current status (verify in [official docs](https://resend.com/docs)): The API remains stable, with recent additions around batch sending and webhook reliability improvements.
---
Why Resend Over Alternatives?
The indie hacker calculus:
Compare this to SendGrid's complexity or Mailgun's dated UX, and the choice clarifies fast.
---
Getting Started: The Right Way
Installation
```bash npm install resend ```
Current version (verify in official docs): ^3.0.0+ is standard for new projects as of early 2026.
First Production Call
This pattern should be your template:
```typescript import { Resend } from 'resend';
const resend = new Resend(process.env.RESEND_API_KEY);
export async function sendWelcomeEmail(email: string, userName: string) {
try {
const { data, error } = await resend.emails.send({
from: 'onboarding@yourdomain.com',
to: email,
subject: 'Welcome aboard!',
html: <h1>Hi ${userName}!</h1><p>You're in.</p>,
// Always set reply-to for customer expectations
reply_to: 'support@yourdomain.com',
// Track engagement (optional but recommended)
tags: [{ name: 'category', value: 'welcome' }],
});
if (error) {
console.error('Resend error:', error.message);
throw new Error(Email send failed: ${error.message});
}
// Store the message ID for webhook reconciliation await db.emailLog.create({ messageId: data.id, email, type: 'welcome', sentAt: new Date(), });
return { success: true, messageId: data.id };
} catch (err) {
console.error('Unexpected error sending email:', err);
// Alert ops - this shouldn't happen
await alertSlack(Email send error for ${email});
throw err;
}
}
```
---
Console Errors You'll See (And How to Fix Them)
Error 1: Invalid From Address
``` Resend error: Invalid from address. Please use a verified domain. Cause: You're sending from an unverified domain Fix: Add your domain in Resend dashboard > Domains > Add Domain, then verify DNS records ```
Error 2: Rate Limiting
``` { "message": "Too many requests", "error": "rate_limit_exceeded" } Cause: Hitting API rate limits (typically 100 req/sec) Fix: Implement queue-based sending with Bull/BullMQ for batch operations ```
Error 3: Missing API Key
``` Error: The RESEND_API_KEY environment variable is missing Cause: Not set in .env.local or deployment config Fix: Copy from Resend dashboard > API Keys > ensure it's in your runtime environment ```
---
React Email Templates: The Secret Weapon
This is where Resend shines. Use [React Email](https://react.email) to write templates as components:
```typescript import { Button, Container, Head, Html, Img, Link, Preview, Row, Section, Text } from '@react-email/components';
interface InvoiceEmailProps { invoiceNumber: string; amount: number; customerName: string; }
export function InvoiceEmail({ invoiceNumber, amount, customerName, }: InvoiceEmailProps) { return ( <Html> <Head /> <Preview>Invoice {invoiceNumber} for ${amount}</Preview> <Body style={main}> <Container style={container}> <Section> <Text style={heading}>Invoice {invoiceNumber}</Text> <Text>Hi {customerName},</Text> <Text>Your invoice total: <strong>${amount.toFixed(2)}</strong></Text> <Button style={button} href="https://yourdomain.com/invoice/{invoiceNumber}"> View Invoice </Button> </Section> </Container> </Body> </Html> ); } ```
Send it:
```typescript
const { data, error } = await resend.emails.send({
from: 'billing@yourdomain.com',
to: customerEmail,
subject: Invoice ${invoiceNumber},
react: <InvoiceEmail invoiceNumber={invoiceNumber} amount={amount} customerName={name} />,
});
```
Why this matters: Type-safe emails, version control your templates, no separate template language to learn.
---
Webhook Handling for Production
Don't assume emails delivered. Listen for events:
```typescript // pages/api/webhooks/resend.ts (Next.js) import { WebhookEvent } from 'resend'; import crypto from 'crypto';
export async function POST(req: Request) { const signature = req.headers.get('resend-signature'); const body = await req.text();
// Verify signature (CRITICAL for security) const hash = crypto .createHmac('sha256', process.env.RESEND_WEBHOOK_SECRET!) .update(body) .digest('base64');
if (signature !== hash) { return new Response('Unauthorized', { status: 401 }); }
const event: WebhookEvent = JSON.parse(body);
switch (event.type) {
case 'email.delivered':
await db.emailLog.update(
{ messageId: event.data.email_id },
{ status: 'delivered', deliveredAt: new Date() }
);
break;
case 'email.bounced':
// Add to suppression list immediately
await db.suppressedEmails.create({ email: event.data.email_address });
console.warn(Email bounced: ${event.data.email_address});
break;
case 'email.complained':
// User marked as spam - handle accordingly
await alertSlack(Spam complaint: ${event.data.email_address});
break;
}
return new Response('OK', { status: 200 }); } ```
---
Pricing Reality Check
At 100k emails/month: ~$20. At 1M/month: ~$200. Scale the cost linearly without negotiating contracts.
---
Production Checklist
---
Related Guides
---
What am I missing?
Resend is actively developed and the ecosystem shifts. Tell us in the comments:
We update based on reader feedback.