EmpireUI
Get Pro
← Blog8 min read#resend#email#react email

Resend + React Email: Transactional Emails That Don't Land in Spam

Stop fighting spam filters. Here's how to wire Resend and React Email into your Next.js app for transactional emails that actually reach the inbox.

laptop screen showing email inbox with code editor open beside it

Why Your Transactional Emails Keep Disappearing

You've been there. User signs up, you trigger a welcome email, and three days later they open a support ticket asking "did you send me anything?" The email landed in spam — or got silently dropped by their mail server — and you had no idea. This isn't a fluke. It's what happens when you send transactional email through the wrong stack.

The old approach was SMTP credentials bolted onto SendGrid or Mailgun, a nodemailer config buried in some utils file, and HTML strings with inline styles that you were absolutely terrified to touch. In practice, that setup works until it doesn't — and when it breaks (IP reputation tanked, domain auth misconfigured, some ancient HTML table rendering wrong in Outlook 2019), debugging it is a nightmare.

Resend flipped this in 2023. It's an email API built specifically for developers, with a generous free tier (3,000 emails/month as of version 1.x), first-class Next.js support, and a dead-simple SDK. Pair it with React Email — the component library from the same team — and you're writing emails in JSX instead of wrestling with HTML tables. Honestly, the first time you render a proper transactional email in 40 lines of clean React, it feels a bit surreal.

Setting Up Resend in a Next.js App Router Project

Start with the install. You need two packages — the Resend SDK and React Email itself:

npm install resend @react-email/components

Then grab your API key from resend.com/api-keys, drop it in .env.local, and you're ready. One environment variable. That's the whole "configuration" step.

RESEND_API_KEY=re_xxxxxxxxxxxxxxxx

Worth noting: Resend requires you to verify a domain before you can send from it in production. The DNS records they need are SPF, DKIM, and DMARC. This sounds intimidating but the Resend dashboard walks you through exactly which records to add — usually just three TXT records and you're done inside 10 minutes. This domain verification is the single biggest reason your emails stop landing in spam. No verified domain = no deliverability, doesn't matter which provider you're on.

Building Your First React Email Template

React Email gives you a set of components — <Html>, <Body>, <Container>, <Text>, <Button>, <Link>, <Img> — that compile to email-safe HTML. You write familiar JSX, they handle the cross-client compatibility layer underneath. No more staring at a 400-line HTML table praying it doesn't break in Gmail.

Here's a basic welcome email template. Create it at emails/WelcomeEmail.tsx:

import {
  Html,
  Body,
  Container,
  Heading,
  Text,
  Button,
  Link,
  Preview,
} from '@react-email/components';

interface WelcomeEmailProps {
  username: string;
  confirmUrl: string;
}

export function WelcomeEmail({ username, confirmUrl }: WelcomeEmailProps) {
  return (
    <Html lang="en">
      <Preview>Welcome to the app, {username}!</Preview>
      <Body style={{ backgroundColor: '#f4f4f5', fontFamily: 'sans-serif' }}>
        <Container
          style={{
            maxWidth: '560px',
            margin: '40px auto',
            backgroundColor: '#ffffff',
            borderRadius: '8px',
            padding: '40px',
          }}
        >
          <Heading style={{ fontSize: '24px', color: '#111827' }}>
            Hey {username}, welcome aboard.
          </Heading>
          <Text style={{ color: '#6b7280', lineHeight: '1.6' }}>
            Click the button below to confirm your email address.
            This link expires in 24 hours.
          </Text>
          <Button
            href={confirmUrl}
            style={{
              backgroundColor: '#6366f1',
              color: '#ffffff',
              padding: '12px 24px',
              borderRadius: '6px',
              display: 'inline-block',
              textDecoration: 'none',
              fontSize: '14px',
            }}
          >
            Confirm email
          </Button>
          <Text style={{ color: '#9ca3af', fontSize: '12px', marginTop: '32px' }}>
            Didn't create an account?{' '}
            <Link href="mailto:support@yourapp.com" style={{ color: '#6366f1' }}>
              Let us know
            </Link>
          </Text>
        </Container>
      </Body>
    </Html>
  );
}

Notice the inline styles — this isn't laziness, it's required. Most email clients strip <style> tags or ignore class names entirely. React Email's components make inline styles feel natural rather than messy. Quick aside: the <Preview> component controls the preview text that shows up in Gmail's inbox list next to the subject line. 90% of developers forget this and leave it empty, which tanks open rates.

Wiring It Into a Next.js Route Handler

With App Router, you'll send email from a Route Handler — a route.ts file inside the app/api/ directory. Here's a /api/send-welcome endpoint that ties everything together:

// app/api/send-welcome/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { Resend } from 'resend';
import { render } from '@react-email/render';
import { WelcomeEmail } from '@/emails/WelcomeEmail';

const resend = new Resend(process.env.RESEND_API_KEY);

export async function POST(req: NextRequest) {
  const { username, email, confirmUrl } = await req.json();

  const html = render(
    <WelcomeEmail username={username} confirmUrl={confirmUrl} />
  );

  const { data, error } = await resend.emails.send({
    from: 'Your App <hello@yourdomain.com>',
    to: email,
    subject: `Welcome, ${username}!`,
    html,
  });

  if (error) {
    return NextResponse.json({ error }, { status: 500 });
  }

  return NextResponse.json({ id: data?.id });
}

