The Mailgun Routes alternative whose mailbox doesn't expire
Mailgun's store() is a mailbox measured in days and its routes are regular expressions. Inboundr gives you real addresses, real endpoints and retention you choose.
Mailgun Routes’s documentation and pricing last read 30 July 2026
The short version
- store() is a temporary mailbox measured in days. Replay, showing a user the original message, and agents that read on their own schedule all outlive it.
- Retention here is a plan setting up to 90 days, with every message and attachment fetchable over REST or MCP for the whole window.
- Routes are ordered regular expressions evaluated per message; addresses here are objects you point at endpoints — no priority order, no unescaped dot in production.
- Filtering runs before delivery and reports what it blocked, instead of being composed out of routes that match a spam header.
What routes actually do
Mailgun receives on your domain and evaluates a list of routes against each message. A route has a priority, a filter expression — match_recipient with a regular expression, match_header, or catch_all — and one or more actions: forward() to an address or a URL, store() to keep a copy and optionally notify you, and stop() to prevent lower-priority routes from running.
It is a capable inbound webhook: the POST is parsed rather than raw, it carries a signature you can verify, and failures are retried. Two things about it decide whether it is the right home for mail your business depends on — how long the mailbox lasts, and what it takes to change where mail goes.
A mailbox with a three-day floor
store()is described in their own documentation as temporary: messages are kept for around three days, extending to about seven depending on your plan and the domain’s settings. You fetch one through the Messages API using a storage URL that comes from the event log.
Three days is plenty for the job it was designed for — bridging a webhook failure, or letting a worker pick a message up asynchronously. It is not enough for the things people reach for storage for:
- Replaying after a bug you found late. A parsing error noticed on Monday and traced back over a fortnight cannot be re-run against mail that was stored for three days.
- Showing a user the original message. “Here is the email this ticket came from” is a feature request that arrives eventually, and it turns a temporary store into a permanent one — in your database, with your attachment bucket, on your backup schedule.
- An agent catching up on a quiet inbox. An assistant that reads its mail twice a week is asking for something that expired on Wednesday.
Fifteen days, on every plan
Not something you buy your way up to: 15 days on Scale, and the same 15 days on the free plan — with every message listable and fetchable over REST or MCP for the whole window, attachments included.
Routes are a language; addresses are objects
The second difference is how you say where mail should go. A route is an ordered rule with a filter expression and actions, evaluated top-down. An address here is a thing that exists, with endpoints attached to it.
# Routes: ordered expressions, evaluated per message.
priority 0 match_recipient("^support@yourdomain\.com$")
forward("https://app.example.com/hooks/support"), stop()
priority 1 match_recipient("^billing@yourdomain\.com$")
forward("https://app.example.com/hooks/billing"), stop()
priority 2 catch_all()
store(notify="https://app.example.com/hooks/catchall")
# Addresses: objects with endpoints attached.
support@yourdomain.com → webhook "app-support" + forward to team inbox
billing@yourdomain.com → webhook "app-billing"
*@yourdomain.com → store onlyExpressions are more powerful — matching on a header is a one-liner there and not expressible here. Objects are the ones you can be confident about at 3am: no priority order to reason about, no stop() to forget, and no regular expression quietly matching support@yourdomain.company because somebody did not escape a dot.
The practical difference is who can safely change it. Adding an address and pointing it somewhere is a thing a support lead can do in the console. Editing an ordered list of regular expressions that route production mail is not.
Side by side
The rows that decide it are storage, routing and what an agent finds behind the tools.
| What you need | Inboundr | Mailgun |
|---|---|---|
| The parsed message posted to your app | A JSON email.received payload: bodies, addresses, threading ids, auth verdicts, attachment descriptors with links. | Routes parse the message and POST the pieces, including a stripped-text field with the quoted reply history removed. This is a proper inbound webhook, not a forward. |
| Proof the request came from your provider | An HMAC signature over the body and a timestamp, in a header, with a delivery id for idempotency. | A timestamp, token and signature you verify with your signing key — the same scheme as the rest of their webhooks, and well documented. |
| Retries when your endpoint fails | Exponential backoff, and the message stays fetchable over REST for the whole retention window afterwards. | Failed posts are retried on a published schedule, and store() means a copy exists meanwhile. Among the incumbents this is the best-behaved inbound path. |
| Fetch the message days or weeks later | Retained 15 days on every plan, free included, and listable and fetchable over REST or MCP the whole time. | store() is described as a temporary mailbox: roughly three days, up to about seven depending on plan and domain settings. Past that the storage URL is dead and the message is gone. |
| Routing by address rather than by regex | Addresses are objects. Create support@, point it at endpoints, add a catch-all for everything else — no expression to get wrong. | Routes are ordered filter expressions — match_recipient with a regular expression, match_header, catch_all — with actions and a stop(). Powerful, and its own small language to reason about at 3am. |
| Attachments your code can fetch | Stored separately, handed out as short-lived links in the payload and over the API for as long as the message is retained. | In the POST as parts, or as links in the stored copy when you use store() with a notify URL. Both are good; both expire with the temporary store. |
| Filtering before delivery | Rules on sender, recipient, subject or spam verdict drop or divert the message before your endpoint is called, with a list of what was blocked. | Spam verdicts arrive as headers on the message and a route can match a header, so you can build this out of routes. It is composition rather than a feature, and the outcome is not reported back to you as blocks. |
| A threaded reply from code | Name the message you are replying to; In-Reply-To and References are written for you. | Sending is their core product and does this well, but the threading headers are yours to set — the inbound and outbound halves do not share a thread object. |
| An agent that works its own inbox | A hosted MCP server scoped to mail on the same API key: list, read, thread, send, fetch attachments, manage rules. | They ship a first-party MCP server covering a wide slice of their API, so an agent can genuinely drive Mailgun. What it is driving on the inbound side is routes and a three-day store, not a mailbox that persists. |
The parsed message posted to your app
- Inboundr
- A JSON email.received payload: bodies, addresses, threading ids, auth verdicts, attachment descriptors with links.
- Mailgun
- Routes parse the message and POST the pieces, including a stripped-text field with the quoted reply history removed. This is a proper inbound webhook, not a forward.
Proof the request came from your provider
- Inboundr
- An HMAC signature over the body and a timestamp, in a header, with a delivery id for idempotency.
- Mailgun
- A timestamp, token and signature you verify with your signing key — the same scheme as the rest of their webhooks, and well documented.
Retries when your endpoint fails
- Inboundr
- Exponential backoff, and the message stays fetchable over REST for the whole retention window afterwards.
- Mailgun
- Failed posts are retried on a published schedule, and store() means a copy exists meanwhile. Among the incumbents this is the best-behaved inbound path.
Fetch the message days or weeks later
- Inboundr
- Retained 15 days on every plan, free included, and listable and fetchable over REST or MCP the whole time.
- Mailgun
- store() is described as a temporary mailbox: roughly three days, up to about seven depending on plan and domain settings. Past that the storage URL is dead and the message is gone.
Routing by address rather than by regex
- Inboundr
- Addresses are objects. Create support@, point it at endpoints, add a catch-all for everything else — no expression to get wrong.
- Mailgun
- Routes are ordered filter expressions — match_recipient with a regular expression, match_header, catch_all — with actions and a stop(). Powerful, and its own small language to reason about at 3am.
Attachments your code can fetch
- Inboundr
- Stored separately, handed out as short-lived links in the payload and over the API for as long as the message is retained.
- Mailgun
- In the POST as parts, or as links in the stored copy when you use store() with a notify URL. Both are good; both expire with the temporary store.
Filtering before delivery
- Inboundr
- Rules on sender, recipient, subject or spam verdict drop or divert the message before your endpoint is called, with a list of what was blocked.
- Mailgun
- Spam verdicts arrive as headers on the message and a route can match a header, so you can build this out of routes. It is composition rather than a feature, and the outcome is not reported back to you as blocks.
A threaded reply from code
- Inboundr
- Name the message you are replying to; In-Reply-To and References are written for you.
- Mailgun
- Sending is their core product and does this well, but the threading headers are yours to set — the inbound and outbound halves do not share a thread object.
An agent that works its own inbox
- Inboundr
- A hosted MCP server scoped to mail on the same API key: list, read, thread, send, fetch attachments, manage rules.
- Mailgun
- They ship a first-party MCP server covering a wide slice of their API, so an agent can genuinely drive Mailgun. What it is driving on the inbound side is routes and a three-day store, not a mailbox that persists.
When Mailgun is the better choice
- Your routing is genuinely pattern-shaped. Matching on an arbitrary header, or one expression across a family of recipients, is native there and not available here.
- Three days of storage is exactly enough. If the store only ever bridges a webhook outage and your own database is the record, longer retention buys you nothing.
What each one costs
No price table — the numbers change and a stale one is worse than none. Check theirs at the source and ours here.
- Mailgun meters sending volume, with inbound routes and a short store included alongside it, and log retention that varies by plan. If most of your mail goes out, receiving costs you close to nothing extra.
- Inboundr meters messages received, with an allowance then overage at one flat rate, and the same retention window whichever plan you are on. Domains and addresses are not billable units.
The thing to compare is not the line item but what you would build around it: if you would end up copying every stored message into your own bucket before it expires, that job — and the bucket, and its lifecycle rules — belongs in the comparison.
Moving a domain across
Routes translate into addresses fairly mechanically:
- List your routes in priority order. Each
match_recipientthat names one address becomes one address here; acatch_allbecomes the catch-all. Anything matching a header is the part that needs thought — that is usually a filtering rule here, or a branch in your handler. - Add the domain in the Inboundr console, publish the DKIM record it shows you, and leave the MX records alone. Verification does not affect delivery, so this is safe with routes still live.
- Recreate the destinations as endpoints, then adapt your handler: JSON instead of form fields, an HMAC header instead of timestamp/token/signature, and attachment links instead of parts.
- Swap the MX records, TTL lowered an hour beforehand. Mail in flight follows the old records and still hits your routes, which should stay in place.
- Copy anything you still need out of
store()before it expires. This is the step with a deadline — the window is days, not weeks. - Send a test with an attachment and a reply in a thread, then return a 500 deliberately and watch the retry land. Confirm the behaviour rather than assuming it.
Questions people ask
Is this really better than a route with store()?
It is better wherever the message needs to still exist: replay after a bug, showing a user the mail a ticket came from, an agent reading on its own schedule, or an attachment someone asks for next month. If your handler processes everything on arrival and never looks back, a route with store() will do the job.
Can I match on a header the way routes do?
Filtering rules here match on sender, recipient, subject and the spam verdict, not on arbitrary headers. Every header is in the payload, so header-based branching is a couple of lines in your handler — but if that decision has to happen before delivery, routes do something we do not.
What about their MCP server?
It is real and it is first-party, and an agent can drive a lot of their API through it. The distinction is what sits behind the tools: routes, logs and a temporary store, rather than an inbox with threads, retained messages and fetchable attachments. Same protocol, different object model.
Can I run both during a migration?
Across two domains or subdomains, yes — MX records are per domain, so one can point at each with no interaction. On a single domain the MX record picks one winner, so the safe pattern is to move a subdomain first and watch it for a week.
Do you strip quoted replies like stripped-text does?
Not today — the full text and HTML bodies are handed over as they arrived, along with the thread, so separating the new reply is a few lines in your handler or a job for the model already reading the message. If that field is load-bearing for you, it is worth weighing.
Claims about Mailgun were checked against their public documentation on receiving, forwarding and storing mail, their route actions reference, their MCP server and their pricing page. Storage windows in particular vary by plan and domain setting — check yours. Read on 30 July 2026. If something here is out of date, tell us and we will correct it.