Skip to main content

How to Send Transactional Emails with Resend 2026

·APIScout Team
Share:

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:

TypeNameValuePurpose
TXT@v=spf1 include:resend.com ~allSPF (sender authorization)
CNAMEresend._domainkeyresend.domainkey...DKIM (email signing)
TXT_dmarcv=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

TierEmails/MonthPrice
Free3,000$0
Pro50,000$20/month
Scale100,000$90/month
EnterpriseCustomCustom

No per-email overage charges on free tier — sending simply stops at the limit.

Common Mistakes

MistakeImpactFix
Sending from unverified domainEmails go to spamVerify domain with SPF/DKIM/DMARC
No bounce handlingHurts sender reputationHandle email.bounced webhooks
HTML emails without plain text fallbackSome clients show blankProvide text alongside html
Hardcoding "from" addressCan't change sender easilyUse environment variable
Not testing email renderingLooks broken in OutlookPreview in React Email dev server

Deliverability Best Practices

A transactional email API handles the mechanics of sending — SMTP connections, retries, bounce handling at the infrastructure level — but deliverability depends on your sending behavior, not just the platform. Gmail, Outlook, and Apple Mail run incoming email through spam scoring algorithms that consider your domain's reputation, sending volume patterns, and user engagement signals.

Domain reputation is per-IP and per-domain. When you first add a domain to Resend and start sending, you're building reputation from scratch. ISPs (Gmail in particular) watch your initial sends carefully — high bounce rates or spam complaints in the first few weeks can damage your domain's reputation permanently with some ISPs. Best practices for a new sending domain: start with small volumes (under 1,000 emails/day for the first week), send only to engaged users (recent signups, not old lists), and monitor your bounce and complaint rates in the Resend dashboard.

List hygiene is the biggest lever on deliverability. Every email you send to an invalid address returns a hard bounce; every email a user marks as spam generates a complaint. Email providers track these rates: Gmail's spam threshold is roughly 0.10% (1 complaint per 1,000 emails) and will start filtering your email to spam if you exceed it. Above 0.30%, delivery stops entirely. Keep your list clean by removing addresses that haven't engaged in 6 months, and never import purchased or scraped lists — they're full of invalid addresses and spam traps that will destroy your sending reputation instantly.

Engagement signals matter. Gmail uses open rates and click rates as positive signals. Sending to users who never open your emails trains Gmail's algorithm to classify your emails as low-priority. Segment your list and suppress long-inactive subscribers before sending: if a user hasn't opened any email in 12 months, remove them from marketing sends entirely. For transactional emails (password resets, order confirmations), this doesn't apply — those should always be sent regardless of engagement history.

Warm up dedicated IPs carefully. On Resend's Scale plan and above, you can request dedicated sending IPs. These give you full control over your sending reputation but start with zero reputation — ISPs don't know this IP yet. Warm up by sending small volumes initially (500/day week 1, doubling each week) and only sending to your most engaged users during the warmup period. Resend provides guidance on IP warmup schedules in their documentation.


Queuing and Retry Logic

The Resend API returns synchronous errors when the send fails immediately (network issues, invalid API key, malformed payload) but delivery failures (bounces, spam blocks) arrive via webhooks minutes or hours later. A production email system needs both: a queue for rate limiting and retry, and webhook handling for delivery status.

Never send email directly from a user request. If the Resend API is slow or returns a transient error, you don't want the user waiting or seeing an error. Instead, write the email to a database queue and return success to the user immediately. A background worker sends from the queue:

// On user action — add to queue:
await db.emailQueue.create({
  data: {
    to: user.email,
    type: 'welcome',
    payload: JSON.stringify({ name: user.name }),
    status: 'pending',
    scheduledAt: new Date(),
    attempts: 0,
  },
});

// Background worker (runs every 30 seconds):
async function processEmailQueue() {
  const pending = await db.emailQueue.findMany({
    where: {
      status: 'pending',
      scheduledAt: { lte: new Date() },
      attempts: { lt: 3 },
    },
    take: 50,  // Process in batches of 50
  });

  for (const job of pending) {
    try {
      await db.emailQueue.update({
        where: { id: job.id },
        data: { status: 'sending', attempts: { increment: 1 } },
      });

      const { data, error } = await resend.emails.send({
        from: 'App <hello@yourapp.com>',
        to: job.to,
        subject: getSubject(job.type),
        react: getTemplate(job.type, JSON.parse(job.payload)),
      });

      if (error) throw new Error(error.message);

      await db.emailQueue.update({
        where: { id: job.id },
        data: { status: 'sent', resendId: data!.id, sentAt: new Date() },
      });
    } catch (err) {
      const nextAttempt = job.attempts >= 2
        ? new Date(Date.now() + 60 * 60 * 1000)  // 1 hour delay on final retry
        : new Date(Date.now() + 5 * 60 * 1000);  // 5 min delay on first retries

      await db.emailQueue.update({
        where: { id: job.id },
        data: {
          status: job.attempts >= 2 ? 'failed' : 'pending',
          scheduledAt: nextAttempt,
          error: String(err),
        },
      });
    }
  }
}