That's the whole thing. render() from @react-email/render turns your React component into an HTML string with all the inline styles baked in. You pass that string to resend.emails.send(). Resend returns an id you can log for debugging. Look, if you've spent time with older email setups, the simplicity here might make you suspicious — but it genuinely works this cleanly.

One more thing — if you're on the Pages Router instead of App Router, the pattern is almost identical. Replace the Route Handler with pages/api/send-welcome.ts and swap NextRequest/NextResponse for the classic req/res types. The Resend SDK call is identical.

Previewing Emails Locally Without Sending Them

Sending a real email every time you tweak padding-left from 16px to 24px is unbearable. React Email ships a local dev server that renders your templates in the browser — hot-reloading included — so you can iterate without burning through API calls or polluting your own inbox.

npx email dev --dir ./emails

This spins up a server on port 3000 (or 3001 if that's taken) with a sidebar listing every template in your emails/ directory. Click one, see it rendered instantly. You can toggle between HTML view and a preview that simulates what it'll look like in a real client. This is the workflow: edit the JSX, see it in the preview, send once to yourself when you're happy, ship.

In practice, you'll also want to check your emails in real clients before launching. Resend's dashboard shows you opens and clicks, but for cross-client rendering issues — particularly in Outlook 2021 which still has its own HTML rendering engine — tools like Litmus or Email on Acid do a full screenshot suite across 90+ clients. Worth the cost if email is a core part of your product.

Deliverability Checklist: What Actually Keeps You Out of Spam

The template and the API call are the easy part. Deliverability is where most teams leave points on the table. Here's what actually moves the needle, in priority order.

Verify your domain first. This is non-negotiable. SPF, DKIM, and DMARC records tell receiving mail servers that you are who you say you are. Without them, you're just some random IP claiming to be @yourapp.com, and spam filters treat you accordingly. Resend's domain verification flow is genuinely the best I've seen — you add records in your DNS provider and Resend checks them automatically within a few minutes.

Don't send from a free email provider. from: 'hello@gmail.com' in a transactional email is a spam signal. Always use your own domain. Keep your marketing list (newsletters, promotions) on a subdomain like mail.yourdomain.com and your transactional email on your root domain or notify.yourdomain.com — this way a spam complaint from a marketing campaign doesn't tank the IP reputation that handles password resets. That said, this level of separation only really matters once you're sending north of 10,000 emails a month.

Watch your bounce rate. Resend's dashboard surfaces hard bounces and complaints. A bounce rate above 5% will get your account flagged. Clean your list regularly, never purchase email lists, and handle unsubscribes immediately. The List-Unsubscribe header is worth adding even to transactional emails — some clients surface it as a native unsubscribe button, which is better than a spam report.

// Add this to your resend.emails.send() call:
headers: {
  'List-Unsubscribe': '<mailto:unsubscribe@yourdomain.com>',
},

One thing people overlook: the from name matters. "noreply@yourapp.com" is fine technically, but "Your App <hello@yourapp.com>" with a real reply-to address signals legitimacy to spam filters and actually gets replies from users who have questions. Don't make your email look like a void.

Connecting This to Your UI Components

Your email templates don't live in isolation — they're part of your product's design language. If you're already using a design system for your web UI, your email templates should feel visually consistent. Same brand colors, same button radius, same typography choices as much as email clients will allow.

If you're building the surrounding UI in Empire UI — say, the signup flow that triggers the welcome email — you can pull design tokens and component patterns directly from the Empire UI library. We have styles ranging from glassmorphism to neobrutalism, and while email clients won't render backdrop-filter blur, you can absolutely match the color palette and spacing system. Your user's first interaction with your product might be an email. Make it feel like the same product they signed up for.

Worth noting: Resend's webhooks let you react to email events — deliveries, opens, bounces, complaints — and pipe that data back into your app. You can trigger UI state changes (show a "resend confirmation" button after 10 minutes without a delivery event), log analytics, or alert your team on complaint spikes. The Resend SDK has a webhooks.constructEvent() helper that works exactly like Stripe's webhook signature verification — same pattern, easy to plug in.

FAQ

Does Resend work with Next.js App Router and Server Actions?

Yes, fully. You can call resend.emails.send() inside a Server Action directly — no Route Handler needed. Just mark the function with 'use server', import the Resend SDK, and call it. The SDK is async and works in any Node.js environment Next.js runs server-side code in.

Can I use React Email without Resend?

Absolutely. React Email is a standalone template library. You call render() to get an HTML string, then pass that string to any email provider — Nodemailer, SendGrid, Postmark, AWS SES, whatever you're already using. The two tools pair well but neither requires the other.

How do I handle email in local development so I'm not sending real emails?

Two options. Use the npx email dev preview server for visual iteration without sending anything. For testing the actual send flow locally, point your Resend API key at a real test address you own, or conditionally skip the send call when NODE_ENV === 'development' and just log the rendered HTML instead.

What's the Resend free tier and when do I need to upgrade?

As of 2026, Resend's free plan includes 3,000 emails per month and 1 custom domain. That covers most early-stage products comfortably. Once you're sending password resets, onboarding sequences, and order confirmations at scale, the Pro plan starts at $20/month for 50,000 emails. Check their pricing page for current limits.

Free components in 40 styles
React & Tailwind, copy-paste ready.
Browse →

Read next

UploadThing Guide: File Uploads in Next.js Without the S3 Config PainVercel Edge Config: Feature Flags, A/B Tests Without RedeployReact Email: Building HTML Emails With React ComponentsResend vs SendGrid in 2026: Deliverability, Pricing and DX