Skip to content

email · side by side

Mailgun vs Resend for a Next.js app

Both fill the email slot, so a generated repo carries one or the other, never both. Every line below is read out of the two manifests.

Short answer

Pick Mailgun if

Teams that need EU data residency, or want suppression and analytics held by the provider instead of their own database. Also teams sending enough volume to care about dedicated IPs and warm-up support. And the pragmatic choice when an existing system already speaks Mailgun.

Pick Resend if

Product teams sending transactional mail: magic links, receipts, notifications. Templates are React components, reviewed in a pull request, not pasted into a vendor's WYSIWYG editor.

Side by side

Price, obligations, and the surface each one adds. No row is written by hand. This is manifest.yaml, rendered.

Mailgun compared with Resend on pricing, fit, trade-offs, required companions, environment variables, dependencies, MCP servers and repo footprint.
From the manifestOption AMailgunOption BResend
In one lineMailgun

The veteran sending API. EU data residency and suppression lists kept server-side.

Resend

Transactional email with React Email templates you review in a pull request.

PricingMailgun

Free plan: 100 emails a day and 1 custom domain. Basic starts at $15/month for 10,000 emails. Foundation is $35/month for 50,000 and Scale $90/month for 100,000. Logs are kept 1 day on Free and Basic, 5 days on Foundation and 30 days on Scale.

Resend

Free: 3,000 emails a month (100 a day) on up to 3 domains. Pro is $20/month for 50,000 emails and 10 domains. Every self-serve plan keeps 30 days of data. Dedicated IPs are a $30/month add-on on Scale.

Best forMailgun

Teams that need EU data residency, or want suppression and analytics held by the provider instead of their own database. Also teams sending enough volume to care about dedicated IPs and warm-up support. And the pragmatic choice when an existing system already speaks Mailgun.

Resend

Product teams sending transactional mail: magic links, receipts, notifications. Templates are React components, reviewed in a pull request, not pasted into a vendor's WYSIWYG editor.

Trade-offsVerbatim from the manifestMailgun
  • Region is chosen when the domain is created and cannot change. An EU domain must use api.eu.mailgun.net. The US host returns a 401 that reads like a bad key.
  • The API is form-encoded, not JSON, which is why the SDK needs form-data as a peer. It is an older API and it shows in places.
  • No React rendering on Mailgun's side. Templates are rendered to HTML in your app before sending. Mailgun's own templates are Handlebars stored in the dashboard, a different workflow this battery does not use.
  • No built-in idempotency key. A retried job can send twice unless you deduplicate, so this battery attaches a deterministic Message-Id and a custom variable.
  • Suppression lists live in Mailgun and are queryable through the API. No local table to maintain and one less thing to keep in sync.
Resend
  • Transactional first. There is a broadcast product, but if you need segmentation, drip campaigns and a marketing CRM, this is not that tool.
  • Shared IPs by default. Dedicated IPs are a paid add-on on the Scale plan, so on the shared pool your own sending hygiene matters more.
  • Data is kept 30 days. If you need to prove what you sent someone six months ago, log the message id at send time. The repo does.
  • React Email is the headline feature and a real one: templates are components, previewable locally, diffable in review. It also means your email templates are part of your build.
  • Fewer knobs than a full ESP: no server-side template variables, no A/B testing, limited routing rules. For transactional mail that is a simpler model, not a gap.
Required companionsAdded for you, with a reasonMailgun

Nothing. It stands on its own.

Resend

Nothing. It stands on its own.

Recommended alongsideSuggested, never added for youMailgun

Nothing suggested.

Resend
  • An ORM battery
Env vars you will manageEvery one documented in docs/onboard.mdMailgun

7 variables · 3 required

  • EMAIL_FROM
  • MAILGUN_API_KEY
  • MAILGUN_DOMAIN
  • EMAIL_OUTBOX_DIR
  • MAILGUN_REGION
  • MAILGUN_WEBHOOK_SIGNING_KEY
  • REPLY_TO
Resend

5 variables · 2 required

  • EMAIL_FROM
  • RESEND_API_KEY
  • EMAIL_OUTBOX_DIR
  • REPLY_TO
  • RESEND_WEBHOOK_SECRET
Dependencies addedMailgun
  • @react-email/render ^2.1.0
  • form-data ^4.0.6
  • mailgun.js ^14.0.1
  • react-email ^6.11.0
  • server-only ^0.0.1
