Code Tips12 min read

Resend vs SendGrid vs Amazon SES: Choosing a Transactional Email Provider for Next.js

An engineering comparison of Resend, SendGrid, and Amazon SES for transactional email in Next.js — covering API design, React Email integration, deliverability, pricing at scale, and how to build a provider abstraction layer.

R

React Emails Pro

March 11, 2026

You've built your Next.js app, designed your React Email templates, and now you need something to actually deliver them. The three providers that dominate transactional email for developers — Resend, SendGrid, and Amazon SES — take fundamentally different approaches to the same problem. Picking the wrong one costs you months of migration work later.

This isn't a marketing comparison. It's an engineering breakdown: API design, Next.js integration patterns, deliverability trade-offs, and the real costs once you're sending at scale.

99.5%

Deliverability baseline

All three providers achieve similar inbox placement rates when configured correctly.

10x

Price gap at scale

SES costs ~$0.10/1K emails vs. Resend at ~$1/1K — the gap compounds fast.

47%

Devs switch providers in year 1

Most migrations happen because the first choice didn't match growth trajectory.


Resend: developer-first, React Email native

Resend was built by the same team behind React Email, so the integration is seamless. You pass a React component directly to the send function — no render step, no HTML string, no Handlebars compilation.

lib/email/send-with-resend.ts
import { Resend } from "resend";
import { WelcomeEmail } from "@/emails/welcome";

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

export async function sendWelcomeEmail(user: {
  email: string;
  name: string;
}) {
  const { data, error } = await resend.emails.send({
    from: "App <hello@yourdomain.com>",
    to: user.email,
    subject: `Welcome, ${user.name}`,
    react: <WelcomeEmail name={user.name} />,
  });

  if (error) throw new Error(`Resend error: ${error.message}`);
  return data;
}

The API returns structured errors, supports batch sending, and provides webhook events for delivery tracking. The dashboard shows rendered previews of every email sent — invaluable for debugging template issues in production.

Where Resend shines

  • React Email native — pass JSX directly, no render-to-HTML step required.
  • TypeScript SDK — fully typed responses, autocomplete for every option.
  • Simple pricing — free tier includes 3,000 emails/month, paid plans start at $20/month.
  • Built-in email previews — see rendered HTML in the dashboard without additional tooling.

Where it gets tricky

  • Higher cost at volume — at 500K+ emails/month, the per-email cost is significantly higher than SES.
  • Younger ecosystem — fewer integrations than SendGrid, smaller community for edge-case troubleshooting.
  • No marketing email features — purely transactional. You'll need a separate tool for newsletters.

SendGrid: the enterprise workhorse

SendGrid (now Twilio SendGrid) has been the default choice for a decade. It handles both transactional and marketing email, offers a visual template editor, and has the most mature deliverability infrastructure of the three.

lib/email/send-with-sendgrid.ts
import sgMail from "@sendgrid/mail";
import { render } from "@react-email/components";
import { WelcomeEmail } from "@/emails/welcome";

sgMail.setApiKey(process.env.SENDGRID_API_KEY!);

export async function sendWelcomeEmail(user: {
  email: string;
  name: string;
}) {
  // SendGrid needs rendered HTML — no native React support
  const html = await render(<WelcomeEmail name={user.name} />);

  const [response] = await sgMail.send({
    from: "App <hello@yourdomain.com>",
    to: user.email,
    subject: `Welcome, ${user.name}`,
    html,
  });

  if (response.statusCode !== 202) {
    throw new Error(`SendGrid error: ${response.statusCode}`);
  }
}

The extra render() call is the main friction point. It's not a dealbreaker, but it means you need @react-email/components as a production dependency and you're managing the render lifecycle yourself.

Where SendGrid shines

  • Unified transactional + marketing — one platform for all email types, shared analytics.
  • Mature deliverability tools — dedicated IP pools, IP warmup automation, email validation API.
  • Extensive webhook events — delivery, open, click, bounce, spam report, unsubscribe — all with timestamps.
  • Enterprise support — SOC 2, HIPAA-eligible, dedicated account managers at higher tiers.

Where it gets tricky

  • Complex pricing — free tier is 100 emails/day. Paid plans are contact-based or volume-based. Easy to overshoot.
  • No React Email support — you render to HTML yourself. The SDK types are also less precise than Resend's.
  • Dashboard complexity — the UI has grown organically over years. Finding the right setting can take time.

Amazon SES: raw power, maximum control

Amazon SES is not an email platform — it's infrastructure. There's no template editor, no analytics dashboard, no drag-and-drop anything. What you get is an API that delivers email reliably at $0.10 per 1,000 messages.

lib/email/send-with-ses.ts
import { SESv2Client, SendEmailCommand } from "@aws-sdk/client-sesv2";
import { render } from "@react-email/components";
import { WelcomeEmail } from "@/emails/welcome";

const ses = new SESv2Client({ region: process.env.AWS_REGION });

export async function sendWelcomeEmail(user: {
  email: string;
  name: string;
}) {
  const html = await render(<WelcomeEmail name={user.name} />);

  await ses.send(
    new SendEmailCommand({
      FromEmailAddress: "App <hello@yourdomain.com>",
      Destination: { ToAddresses: [user.email] },
      Content: {
        Simple: {
          Subject: { Data: `Welcome, ${user.name}` },
          Body: { Html: { Data: html } },
        },
      },
    })
  );
}
SES requires domain verification and starts in sandbox mode. You must request production access before sending to unverified addresses. Plan 1-3 business days for approval.