Resend's API rate limits: the free tier allows 2 requests/second; paid plans allow up to 10 requests/second. For batch sends to many users (weekly digests, announcements), use resend.batch.send() to send up to 100 emails per API call rather than 100 individual calls. The queue pattern above with take: 50 combined with batch sending gives you 5,000 emails per second at rate limit, which handles most production volumes.


Testing Email Templates

React Email provides a development server that renders your email templates in a browser, making it easy to iterate on design before sending real emails. Run it alongside your Next.js app:

npx react-email dev --dir emails --port 3001

This starts a preview server at localhost:3001 with hot reload for all templates in your emails/ directory. Each template renders as it would in email clients, with a preview text bar and basic responsiveness.

Email client compatibility testing is more involved. React Email components are designed to output HTML compatible with Outlook, Gmail, Apple Mail, and mobile clients, but you should test real renders before launching. Email on Acid and Litmus both offer preview rendering across 50+ client and device combinations. For critical email templates (password reset, receipt), test across at least Gmail (web), Outlook 365, and iOS Mail before shipping.

Inline CSS vs. style sheets: email clients notoriously strip <style> tags from <head>. React Email handles this automatically by inlining styles on each element, but if you're writing raw HTML templates, you must inline styles yourself or use an inliner library like juice. The React Email components (Container, Button, Text, etc.) all use inline styles internally, which is why they render correctly across clients.

Dark mode support is inconsistent across email clients. Gmail on Android and iOS renders dark mode by inverting colors. Outlook has its own dark mode behavior. React Email's components don't include dark mode CSS by default — if dark mode matters for your brand, use media queries in a <Head> component and test in Litmus or Email on Acid which simulates dark mode rendering.

Testing webhook delivery locally: use the Resend webhook preview feature in your dashboard, or set up a local tunnel with ngrok and point your webhook URL to https://your-ngrok-id.ngrok.io/api/webhooks/resend. Send test emails through the Resend dashboard and verify your webhook handler processes all event types correctly before deploying to production.


Methodology

Resend is one of several modern transactional email APIs targeting developers — Postmark, Mailgun, and SendGrid are the main alternatives. Resend's key differentiators are the React Email integration (which makes template development significantly faster than HTML string templates), the developer experience (clear error messages, minimal setup), and transparent pricing without per-email fees beyond the plan limit. Postmark has historically stronger deliverability for high-volume transactional email but a higher price floor. SendGrid is more complex but offers marketing email and advanced analytics on the same platform. For most new projects in 2026, Resend is the fastest path from zero to production email delivery.

Resend SDK examples use resend npm package v3.x. React Email component examples use @react-email/components v0.0.x. Email authentication DNS record values (SPF, DKIM) are illustrative — use the exact records provided in your Resend dashboard during domain setup, as the DKIM CNAME record value is unique to your domain. Deliverability thresholds (0.10% spam complaint rate for Gmail) sourced from Google's Email Sender Guidelines published February 2024 and effective February 2024; verify current thresholds from Google's Postmaster Tools documentation. Resend pricing verified from published pricing page as of March 2026; free tier (3,000 emails/month) and batch API limit (100 emails/call) verified from Resend API documentation. Rate limits (2 req/sec free, 10 req/sec paid) from Resend's rate limiting documentation. SMTP deliverability best practices follow M3AAWG (Messaging, Malware and Mobile Anti-Abuse Working Group) guidelines and RFC 5321.


Choosing an email API? Compare Resend vs SendGrid vs Postmark on APIScout — pricing, deliverability, and developer experience.

Related: Building a SaaS Backend, How to Build Email Templates with React Email + Resend, Building a Communication Platform

The API Integration Checklist (Free PDF)

Step-by-step checklist: auth setup, rate limit handling, error codes, SDK evaluation, and pricing comparison for 50+ APIs. Used by 200+ developers.

Join 200+ developers. Unsubscribe in one click.