Resend
  • @react-email/render ^2.1.0
  • react-email ^6.11.0
  • resend ^6.30.0
  • server-only ^0.0.1
  • svix ^2.5.0
MCP serversWritten into .mcp.jsonMailgun

None. No extra agent tools from this one.

Resend

None. No extra agent tools from this one.

Footprint in your repoMailgun

22 files, plus 3 injections into shared stack files

Resend

26 files, plus 3 injections into shared stack files

What changes in your repo

The paths each battery contributes, diffed. A path in the third list is written by both, so swapping rewrites that file rather than adding one.

Only with Mailgun (4)

  • scripts/1 file
    • email/1 file
      • suppressions.ts
  • src/3 files
    • app/1 file
      • api/1 file
        • webhooks/1 file
          • mailgun/1 file
            • route.ts
    • lib/2 files
      • email/2 files
        • templates/1 file
          • layout.tsx
        • mailgun.ts

Only with Resend (8)

  • src/2 files
    • app/1 file
      • api/1 file
        • webhooks/1 file
          • resend/1 file
            • route.ts
    • lib/1 file
      • email/1 file
        • resend.ts
  • variants/6 files
    • orm-drizzle/3 files
      • slots/1 file
        • db-schema.ts
      • src/2 files
        • db/1 file
          • email-schema.ts
        • lib/1 file
          • email/1 file
            • store.ts
    • orm-none/1 file
      • src/1 file
        • lib/1 file
          • email/1 file
            • store.ts
    • orm-prisma/2 files
      • slots/1 file
        • prisma-models.prisma
      • src/1 file
        • lib/1 file
          • email/1 file
            • store.ts

Same path, different implementation (18)

  • scripts/2 files
    • email/2 files
      • render-samples.ts
      • send-test.ts
  • src/12 files
    • lib/12 files
      • email/12 files
        • templates/6 files
          • magic-link.tsx
          • receipt.tsx
          • reset-password.tsx
          • theme.ts
          • verify-email.tsx
          • welcome.tsx
        • address.ts
        • index.ts
        • outbox.ts
        • retry.ts
        • suppression.ts
        • tags.ts
  • tests/2 files
    • unit/2 files
      • email-outbox.test.ts
      • email.test.ts
  • variants/2 files
    • errors-none/1 file
      • src/1 file
        • lib/1 file
          • email/1 file
            • observability.ts
    • errors-sentry/1 file
      • src/1 file
        • lib/1 file
          • email/1 file
            • observability.ts

Shared stack files Mailgun injects into

  • env-required
  • legal-processors
  • verify-checks

Shared stack files Resend injects into

  • env-required
  • legal-processors
  • verify-checks

Mailgun in your .env.local

.env.localbash10 lines
# required
EMAIL_FROM=my-app <hello@mail.example.com>
MAILGUN_API_KEY=key-replace_me
MAILGUN_DOMAIN=mail.example.com

# optional
EMAIL_OUTBOX_DIR=
MAILGUN_REGION=us
MAILGUN_WEBHOOK_SIGNING_KEY=whsk-replace_me
REPLY_TO=support@example.com

Resend in your .env.local

.env.localbash8 lines
# required
EMAIL_FROM=my-app <hello@mail.example.com>
RESEND_API_KEY=re_replace_me

# optional
EMAIL_OUTBOX_DIR=
REPLY_TO=support@example.com
RESEND_WEBHOOK_SECRET=whsec_replace_me

What changes for your agents

