Resend Transactional Email Guide 2026: Setup to Production
Master Resend for reliable transactional emails. Real patterns, error solutions, and production-ready code for indie hackers.
TL;DR
Resend is a transactional email API built for developers. Send emails via REST or SDK, verify domains, handle bounces. No SMTP complexity. Pricing: free tier (100 emails/day), paid starts ~$20/month (verify in official docs). Current SDK version: check [Resend npm package](https://www.npmjs.com/package/resend).
---
Why Transactional Email Matters
Every SaaS needs transactional emails: password resets, order confirmations, notifications. Traditional SMTP (SendGrid, AWS SES) requires infrastructure knowledge—DNS records, bounce handling, rate limiting.
Resend strips that friction. It's SMTP for people who'd rather ship features than debug email infrastructure.
Getting Started: The 5-Minute Setup
1. Sign Up & Get Your API Key
Head to [resend.com](https://resend.com) and create an account. Grab your API key from the dashboard.
Security note: Store in environment variables, never commit to git.
```bash echo "RESEND_API_KEY=re_xxxxxxxxxxxxx" > .env.local ```
2. Install the SDK
```bash npm install resend ```
As of 2026, verify the latest version in [official npm docs](https://www.npmjs.com/package/resend).
3. Verify Your Domain
Transactional email requires domain verification. In Resend dashboard:
1. Add domain (e.g., noreply@yourapp.com)
2. Add DNS records (DKIM, SPF, DMARC)
3. Wait for verification (usually 5-10 minutes)
Without verification, you'll hit this error:
```
Error: Invalid from address. Please add and verify your domain.
```
---
Production-Ready Code Patterns
Pattern 1: Basic Transactional Email
```javascript import { Resend } from 'resend';
const resend = new Resend(process.env.RESEND_API_KEY);
export async function sendPasswordReset(email, resetToken) {
try {
const response = await resend.emails.send({
from: 'noreply@yourapp.com',
to: email,
subject: 'Reset your password',
html: <a href="https://yourapp.com/reset?token=${resetToken}">Reset password</a>,
});
if (response.error) { console.error('Resend error:', response.error); throw new Error(response.error.message); }
return { success: true, id: response.data.id }; } catch (error) { console.error('Email send failed:', error); // Log to monitoring (Sentry, DataDog, etc) throw error; } } ```
Key pattern: Always check response.error. Resend doesn't throw on validation failures—it returns them.
Pattern 2: HTML Templates with Variables
```javascript export async function sendOrderConfirmation(email, orderData) { const { orderId, total, items } = orderData;
const itemsHtml = items
.map(item => <li>${item.name} × ${item.qty}</li>)
.join('');
const html = ` <h2>Order Confirmed</h2> <p>Order #${orderId}</p> <ul>${itemsHtml}</ul> <p><strong>Total: ${(total / 100).toFixed(2)}</strong></p> `;
const response = await resend.emails.send({
from: 'orders@yourapp.com',
to: email,
subject: Order #${orderId} confirmed,
html,
});
return response; } ```
Pattern 3: Batch Emails (Newsletters)
```javascript
export async function sendBatchNotifications(recipients) {
const emails = recipients.map(recipient => ({
from: 'notify@yourapp.com',
to: recipient.email,
subject: Hi ${recipient.name}, new features!,
html: <p>Check out our new features</p>,
}));
try {
const response = await resend.batch.send(emails);
console.log(Sent ${response.data.length} emails);
return response;
} catch (error) {
console.error('Batch send failed:', error);
throw error;
}
}
```
---
Common Errors & Solutions
Error 1: "Invalid from address"
```
Error: {
message: "Invalid from address. Please add and verify your domain.",
code: "invalid_from_address"
}
```
Solution: Verify your domain in Resend dashboard. Use verified sender address only.
Error 2: "Invalid email address"
``` Error: { message: "Invalid email address: notanemail", code: "invalid_email" } ```
Solution: Validate email format before sending. Use a library like email-validator:
```javascript import { validate } from 'email-validator';
if (!validate(email)) { throw new Error('Invalid email format'); } ```
Error 3: "Rate limit exceeded"
``` Error: { message: "Too many requests", code: "rate_limit_exceeded" } ```
Solution: Implement exponential backoff or queue emails:
```javascript export async function sendWithRetry(emailFn, maxRetries = 3) { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await emailFn(); } catch (error) { if (error.code === 'rate_limit_exceeded' && attempt < maxRetries) { const delay = Math.pow(2, attempt) * 1000; await new Promise(resolve => setTimeout(resolve, delay)); } else { throw error; } } } } ```
---
Resend vs Alternatives
| Feature | Resend | SendGrid | AWS SES | |---------|--------|----------|--------| | REST API | ✅ | ✅ | ✅ | | SMTP | ✅ | ✅ | ✅ | | Domain Verification | Simple | Moderate | Complex | | Free Tier | 100/day | 100/day | 62k/month* | | Setup Time | 5 min | 15 min | 30+ min |
*AWS SES requires production access request
For [authentication flows](/?guide=auth), Resend excels. For [scaling to millions](/?guide=scaling-email), compare pricing.
---
Production Checklist
.env.local (not .env)---
Official Resources
---
What am I missing?
This guide covers fundamentals, but transactional email varies by use case:
Leave corrections, gotchas, or your Resend horror stories in comments below. Let's build better indie infrastructure together.