Where SES shines

  • Unbeatable pricing — $0.10/1K emails. At 1M emails/month, you pay $100 vs. $800+ on Resend or SendGrid.
  • AWS ecosystem — native IAM, CloudWatch metrics, SNS notifications for bounces/complaints.
  • No rate limits that matter — production accounts start at 50K/day, easily increased to millions.
  • Configuration sets — per-use-case tracking with separate reputation metrics.

Where it gets tricky

  • No analytics dashboard — you build your own using CloudWatch, SNS, and a database.
  • Sandbox friction — new accounts are sandboxed. You must apply for production access and manage your own reputation.
  • Verbose SDK — the AWS SDK is powerful but ceremony-heavy. Simple sends require more boilerplate.

Side-by-side comparison

Resend
  • React Email native — pass JSX directly
  • Typed SDK with autocomplete
  • Built-in email previews in dashboard
  • Simple, predictable pricing tiers
  • Modern API design (REST + webhooks)
  • Best DX for React Email + Next.js projects
SendGrid / SES
  • Requires manual render() to HTML
  • SendGrid: mature but complex SDK types
  • SES: verbose AWS SDK with boilerplate
  • SendGrid: complex tiers. SES: cheapest at scale
  • SendGrid: full event webhooks. SES: SNS/CloudWatch
  • More configuration, but more control

Which provider should you choose?

Choose Resend when

  • You're building with React Email and want the smoothest DX.
  • Your volume is under 100K emails/month and you value simplicity over cost optimization.
  • You want built-in email previews and don't want to build monitoring infrastructure.

Choose SendGrid when

  • You need transactional and marketing email under one roof.
  • Enterprise compliance matters — SOC 2, HIPAA, dedicated IPs.
  • You're already in the Twilio ecosystem (SMS + email + voice).

Choose Amazon SES when

  • Cost is the primary driver and you're sending 500K+ emails/month.
  • You're already on AWS and want native IAM, CloudWatch, and SNS integration.
  • You have the engineering bandwidth to build your own analytics and monitoring layer.

Build a provider abstraction layer

Regardless of which provider you pick today, wrap it behind an interface. This lets you swap providers without touching business logic — and it's cheap insurance against vendor lock-in.

lib/email/provider.ts
import type { ReactElement } from "react";

export interface EmailProvider {
  send(params: {
    from: string;
    to: string | string[];
    subject: string;
    react: ReactElement;
  }): Promise<{ id: string }>;
}

// Factory pattern — switch via env variable
export function createEmailProvider(): EmailProvider {
  switch (process.env.EMAIL_PROVIDER) {
    case "resend":
      return new ResendProvider();
    case "sendgrid":
      return new SendGridProvider();
    case "ses":
      return new SESProvider();
    default:
      throw new Error(`Unknown email provider: ${process.env.EMAIL_PROVIDER}`);
  }
}
lib/email/providers/resend.ts
import { Resend } from "resend";
import type { EmailProvider } from "../provider";

export class ResendProvider implements EmailProvider {
  private client = new Resend(process.env.RESEND_API_KEY);

  async send(params: {
    from: string;
    to: string | string[];
    subject: string;
    react: ReactElement;
  }) {
    const { data, error } = await this.client.emails.send({
      from: params.from,
      to: Array.isArray(params.to) ? params.to : [params.to],
      subject: params.subject,
      react: params.react,
    });

    if (error) throw new Error(error.message);
    return { id: data!.id };
  }
}
Use environment variables to switch providers per environment. Run Resend in development (better DX), SES in production (lower cost). Your templates stay identical.

Next.js App Router integration patterns

All three providers work in Next.js API routes and Server Actions. The key difference is where rendering happens.

app/api/email/welcome/route.ts
import { NextResponse } from "next/server";
import { createEmailProvider } from "@/lib/email/provider";
import { WelcomeEmail } from "@/emails/welcome";

const provider = createEmailProvider();

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

  try {
    const result = await provider.send({
      from: "App <hello@yourdomain.com>",
      to: email,
      subject: `Welcome, ${name}`,
      react: <WelcomeEmail name={name} />,
    });

    return NextResponse.json({ id: result.id });
  } catch (error) {
    console.error("Email send failed:", error);
    return NextResponse.json(
      { error: "Failed to send email" },
      { status: 500 }
    );
  }
}

With the abstraction layer, your API routes don't import any provider-specific code. Swap Resend for SES by changing a single environment variable.


Real cost comparison at scale

Provider pricing pages are designed to make comparison difficult. Here's the real monthly cost at common SaaS volumes:

10K/mo

Early-stage SaaS

Resend: $20 | SendGrid: $20 | SES: $1. All three work fine here.

100K/mo

Growing SaaS

Resend: $80 | SendGrid: $50 | SES: $10. Cost differences start showing.

1M/mo

Scaling SaaS

Resend: $550 | SendGrid: $400 | SES: $100. SES wins on raw cost.

But cost isn't just the API bill. Factor in the engineering time to build analytics dashboards for SES, or the complexity of managing SendGrid's configuration. Resend's higher price buys you time — which might be worth more than $500/month at a startup.


Key takeaway

The bottom line:

  • Resend for React Email projects under 100K emails/month where DX is a priority.
  • SendGrid when you need transactional + marketing email, enterprise compliance, or Twilio integration.
  • Amazon SES for high-volume senders who want the lowest cost and are comfortable building their own tooling.
  • Always build a provider abstraction layer — migration is inevitable as your product grows.
  • Your templates are provider-agnostic. Well-structured React Email components work identically across all three.
R

React Emails Pro

Team

Building production-ready email templates with React Email. Writing about transactional email best practices, deliverability, and developer tooling.

Production-ready templates

Pick from 9 template packs built with React Email. One-time purchase, lifetime updates, tested across every major email client.

Browse all templates