Each battery ships rules, skills, subagents and hooks that an agent loads before it touches the code that battery owns. Picking one is also picking how your agents behave in src/lib/email/**.

Mailgun

  • 3

    Skills

  • 1

    Rules

  • 5

    Solution docs

Rules (1)

One send function, a verified domain, a reply-to, and no secrets in the body

src/lib/email/** · src/app/api/webhooks/mailgun/** · src/lib/auth/** · src/lib/billing/**

Skills (3)

/add-email-template

Add a React Email template on the shared layout, preview it, wire it into a sendEmail call, and confirm it renders as HTML and as text in a real inbox.

/diagnose-email-delivery

Work out why a Mailgun message did not arrive (region, key, suppression, authentication or placement) in the order that finds it fastest.

/test-email

Prove a send actually works: check the region and domain, send one of each template, read the Mailgun logs, inspect the suppression lists and confirm placement in a real inbox.

Subagents and hooks

None of its own. The foundation agents and guard hooks still ship.

Resend

  • 2

    Skills

  • 1

    Rules

  • 5

    Solution docs

Rules (1)

Every email goes through sendEmail, from a verified domain, with a reply-to

src/lib/email/** · src/app/api/webhooks/resend/** · src/lib/auth/** · src/lib/billing/**

Skills (2)

/add-email-template

Add a React Email template, preview it, wire it into a send, and check it renders and lands in a real inbox.

/preview-and-test-email

Diagnose an email problem (not sending, landing in spam, rendering wrong) in the order that finds the cause fastest.

Subagents and hooks

None of its own. The foundation agents and guard hooks still ship.

What each one already knows

Solution docs land in docs/solutions/ in your repo and are published here, so you can read the failure modes before you commit.

Mailgun (5)

Resend (5)

Which one to pick

From meta.bestFor and meta.tradeoffs. If a claim is not in the manifest, it is not on this page.

Pick Mailgun when

Teams that need EU data residency, or want suppression and analytics held by the provider instead of their own database. Also teams sending enough volume to care about dedicated IPs and warm-up support. And the pragmatic choice when an existing system already speaks Mailgun.

And accept that(5)
  • Region is chosen when the domain is created and cannot change. An EU domain must use api.eu.mailgun.net. The US host returns a 401 that reads like a bad key.
  • The API is form-encoded, not JSON, which is why the SDK needs form-data as a peer. It is an older API and it shows in places.
  • No React rendering on Mailgun's side. Templates are rendered to HTML in your app before sending. Mailgun's own templates are Handlebars stored in the dashboard, a different workflow this battery does not use.
  • No built-in idempotency key. A retried job can send twice unless you deduplicate, so this battery attaches a deterministic Message-Id and a custom variable.
  • Suppression lists live in Mailgun and are queryable through the API. No local table to maintain and one less thing to keep in sync.

Pick Resend when

Product teams sending transactional mail: magic links, receipts, notifications. Templates are React components, reviewed in a pull request, not pasted into a vendor's WYSIWYG editor.

And accept that(5)
  • Transactional first. There is a broadcast product, but if you need segmentation, drip campaigns and a marketing CRM, this is not that tool.
  • Shared IPs by default. Dedicated IPs are a paid add-on on the Scale plan, so on the shared pool your own sending hygiene matters more.
  • Data is kept 30 days. If you need to prove what you sent someone six months ago, log the message id at send time. The repo does.
  • React Email is the headline feature and a real one: templates are components, previewable locally, diffable in review. It also means your email templates are part of your build.
  • Fewer knobs than a full ESP: no server-side template variables, no A/B testing, limited routing rules. For transactional mail that is a simpler model, not a gap.

Questions people actually ask

Should I choose Mailgun or Resend?
Mailgun is best for teams that need EU data residency, or want suppression and analytics held by the provider instead of their own database. Also teams sending enough volume to care about dedicated IPs and warm-up support. And the pragmatic choice when an existing system already speaks Mailgun. Resend is best for product teams sending transactional mail: magic links, receipts, notifications. Templates are React components, reviewed in a pull request, not pasted into a vendor's WYSIWYG editor. Both fill the email slot, so a generated repo carries one or the other, never both.
How much do Mailgun and Resend cost?
Mailgun: Free plan: 100 emails a day and 1 custom domain. Basic starts at $15/month for 10,000 emails. Foundation is $35/month for 50,000 and Scale $90/month for 100,000. Logs are kept 1 day on Free and Basic, 5 days on Foundation and 30 days on Scale. Resend: Free: 3,000 emails a month (100 a day) on up to 3 domains. Pro is $20/month for 50,000 emails and 10 domains. Every self-serve plan keeps 30 days of data. Dedicated IPs are a $30/month add-on on Scale.
What changes in my repo if I switch from Mailgun to Resend?
Mailgun writes 22 files, 7 environment variables and 5 dependencies, and installs 1 path-scoped rule, 3 skills and 5 solution docs. Resend writes 26 files, 5 environment variables and 5 dependencies, and installs 1 path-scoped rule, 2 skills and 5 solution docs.

Decide once, then build the repo that already knows the decision.

Either way you get that choice’s rules, skills and solution docs installed, plus the guard hooks, an onboarding doc for exactly these env vars, and the Compound Engineering loop. Free and MIT.