---
title: Migrating from Mailgun to AWS SES: Step-by-Step Guide
description: Technical migration guide from Mailgun to AWS SES. Email deliverability, SMTP, configuration, and cost comparison.
url: https://www.factualminds.com/compare/mailgun-to-aws-ses/
publishDate: 2025-06-01
updateDate: 2026-06-16
---

# Migrating from Mailgun to AWS SES: Step-by-Step Guide

> Step-by-step migration guide for engineers moving off Mailgun — pricing breakdown, routing rule equivalents, GDPR-compliant EU region setup, and SNS-based webhook event handling.

<div class="quick-answer">

**Quick Answer:** AWS SES is usually the better fit when you need lower unit email cost, AWS-native eventing, and tighter deliverability controls at scale.

</div>

## Freshness Check (June 2026)

In this cycle, SES updates include tenant-level suppression lists, inbox placement metrics, and email validation capabilities that materially improve migration operating models.

This page was refreshed against official AWS announcements and service documentation published in the last 12 months. Confirm region support, quotas, and pricing before final architecture sign-off.

- [AWS What's New](https://aws.amazon.com/about-aws/whats-new/)
- [SES announcement updates](https://aws.amazon.com/about-aws/whats-new/2026/06/amazon-ses-tenant-level-suppression-lists/)

Mailgun has long been the go-to for developers who want a clean REST API and minimal operational overhead. AWS SES is where teams land when they prioritize cost efficiency and AWS ecosystem consolidation over out-of-the-box simplicity. If your stack is already on AWS, the migration pays for itself quickly.

## Related Comparisons

Explore other technical comparisons:

- [SendGrid to AWS SES](/compare/sendgrid-to-aws-ses/)
- [Postmark to AWS SES](/compare/postmark-to-aws-ses/)

## Why Teams Migrate from Mailgun to SES

Cost is the immediate trigger for most migrations. Mailgun's pricing model has shifted over the years — the legacy free tier is gone, and the current Foundation plan carries a meaningful monthly fixed cost.

| Volume         | Mailgun Foundation   | Mailgun Flex  | AWS SES      | SES Savings vs Foundation |
| -------------- | -------------------- | ------------- | ------------ | ------------------------- |
| 10,000 emails  | $35/month (included) | $8.00/month   | $1.00/month  | $34.00                    |
| 50,000 emails  | $35/month (included) | $40.00/month  | $5.00/month  | $30.00                    |
| 100,000 emails | $80/month (overage)  | $80.00/month  | $10.00/month | $70.00                    |
| 500,000 emails | ~$360/month          | $400.00/month | $50.00/month | $310.00                   |

Mailgun Flex charges $0.80 per 1,000 emails with no monthly minimum — 8x the SES rate. Even the Foundation plan's included 50,000 emails work out to $0.70 per 1,000 against SES's $0.10.

## API Migration: Mailgun → AWS SES

Mailgun's Messages API and SES share the same conceptual model but use different request formats.

| Mailgun API                             | AWS SES Equivalent                              | Notes                             |
| --------------------------------------- | ----------------------------------------------- | --------------------------------- |
| `POST /v3/{domain}/messages`            | `SendEmail` / `SendRawEmail`                    | Core send operation               |
| `from`, `to`, `subject`, `text`, `html` | `Source`, `Destination`, `Message`              | Direct field mapping              |
| API Key (Basic auth)                    | IAM access key or SES SMTP credentials          | Use IAM for AWS-native apps       |
| SMTP (smtp.mailgun.org, port 587)       | `email-smtp.[region].amazonaws.com`, port 587   | Drop-in SMTP replacement          |
| `h:X-Mailgun-*` custom headers          | `MessageAttributes` or raw message headers      | Same capability, different syntax |
| `o:tag` message tags                    | Configuration Set tags + SNS message attributes | Equivalent for event filtering    |

If your application uses SMTP, the migration is a credentials swap. Update the SMTP host to `email-smtp.us-east-1.amazonaws.com` (or your chosen region), generate SES SMTP credentials from the SES console, and update your application config. No code changes required.

## Routing Rules: Mailgun Routes → SES Receipt Rules

Mailgun Routes allow you to match inbound email by recipient pattern or domain and forward, store, or call a webhook. SES Receipt Rules provide the same capabilities with deeper AWS integration.

**Mailgun route (example):**

```
match_recipient(".*@support.example.com")
→ forward("https://app.example.com/inbound")
→ store(notify="https://app.example.com/stored")
```

**SES Receipt Rule equivalent:**

1. Create a Rule Set in SES Receipt Rules
2. Add a rule matching `@support.example.com`
3. Set actions: Publish to SNS → Lambda function that calls your webhook endpoint

The SNS → Lambda pattern is more verbose to configure but gives you full programmatic control over inbound email routing, parsing, and processing. For teams comfortable with Lambda, it is the more powerful option.

## EU Data Residency: Mailgun EU → SES eu-west-1

Mailgun offers a dedicated EU region (api.eu.mailgun.net) to keep email data within Europe for GDPR compliance. SES supports the same via region selection.

**SES regions for EU data residency:**

- `eu-west-1` (Ireland) — primary EU region, lowest latency to EU users
- `eu-central-1` (Frankfurt) — German data residency if required
- `eu-west-2` (London) — UK data residency post-Brexit

Configure your SES SDK client to use the appropriate regional endpoint. DNS verification records (DKIM CNAME, MAIL FROM MX) must be added for each region separately — you cannot share verification between SES regions.

## Webhook Event Format Mapping

Mailgun fires webhook POSTs to your endpoint for each email event. SES routes events through SNS, which then calls your HTTP endpoint (via SNS HTTP subscription) or Lambda.

| Mailgun Event        | SES/SNS Event Type   | Key Fields                                                                  |
| -------------------- | -------------------- | --------------------------------------------------------------------------- |
| `delivered`          | `Delivery`           | `timestamp`, `recipients[]`, `smtpResponse`, `reportingMTA`                 |
| `failed` (permanent) | `Bounce` (Hard)      | `bounceType: "Permanent"`, `bouncedRecipients[].emailAddress`, `timestamp`  |
| `failed` (temporary) | `Bounce` (Soft)      | `bounceType: "Transient"`, `bouncedRecipients[].emailAddress`, `timestamp`  |
| `complained`         | `Complaint`          | `complainedRecipients[].emailAddress`, `complaintFeedbackType`, `timestamp` |
| `opened`             | `Open`               | `timestamp`, `ipAddress`, `userAgent`, `destination`                        |
| `clicked`            | `Click`              | `timestamp`, `ipAddress`, `userAgent`, `link`                               |
| `unsubscribed`       | No native equivalent | Must implement via List-Unsubscribe header + custom application handler     |
| `stored`             | No equivalent        | SES does not store messages; use S3 action via Receipt Rules                |

**Important:** SES events arrive wrapped in an SNS notification envelope. The `Message` field of the SNS notification contains a JSON string (double-encoded) of the actual SES event. Your Lambda handler must `JSON.parse(event.Records[0].Sns.Message)` to access the SES event object.

## Email Authentication on SES: SPF, DKIM, DMARC, BIMI

Mailgun handles authentication via your verified sending domain — the dashboard publishes the SPF, DKIM, and tracking CNAMEs you need, and Mailgun signs outbound mail with their key infrastructure. SES makes you own the same surface with no managed defaults. This is good for control, and unavoidable in 2026 — Gmail, Yahoo, and Microsoft now treat unauthenticated mail from any sender above 5,000 messages per day as effectively undeliverable.

**SPF.** Add `include:amazonses.com` to your sending domain's SPF record. If you are keeping Mailgun running in parallel during a phased cutover, your record looks like `v=spf1 include:mailgun.org include:amazonses.com -all` until you fully cut over. Watch the 10-DNS-lookup limit — chaining too many `include:` directives silently breaks SPF and is the most common deliverability regression during a Mailgun-to-SES migration.

**DKIM.** Enable Easy DKIM in the SES console for each verified identity and publish the three CNAME records SES generates. Easy DKIM rotates keys automatically. During the cutover window where both Mailgun and SES are sending, both DKIM keys validate independently because they use different selectors — Mailgun's `krs._domainkey.[your-domain]` (or `pic._domainkey.[your-domain]` for newer accounts) and SES's three CNAMEs coexist without conflict.

**DMARC.** Publish `_dmarc.yourdomain.com` with at minimum `v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com` on day one to start collecting aggregate reports. Move to `p=quarantine` after two to four weeks of clean reports, then to `p=reject` once every legitimate sender — transactional, marketing, internal tooling, calendar invites — is aligned. Gmail and Yahoo's 2024 bulk-sender requirements (DMARC mandatory above 5,000 messages/day per recipient domain) are now an established baseline — `p=none` satisfies the rule on paper, but by 2026 most enterprise mailbox providers and ISPs flag bulk senders still on `p=none` as a deliverability risk. Plan for `p=quarantine` at minimum and `p=reject` once your sender map is clean.

**BIMI.** Once you reach `p=quarantine` or stricter and have a Verified Mark Certificate (VMC), publish a BIMI record so your logo renders next to messages in Gmail, Apple Mail, and Yahoo. BIMI does not directly improve deliverability, but the trust signal lifts open rates measurably for transactional and lifecycle email.

**Common Mailgun DNS cleanup mistakes after cutover:**

- Leaving Mailgun MX records (`mxa.mailgun.org`, `mxb.mailgun.org`) on a domain you also use for SES inbound — the older MX entries keep stealing inbound mail and breaking SES Receipt Rules. Remove them only after you have verified SES inbound works end-to-end.
- Leaving the Mailgun tracking CNAME (`email.yourdomain.com` pointing to `mailgun.org`) live after you have stopped sending from Mailgun — orphaned tracking domains cause hard-to-diagnose dead links in archived emails and pollute DMARC alignment reports.
- Forgetting to add SPF and DKIM for the EU SES region if you have split sending across regions — verification, DKIM, and SPF are per-region in SES, unlike Mailgun's global EU/US split.

## Reputation, IP Warming, and List Hygiene After Mailgun

Mailgun customers on Foundation or Flex share a warmed IP pool. The reputation built on those IPs belongs to Mailgun and does not transfer to SES — you start cold the day you cut over, regardless of how clean your domain history is. Plan for it.

**Migrating to SES shared IPs (most senders).** The SES shared pool is suitable for transactional senders under 500,000 emails per month. There is no formal warming required, but mailbox providers still observe sending velocity per domain. Ramp gradually over 7–10 days rather than spiking from zero to full volume on day one.

**Migrating to SES dedicated IPs (high-volume marketing).** SES dedicated IPs cost $24.95 per IP per month — meaningfully cheaper than Mailgun's $59/month dedicated IPs. The trade-off is that you handle warming yourself.

| Day Range  | Daily Volume Cap   | Notes                                    |
| ---------- | ------------------ | ---------------------------------------- |
| Days 1–3   | 200 emails/day     | Send to most engaged segment only        |
| Days 4–7   | 1,000 emails/day   | Watch bounce rate (target &lt;2%)        |
| Days 8–14  | 10,000 emails/day  | Add second engagement tier               |
| Days 15–21 | 50,000 emails/day  | Monitor complaint rate (target &lt;0.1%) |
| Days 22–30 | Full target volume | Reputation established                   |

Keep Mailgun running in parallel for non-engaged segments during the warming window. The dual-vendor month is the cost of insurance against a deliverability dip that can take 60+ days to recover from.

**List hygiene before cutover.** Reputation built on a clean list is durable; reputation built on a stale list is borrowed time.

- Export Mailgun's bounce, unsubscribe, and complaint history via the Events API and import the hard bounces and complaints to the SES account-level suppression list before the first production send. The 7-day event log retention on Mailgun Foundation means anything older than a week is already lost — pull what you can now.
- Suppress addresses that have not engaged in the last 6–12 months for marketing streams. Re-engagement campaigns belong on the legacy provider, not on a fresh SES IP.
- Run a syntax + MX validation pass on your active list. Typo'd domains (`gmial.com`, `hotnail.com`), role accounts, and disposable mailboxes inflate the bounce rate and burn reputation while warming.

**From Mailgun tags to SES Configuration Sets.** Mailgun's `o:tag` lets you classify sends for analytics. The SES equivalent is a Configuration Set per sending stream — transactional, marketing, lifecycle, internal — with separate event destinations and, optionally, separate dedicated IP pools. Isolating streams means a marketing reputation hit does not take down password resets.

**Sending cadence and throttling.** SES enforces a per-second send rate and a 24-hour quota that grow with reputation. Smooth bulk sends across hours rather than firing at minute zero — bursty patterns trigger greylisting at smaller mailbox providers and look like list-bombing to spam classifiers. Use `SendBulkEmail` with batched destinations and throttle at the SDK or queue layer.

**Engagement-based filtering.** The single highest-leverage change most teams make post-migration is to stop sending to disengaged subscribers entirely. Maintain a "send eligibility" flag in your subscriber store, updated nightly from SES open and click events, and exclude anyone who has not engaged in your defined window. Gmail and Microsoft weight recent positive engagement (opens, replies, "move to inbox") far more heavily than total volume, so a smaller, hotter list lifts inbox placement for the entire domain — typically from the mid-80s to the mid-90s within 30 days.

## Production Event Tracking: SES → Kinesis Firehose → S3 → Node.js

Mailgun gives you a polished event log with 7 days of retention on Foundation and 30 days on Flex/Growth. After cutover to SES, the SNS → Lambda pattern from the webhook section above handles tens of thousands of events per day. Past that volume, or when you need historical analytics that outlive Mailgun's hosted window, the production-grade pipeline is **SES → Kinesis Data Firehose → S3 → Node.js API**.

**How it works.**

1. **SES Configuration Set** — define one per sending stream (transactional, marketing, lifecycle) and attach a Kinesis Firehose event destination. Subscribe to all event types: `send`, `delivery`, `bounce`, `complaint`, `open`, `click`, `reject`, `renderingFailure`, `deliveryDelay`, and `subscription`.
2. **Kinesis Data Firehose** — buffers events (typically 60 seconds or 5 MB) and writes newline-delimited JSON to S3 partitioned by `year/month/day/hour`. Enable dynamic partitioning to split by Configuration Set or sending IP for downstream filtering.
3. **S3 with lifecycle policy** — Standard for 30 days (hot analytics window), Standard-IA at 30 days, Glacier Flexible Retrieval at 180 days. Raw event archives are the cheapest part of the stack and the most useful during deliverability investigations.
4. **Node.js API** — consume events by tailing new S3 objects via S3 Event Notifications → SQS → Node.js worker (Express or Fastify behind an ALB, or a Lambda function for spiky traffic), or query historical data via Amazon Athena. The API exposes per-recipient timelines, per-campaign engagement, and per-domain placement metrics to your dashboards.

**Why this beats Mailgun's hosted event log.**

- **Retention.** S3 with Glacier transition gives you years of event history for a few dollars a month. Mailgun's hosted log capped retroactive analytics at 7–30 days; you can do better.
- **Replayable.** S3 is the source of truth. Rebuild a recipient timeline, re-run a deliverability analysis, or backfill a new dashboard without re-fetching from a vendor API.
- **Cheap analytics.** Athena over partitioned S3 answers "what was my Gmail open rate by sending IP last week?" in seconds, not engineering tickets.
- **Audit trail.** Compliance and abuse investigations require raw event history. Firehose-to-S3 gives it to you immutably.

**Filtering bots, proxies, and Apple MPP.** SES open events fire on pixel load and click events fire on the SES tracking redirector. Both are noisy: corporate proxies, link-scanning gateways, and Apple Mail Privacy Protection pre-fetch images and follow links, generating engagement signals that have nothing to do with a human reading the message. Without filtering, your "open rate" double-counts machine activity and your engagement-based suppression logic ends up suppressing real subscribers.

[InboxEagle's Bot Finder](https://www.inboxeagle.com/) sits naturally between the S3 raw layer and the engagement-based filtering layer described above — it analyzes SES open and click streams, separates human engagement from bot and proxy activity (Apple MPP, Microsoft Defender, Gmail image proxy, link-scanning gateways), and feeds your Node.js API cleaned engagement data so your reputation signals reflect real subscriber behavior. Teams migrating off Mailgun's pre-aggregated event UI underestimate how much inbox-placement signal lives in distinguishing real opens from scanner activity; a bot filter is the difference between an engagement model that improves placement and one that quietly degrades it.

## Why Choose FactualMinds for Your Email Migration

FactualMinds is an **AWS Select Tier Consulting Partner** specializing in email infrastructure migration. We have executed SendGrid, Mailgun, Postmark, and SparkPost to AWS SES migrations and know exactly where teams get stuck.

- **Email migration experts** — we handle domain verification, DKIM, bounce architecture, IP warming
- **Assessment-first approach** — we map your current state before writing a line of infrastructure code
- **Zero-downtime cutover planning included** — no failed deliveries during migration
- **AWS Select Tier Partner** — [verified on AWS Partner Network](https://partners.amazonaws.com/partners/001aq000008su2EAAQ/Factual%20Minds)

---

## FAQ

### How does Mailgun compare to AWS SES?
Mailgun and SES occupy similar market positions — developer-focused, API-first transactional email platforms — but differ significantly in pricing and ecosystem fit. Mailgun Foundation costs $35/month for 50,000 emails ($0.70 per 1,000). SES costs $0.10 per 1,000, making it $5 for the same 50,000 emails. Mailgun's advantage is a simpler routing rules UI, better inbound email processing, and a cleaner developer experience out of the box. SES wins on cost at scale, deep AWS-native integration, and IAM-based access control. For teams already on AWS sending more than 30,000 emails per month, SES is almost always the economically rational choice.

### Can AWS SES receive email like Mailgun?
Yes. SES Receipt Rules handle inbound email processing and are the SES equivalent of Mailgun Routes. You define conditions (recipient address patterns, domain matches) and actions (deliver to S3, trigger Lambda, forward to SNS, call a custom HTTP endpoint via SNS). The capability is equivalent to Mailgun routing, but the configuration interface is more complex — you define rules in the SES console or via CloudFormation/CDK rather than Mailgun-style regex route strings. The added complexity comes with the benefit of native integration with Lambda, S3, and SNS for inbound processing.

### Is Mailgun or AWS SES better for transactional email?
For pure transactional email (order confirmations, password resets, notifications), SES is better for teams on AWS. The deliverability is comparable, IAM makes credential management simpler, and the cost at volume is dramatically lower. Mailgun has a slight edge in developer onboarding speed — you can be sending in 15 minutes without AWS account setup. Mailgun also has a more polished analytics dashboard for per-message tracking without building your own SNS event pipeline. For AWS-native teams with meaningful volume, the operational overhead of SNS event handling is worth the cost savings.

### How do I migrate Mailgun suppression lists to SES?
Export your Mailgun suppression list (bounces, unsubscribes, complaints) via the Mailgun API using GET /v3/{domain}/bounces, /unsubscribes, and /complaints. For hard bounces and complaints, upload addresses to the SES account-level suppression list via the SES console or the PutSuppressedDestination API in bulk. For soft-bounce retries and application-level unsubscribe management, store the exported list in DynamoDB or your application database and check it before each send. The SES account-level suppression list automatically blocks hard bounces and complaints going forward once an address hits those thresholds.

### What is the equivalent of Mailgun routing in AWS SES?
SES Receipt Rules are the equivalent of Mailgun Routes. A Receipt Rule Set contains ordered rules, each with match conditions (recipient address, domain) and one or more actions: store to S3, invoke Lambda, publish to SNS, send to WorkMail, or stop processing. The most flexible inbound pattern is SNS → Lambda: SES publishes the raw email (headers + body) to an SNS topic, and a Lambda function parses and routes it. This is more powerful than Mailgun routing for complex logic but requires more infrastructure setup. For simple forwarding, the Lambda action with a forwarding function is the practical equivalent of a Mailgun forward route.

---

*Source: https://www.factualminds.com/compare/mailgun-to-aws-ses/*
