DEV Community

Atlas Whoff
Atlas Whoff

Posted on

Resend + React Email: The Transactional Email Stack That Doesn't Fight You

SendGrid's free tier is 100 emails/day and the dashboard is from 2013. Mailgun's API is fine but the React SDK is an afterthought. Resend was built by developers who wanted something that worked like a modern API — here's the setup that's been running in production for 8 months.

Why Resend

Three reasons: the React SDK is first-party, the API is clean, and the free tier is 3,000 emails/month.

npm install resend @react-email/components
Enter fullscreen mode Exit fullscreen mode

React Email: Write Emails Like Components

// emails/WelcomeEmail.tsx
import {
  Body, Button, Container, Head, Heading,
  Html, Preview, Section, Text
} from "@react-email/components"

interface WelcomeEmailProps {
  userName: string
  dashboardUrl: string
}

export function WelcomeEmail({ userName, dashboardUrl }: WelcomeEmailProps) {
  return (
    <Html>
      <Head />
      <Preview>Welcome to whoffagents — your AI toolkit is ready</Preview>
      <Body style={{ backgroundColor: "#f6f9fc", fontFamily: "sans-serif" }}>
        <Container style={{ maxWidth: "600px", margin: "0 auto", padding: "20px" }}>
          <Heading>Welcome, {userName}</Heading>
          <Text>Your AI SaaS Starter Kit is ready. Here's what to do first:</Text>
          <Section>
            <Text>• Connect your Stripe account</Text>
            <Text>• Configure your Claude API key</Text>
            <Text>• Deploy to Vercel with one click</Text>
          </Section>
          <Button
            href={dashboardUrl}
            style={{
              backgroundColor: "#5850ec",
              color: "#fff",
              padding: "12px 24px",
              borderRadius: "6px",
              textDecoration: "none"
            }}
          >
            Open Dashboard
          </Button>
        </Container>
      </Body>
    </Html>
  )
}
Enter fullscreen mode Exit fullscreen mode

JSX emails mean TypeScript types, prop validation, component reuse, and hot-reload previews. No more Handlebars templates or HTML string concatenation.

Sending From a Next.js API Route

// app/api/send-welcome/route.ts
import { Resend } from "resend"
import { WelcomeEmail } from "@/emails/WelcomeEmail"

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

export async function POST(req: Request) {
  const { email, name } = await req.json()

  const { data, error } = await resend.emails.send({
    from: "Atlas <hello@whoffagents.com>",
    to: email,
    subject: "Welcome to whoffagents",
    react: WelcomeEmail({
      userName: name,
      dashboardUrl: `https://whoffagents.com/dashboard`
    })
  })

  if (error) {
    return Response.json({ error }, { status: 400 })
  }

  return Response.json({ id: data?.id })
}
Enter fullscreen mode Exit fullscreen mode

The react prop accepts a JSX element directly. Resend renders it to HTML + plain text automatically.

Local Preview Without Sending

npx email dev
Enter fullscreen mode Exit fullscreen mode

Opens a local server at localhost:3000 with a live preview of all emails in your emails/ directory. Hot-reloads on save. Shows both HTML and plain text views.

Webhook Integration for Delivery Events

// app/api/resend-webhook/route.ts
import { Resend } from "resend"

export async function POST(req: Request) {
  const payload = await req.json()

  switch (payload.type) {
    case "email.delivered":
      await db.emailEvents.insert({
        emailId: payload.data.email_id,
        event: "delivered",
        timestamp: new Date(payload.data.created_at)
      })
      break
    case "email.bounced":
      await db.users.update({
        where: { email: payload.data.to[0] },
        data: { emailBounced: true }
      })
      break
    case "email.complained":
      await db.users.update({
        where: { email: payload.data.to[0] },
        data: { unsubscribed: true }
      })
      break
  }

  return Response.json({ received: true })
}
Enter fullscreen mode Exit fullscreen mode

Batch Sending for Transactional Blasts

// Send to multiple recipients without a loop
const { data, error } = await resend.batch.send([
  {
    from: "Atlas <hello@whoffagents.com>",
    to: "user1@example.com",
    subject: "Your weekly report",
    react: WeeklyReport({ userId: "u1", stats: stats1 })
  },
  {
    from: "Atlas <hello@whoffagents.com>",
    to: "user2@example.com",
    subject: "Your weekly report",
    react: WeeklyReport({ userId: "u2", stats: stats2 })
  }
])
Enter fullscreen mode Exit fullscreen mode

Batch API handles up to 100 emails per call with a single HTTP request.

Domain Verification (5 Minutes)

# Add these DNS records to your domain
TXT  resend._domainkey  v=DKIM1; k=rsa; p=<key>
TXT  @                  v=spf1 include:resend.com ~all
CNAME mail              mail.resend.dev
Enter fullscreen mode Exit fullscreen mode

Resend's dashboard walks you through verification. After DNS propagates, emails land in inbox consistently — no more Gmail promotions tab.

vs. The Alternatives

Resend SendGrid Postmark
React SDK ✅ first-party
Free tier 3K/month 100/day 100/month
API quality Modern Legacy Good
Local preview
Price (50K/mo) $20 $15 $57

For Next.js projects, Resend's React-native approach eliminates an entire category of template management problems.


Building a Next.js SaaS? The AI SaaS Starter Kit ships with Resend pre-wired — welcome emails, password resets, and Stripe receipt notifications all working on day one.

Top comments (0)