In-depth two-product comparison
Resend vs Mailgun
Resend vs Mailgun for SaaS developers: monthly cost at 10k, 50k, 100k and 500k emails, webhooks, inbound, templates and how to switch either way.
Last updated: 2026-10-07
Short answer
For a SaaS team that sends product email through an API, Resend is the better default: at 50,000 emails a month Resend Pro is $20 against $35 for Mailgun Foundation, at 100,000 it is $35 against $75, and its Free plan covers 3,000 emails a month for development. Mailgun is the better pick when you need address validation, inbound routing rules, a longer log window (30 days on Scale) or a US and EU option on one account. At 10,000 emails Mailgun is the cheaper start ($15 against Resend Pro's $20), and at 100,000 on the top plans the two cost the same $90 a month, so the decision there is features rather than price.
Resend
A flat price ladder that undercuts Mailgun between 50,000 and 500,000 emails, with batch, templates, receiving and a Marketing tab on one account.
Mailgun
The wider developer toolbox: routes, validation, per-domain SMTP credentials, long log retention on Scale and a documented US or EU choice.
Resend is the younger product and Mailgun the broader one, but the useful comparison is narrower than that. Both send over an API or SMTP, both sign webhooks, both can receive mail. The difference is who writes the glue code: Resend assumes you will fetch an inbound message body with a second API call and handle suppressions in its dashboard, while Mailgun lets you declare routing and validation rules inside the platform. Decide which of those two habits suits your team.
Quick comparison
| Decision area | Resend | Mailgun | What to verify |
|---|---|---|---|
| Free plan | 3,000 emails a month, 100 a day, 3 domains; also 1,000 marketing contacts with 10,000 automation runs | 100 emails a day, one custom sending domain, one day of log retention | Resend counts both sent and received emails toward its quotas, so test receiving on a free account before you plan around the 3,000. |
| Price at 10,000 emails | Pro $20 a month (the plan includes 50,000) | Basic from $15 a month (includes 10,000) | At 10,000 emails you use a fifth of the 50,000 that Pro includes, so you are paying for headroom; check whether you will grow into it within the year. |
| Price at 50,000 emails | Pro $20 a month | Foundation $35 a month after a free first month | Foundation includes five days of log retention, so compare that against the debugging window your support team needs. |
| Price at 100,000 emails | Pro $35 or Scale $90 a month | Foundation $75 or Scale $90 a month | At $90 the Scale plans match, so compare their feature lists (domains, retention, support channel) rather than the sticker. |
| Price at 500,000 emails | Scale $350 a month | Scale $400 a month | Add the add-ons you need: Resend dedicated IP $30 a month, Mailgun extra IP $59 a month after the one included. |
| Batch sending | Up to 100 emails per call; attachments are not supported in the batch endpoint | Up to 1,000 recipients per call with recipient-variables for per-person values | Count how many distinct messages you fan out in a burst; 100 per call means ten calls where Mailgun needs one. |
| Webhook authentication | Svix headers (svix-id, svix-timestamp, svix-signature) checked by the Resend SDK or the Svix package | HMAC-SHA256 over timestamp and token with an account-wide signing key | Both need the raw request body for verification; check that your framework does not parse it first. |
| Inbound email | Receive at a verified domain or an id.resend.app address; webhook notifies you and you fetch body and attachments by API | Routes with priority, filters and forward, store or stop actions; forward posts the parsed message to your URL | Send a reply with an attachment to each and compare how much of the message you receive without a second call. |
| Address validation | Not listed on the pricing page | Single and bulk validation; 5,000 included on Scale | If bad signup addresses are your problem, price Mailgun validation separately even if you send with Resend. |
| Marketing sends | Marketing tab: free at 1,000 contacts, $40 at 5,000, $80 at 10,000, $180 at 25,000, $250 at 50,000, $450 at 100,000 | The pricing page sells sending plans only; campaign tooling is not listed there | If you want newsletters and product announcements from the same vendor, only Resend lists a contact-based plan. |
The Resend ladder in the table comes from its pricing page sliders (Pro reads $20 at 50,000 and $35 at 100,000, Scale reads $90 at 100,000 and $160 at 200,000), and Mailgun's from its plan cards and volume readouts. They line up at 50,000, 100,000 and 500,000 but not at 200,000 or 250,000, so no row compares those. Do not infer a price between two published points; the sliders step rather than scale linearly.
Feature comparison
| Feature | Resend | Mailgun |
|---|---|---|
| Request shape | POST https://api.resend.com/emails with a Bearer API key; from, to and subject are required, and to takes up to 50 addresses. | A messages call per sending domain; you must provide text, html, amp-html or template, and o:, h:, v: and t: options are limited to 16 KB combined. |
| Retry safety | An optional Idempotency-Key header prevents duplicate emails; keys are valid for 24 hours and may be up to 256 characters. | The send documentation read for this page describes no idempotency header, so retries need a send ledger in your own database. |
| Scheduling | scheduled_at accepts a natural-language value such as in 1 min or an ISO 8601 timestamp. | SMTP scheduling headers allow delivery up to 3 or 7 days ahead depending on billing plan; X-Mailgun-Deliver-Within accepts 5m to 24h. |
| Rate limit | 10 requests per second per team by default, across all API keys; ratelimit-limit, ratelimit-remaining, ratelimit-reset and retry-after headers are returned. | Throughput is not stated on the pages this comparison relies on; ask Mailgun for the limit that applies to your plan. |
| SDKs | Official SDKs for Node.js, PHP, Laravel, Python, Ruby, Go, Java, Rust and .NET; community ones for Elixir, NestJS and Dart. | SDK pages for Go, Node.js, PHP, Java, Ruby and Python. |
| Stored templates | Pass template with an id or alias plus variables; html, text or react may not be combined with a template. FIRST_NAME, LAST_NAME, EMAIL and UNSUBSCRIBE_URL are reserved variable names. | Handlebars with if, unless, each, with and equal helpers; send with the template name and t:variables. Capped at 100 templates and 40 versions per template. |
| React components | The react parameter works in the Node.js SDK only. | Render React Email or MJML to HTML in your own code first. |
| Per-recipient values in bulk | Send an array of up to 100 message objects to the batch endpoint, each with its own to and variables. | recipient-variables is a JSON object keyed by address, referenced with %recipient.key%; without it every recipient sees every address. |
| Event types | email.sent, delivered, delivery_delayed, bounced, complained, opened, clicked, failed, scheduled, suppressed and received, plus domain, contact, suppression and topic events. | accepted, delivered, temporary_fail, permanent_fail, opened, clicked, unsubscribed and complained. |
| Webhook retries | Eight attempts with exponential backoff: immediately, then 5 seconds, 5 minutes, 30 minutes, 2 hours, 5 hours and 10 hours twice. Failed or succeeded messages can be replayed from the dashboard, and a persistently failing endpoint is disabled with an email to your team. | A 200 stops delivery and a 406 rejects without a retry; anything else is retried over 8 hours. |
| Suppressions | A team-wide list with origins bounce, complaint and manual; hard bounces and complaints add addresses automatically, and the API adds or removes up to 100 addresses per call. | Separate bounce, complaint and unsubscribe lists per domain, plus an allowlist; CSV import up to 25 MB. |
| Validation | No validation endpoint is listed on the pricing or docs pages read. | Single address check returns result and risk, with a did_you_mean typo hint and flags for disposable and role addresses. |
Pricing and operating cost
Resend
Free plan: 3,000 emails a month, 100 a day and 3 domains, plus a free Marketing plan with 1,000 contacts, unlimited broadcasts, 10,000 automation runs and 3 segments.
Both sent and received emails count toward quotas, so a busy inbound address eats into the 3,000 free or the plan allowance. Dedicated IPs ($30 a month) are available only to Scale customers exceeding 3,000 emails a day, and SSO costs $150 a month. More than 3 domains on Free needs a paid plan; extra domains are $20 a month for 100 more. The batch endpoint takes 100 messages without attachments, so large fan-outs need several calls under the 10-requests-per-second limit.
Mailgun
Free plan: 100 emails a day, one custom sending domain and one day of log retention. Foundation and Scale come with a free first month.
Log history is the plan lever: only the Scale plan reaches 30 days, so the cheaper plans are a poor fit for a team that debugs delivery problems weeks later. Checking more than the 5,000 addresses included on Scale costs extra, at the per-100 validation rates on Mailgun's pricing page. A second dedicated IP is $59 a month, and a template built for the US region has to be rebuilt for the EU one. Extra emails are quoted as 'from' rates, so ask what a spike month is billed at before you rely on the headline.
| Tier or usage case | Resend | Mailgun |
|---|---|---|
| Free | $0 Daily cap of 100 emails; hitting it returns a daily_quota_exceeded 429 that resets at midnight UTC. | $0 One day of log retention and one custom domain. |
| 10,000 emails a month | Pro $20 Pro includes 50,000, so you pay for headroom; extra emails are $0.90 per 1,000. | Basic from $15 Basic includes 10,000 and bills extra emails from $1.80 per 1,000. |
| 50,000 emails a month | Pro $20 Yearly total $240. | Foundation $35 Yearly total $420; five days of log retention. |
| 100,000 emails a month | Pro $35, Scale $90 Scale lists 1,000 domains and a dedicated Slack channel; SSO ($150 a month) and dedicated IP ($30 a month) are add-ons. | Foundation $75, Scale $90 Scale adds 30 days of logs, 5,000 validations and one dedicated IP. |
| 500,000 emails a month | Scale $350 Slider readout shows extra emails at $0.70 per 1,000; yearly total $4,200. | Scale $400 Yearly total $4,800; one dedicated IP included, extra IPs $59 a month. |
Monthly cost at 10,000, 50,000, 100,000 and 500,000 emails
At 10,000 emails, Mailgun is $5 a month cheaper. Its pricing page lists Basic from $15 with 10,000 emails, and Resend's pricing page starts paid sending at Pro, $20 a month with 50,000 emails. That is $180 a year against $240. Resend's Free plan does cover 3,000 emails a month, so a pre-launch product can stay at $0 longer than on Mailgun, where Free is 100 a day.
At 50,000 the order reverses. Resend Pro is $20 a month ($240 a year) and Mailgun Foundation is $35 ($420 a year), a $15 monthly gap. Foundation's first month is free, which trims the first-year total to $385, still $145 above Resend. At 100,000 emails Resend Pro reads $35 ($420 a year) and Mailgun Foundation reads $75 ($900 a year), a $40 gap. If you take the higher plan on both, Scale is $90 a month on each, which is $1,080 a year, so that comparison is about features alone.
At 500,000 emails Resend Scale reads $350 ($4,200 a year) and Mailgun Scale $400 ($4,800), a $50 monthly gap and $600 a year. Add the extras you need: a Resend dedicated IP is $30 a month, so $380, while Mailgun Scale already includes one IP at $400. Resend's slider also shows extra emails at $0.70 per 1,000 at this stop, while Mailgun lists Scale overage from $1.10 per 1,000. Between published points, neither vendor gives a rule that supports a price, so ask for a quote when your volume sits between two readouts.
The send call: idempotency, batches, templates and limits
Resend's send email reference is a single JSON POST. It supports an Idempotency-Key header (unique per request, valid for 24 hours, up to 256 characters) so a retried request will not send twice, scheduled_at in natural language or ISO 8601, tags as name and value pairs, and attachments up to 40 MB per email after Base64 encoding. A template is sent by id or alias with variables; passing html, text or react alongside a template is a validation error, and a missing variable fails the call. The batch endpoint accepts up to 100 emails but does not support attachments yet. The default rate limit is 10 requests per second per team.
Mailgun handles bulk differently. Its batch sending guide allows up to 1,000 recipients in one call when you pair the to list with recipient-variables, a JSON object keyed by address that you reference as %recipient.name% inside the message. Without it, everyone sees everyone else's address. Stored templates use Handlebars with if, unless, each, with and equal helpers, and the templates guide caps an account at 100 templates and 40 versions each, created separately per region.
For a product that sends one message per event (a receipt, an invite), both calls are one request and the difference is minor. For a nightly digest to 30,000 users, Mailgun needs 30 calls with 1,000 recipients each, while Resend's batch endpoint needs 300 calls of 100, which at the default limit of 10 requests per second is at least 30 seconds of calls.
Webhooks: Svix headers against an HMAC token
Resend webhooks are signed with Svix. Per its verification guide, each request carries svix-id, svix-timestamp and svix-signature headers; you pass them with the raw body and the endpoint's signing secret to the Resend SDK's verify method or to the Svix package, and it throws when the signature is wrong. The guide warns that parsing JSON and re-serialising the body breaks the check. The retry schedule has eight attempts, any message can be replayed from the dashboard, and a persistently failing endpoint is switched off with an email to your team.
Mailgun's signature scheme sends a 50-character token, a timestamp and a hex signature inside a signature object next to the event data; you join timestamp and token and compute an HMAC-SHA256 with the account-wide Webhook Signing Key. A 200 response ends delivery, a 406 rejects it without retrying, and any other response is retried over 8 hours per the retry page.
In practice the Resend flow is quicker to write, because one library call does the verification and the dashboard replay button helps during development. The Mailgun flow is a short function you own, and its 8-hour retry window is enough to cover a long deploy outage. Resend's schedule stretches over more than 27 hours (5 seconds, 5 minutes, 30 minutes, 2 hours, 5 hours, then 10 hours twice), so a weekend-long outage is the case that needs a replay on either side.
Receiving mail: fetch it yourself or route it on arrival
Resend's receiving documentation offers two addresses: any username at a Resend-managed id.resend.app subdomain, or any address at your own domain once you add an MX record. Resend parses each message and posts to the endpoint you chose; the full HTML, text and headers are retrieved afterwards through the API, and attachments come as metadata with temporary download URLs. Forwarding and threaded replies are supported. The numeric limits (size, retention) are not on that page, so read the linked guides before you plan around large attachments.
Mailgun's routes evaluate every incoming message against ordered filters (match_recipient, match_header, catch_all) and run actions: forward to an address or URL, store, or stop. Priority runs lowest first, and a stop prevents later routes from firing. A forward to a URL ending in json arrives as application/json with the signature fields moved into headers; any other URL gets form data.
Choose Resend receiving when inbound is a single feature (parse a reply, attach it to a record) and you are happy to make a second call for the body. Choose Mailgun routes when different addresses mean different behaviour and you want the dispatcher outside your own code.
Validation, suppressions and a marketing tab
Mailgun's validation API returns deliverable, undeliverable, do_not_send, catch_all or unknown with a risk level, and flags disposable and role-based addresses. Resend lists no validation endpoint, so a team that needs to filter signups either keeps a separate validator or leans on Resend's automatic suppression. The suppression list is team-wide across domains and applies to both transactional and broadcast mail; hard bounces and spam complaints land there automatically, you can add addresses manually or by CSV, and a webhook fires on suppression.added, suppression.removed and email.suppressed. Removing an address does not guarantee delivery, because a second bounce re-adds it.
Resend's Marketing tab prices broadcasts and automations by contact count: free at 1,000 contacts with 10,000 automation runs and 3 segments, then $40 at 5,000, $80 at 10,000, $180 at 25,000, $250 at 50,000 and $450 at 100,000. Its broadcasts documentation sends to a segment of your contacts and tracks delivery, click and open rates when tracking is enabled. A SaaS that wants product announcements next to transactional mail can stay on one vendor; Mailgun's pricing page lists sending plans, so an announcement tool there would be a separate purchase.
Moving from Mailgun to Resend
Keep a validation step if you used Mailgun's: nothing on Resend's pricing page replaces it. Run both vendors in parallel on a low-risk stream, such as password resets, before moving billing and invite email.
- Add your sending domain in Resend and publish its DNS records. Resend recommends sending from subdomains to isolate reputation, so use one for transactional mail and another for newsletters.
- Create an API key. If you used Mailgun SMTP, switch to smtp.resend.com with the username resend and the API key as the password, on ports 25, 465, 587, 2465 or 2587.
- Map the payload: Mailgun o:tag values become tags as name and value pairs (ASCII letters, digits, underscores and dashes), v: variables become template variables, and recipient-variables batches become arrays of up to 100 message objects. The batch endpoint rejects attachments, so send those individually.
- Rebuild Handlebars templates as Resend templates, taking care that FIRST_NAME, LAST_NAME, EMAIL and UNSUBSCRIBE_URL are reserved variable names. A message that uses a template cannot also pass html, text or react.
- Replace the HMAC check with Svix verification using the raw body, and map Mailgun events to Resend ones: delivered to email.delivered, temporary_fail to email.delivery_delayed, permanent_fail to email.bounced, complained to email.complained.
- Replace routes with receiving: add the MX record (or use an id.resend.app address), subscribe to email.received, then fetch the body and attachments by API. Port any priority or catch-all logic into your handler.
- Export Mailgun's bounce, complaint and unsubscribe lists and add them to Resend's suppression list, up to 100 addresses per API call. Add an Idempotency-Key to retried sends and respect the 10 requests per second limit during bulk jobs.
Moving from Resend to Mailgun
- Choose US or EU for the account, add each sending domain and publish the DNS records Mailgun gives you. Create the templates in the same region you will send from.
- For SMTP use smtp.mailgun.org with per-domain credentials; port 587 is Mailgun's suggestion. For the API, use the key against the messages endpoint for the domain.
- Render React Email components to HTML in your own code, because Mailgun has no react parameter, then upload templates as Handlebars or send HTML directly. Mind the 100-template and 40-version cap.
- Convert Resend batches of 100 messages into one call with up to 1,000 recipients and recipient-variables, if the message body is the same. If every message differs, keep one call per message.
- Replace Svix verification with the HMAC check: join timestamp and token and compare with the signature. Map email.bounced to permanent_fail, email.delivery_delayed to temporary_fail and email.complained to complained.
- Rebuild receiving as routes, with a forward action to your URL and a stop on specific recipients. Export Resend's suppression list and import unsubscribes and complaints by CSV (up to 25 MB).
- Add your own send ledger: the Mailgun send documentation read for this comparison describes no idempotency header, so retries must be made safe in your database. If you also used Resend's Marketing tab, plan a separate home for contacts and broadcasts, since Mailgun's pricing page lists sending plans only.
Pros and cons
Resend
Pros
- $20 at 50,000 emails and $35 at 100,000, against $35 and $75 for Mailgun.
- A real Free plan: 3,000 emails a month, plus 1,000 marketing contacts.
- Idempotency-Key, scheduled_at and template variables on the send call.
- Svix-signed webhooks with dashboard replay and automatic endpoint disabling.
- Broadcasts, segments and automations on the Marketing tab, priced by contacts.
Cons
- No validation endpoint listed on the pricing page.
- Inbound bodies need a second API call; no routing rules.
- The batch endpoint takes 100 messages and no attachments, under a 10-requests-per-second team limit.
- Dedicated IP ($30 a month) only for Scale customers above 3,000 emails a day.
Mailgun
Pros
- Routes for inbound mail with priorities, filters and forward, store or stop actions.
- Address validation with risk levels, 5,000 included on Scale.
- Batches of up to 1,000 recipients per call.
- Logs up to 30 days on Scale, and a US or EU choice on one account.
- Cheaper than Resend at 10,000 emails ($15 against $20).
Cons
- $35 at 50,000 and $75 at 100,000 on Foundation, above Resend Pro.
- Short log history below Scale: a day on Free and Basic, five days on Foundation.
- Stored templates stop at 100 templates and 40 versions each, and regions do not share them.
- No idempotency header described in the send documentation read.
Who should choose what?
Seed-stage SaaS on a React stack, 5,000 to 50,000 emails a month
Receipts, invites and password resets from a Next.js backend, with one developer managing email.
Recommendation: Resend. Resend Free covers 3,000 emails a month for development and Pro is $20 for up to 50,000, against $35 on Foundation. The Idempotency-Key header and Svix verification save glue code that a small team would otherwise own.
Signup-heavy product with fake and mistyped addresses
A free trial where a share of registrations use bad addresses and bounces hurt your sending reputation.
Recommendation: Mailgun. Mailgun validation returns a risk level, flags disposable and role addresses and suggests typo fixes. Scale includes 5,000 checks; Resend lists no equivalent, so you would add a second vendor.
Product announcements to 10,000 contacts alongside transactional mail
You want one invoice for receipts and a monthly update to your customer base.
Recommendation: Resend. Resend's Marketing tab reads $80 at 10,000 contacts, with segments and broadcasts, next to Pro sending. Mailgun's pricing page sells sending only, so campaigns would be a separate tool.
Inbound mail routed to different systems by address
support@ goes to a helpdesk, invoices@ to a parser and everything else to a store.
Recommendation: Mailgun. Mailgun routes apply ordered filters and forward, store or stop, so the dispatching is configuration. Resend delivers each message to one webhook and you fetch the body by API.
EU customers with a data-residency question in the contract
A procurement form asks where email data is processed.
Recommendation: Mailgun. Mailgun's pricing page states that you can send through the US or the EU from a single account. Resend's domain documentation lets you pick a sending region for speed, but the pages read for this comparison make no data-hosting statement, so you would need its DPA.
Steady 500,000 emails a month with a dedicated IP
A notification platform that has outgrown shared IPs.
Recommendation: Resend. Resend Scale at 500,000 is $350 plus $30 for a dedicated IP, which is $380; Mailgun Scale is $400 with one IP included. The $20 monthly saving is real but modest, so rank retention and validation higher if you need them.
Each scenario names the one capability that decided it. If your own situation is not in the list, look for that deciding capability first: validation, routing, retention, region, or marketing sends.
Final verdict
Choose Resend if
- you send between 50,000 and 500,000 emails a month and want the lower published price
- your team writes in Node, Python, Go or another language with an official Resend SDK
- you want Idempotency-Key, scheduled_at and template variables on one JSON call
- marketing broadcasts priced by contact count should sit next to transactional mail
Choose Mailgun if
- you validate addresses at signup and want it from your sending vendor
- inbound mail needs routing rules instead of one webhook
- your support team needs logs older than a day or five days, and you are on Scale
- contracts require a documented US or EU sending choice
FAQ
Is Resend cheaper than Mailgun?
At 50,000 emails Resend Pro is $20 a month and Mailgun Foundation is $35; at 100,000 it is $35 against $75; at 500,000 Resend Scale is $350 against $400. At 10,000 emails Mailgun Basic from $15 beats Resend Pro at $20, and at 100,000 on the top plans both charge $90.
Does Resend support SMTP and inbound email?
Yes to both. Its SMTP server is smtp.resend.com with the username resend and your API key as the password, on ports 25, 465, 587, 2465 or 2587. Receiving works through a Resend-managed address or an MX record on your own domain, with a webhook on arrival and the message body retrieved by API.
Which one signs its webhooks?
Both. Resend uses Svix headers and a verify call in its SDK; Mailgun uses an HMAC-SHA256 signature over a timestamp and token with an account-wide signing key. Both documentation pages say to verify against the raw request body.
Does Mailgun have anything like Resend's Marketing tab?
Not on the pricing page: Mailgun lists sending plans (Free, Basic, Foundation, Scale) and validation, not contact-based campaign plans. Resend's Marketing tab is free at 1,000 contacts and reads $40 at 5,000 and $80 at 10,000.
What changes most when migrating between them?
Templates (Handlebars against Resend's variable-based templates), webhook verification, and inbound handling. Resend's batch endpoint takes 100 messages without attachments while Mailgun takes 1,000 recipients with recipient-variables, so bulk jobs need reshaping too.