A Postmark inbound alternative that keeps your attachments
Postmark's inbound API returns everything about an attachment except the file itself. Inboundr stores every message and every attachment, and hands them back whenever you ask.
Postmark’s documentation and pricing last read 30 July 2026
The short version
- Attachments are the hole: Postmark's inbound API returns a file's name, type and size, but never its contents. If your handler lost it, it is gone.
- Here every attachment is stored on arrival and fetchable by link, by API or by an agent tool call for the whole retention window.
- Routing lives on the address rather than in your handler: one message can post to your app and forward to a person, without a deploy.
- Filtering runs before delivery on sender, recipient, subject or spam verdict — a router, not just a blocklist.
What Postmark inbound gives you
Postmarkis a transactional sending service that also receives. You get an inbound stream with its own address, or point a domain’s MX record at them, and every message arrives at your endpoint as structured JSON: bodies, headers, and a mailbox hash pulled out of anything+hash@yourdomain so you can correlate a reply with the thing it answers. Failures are retried on a published schedule and inbound messages stay searchable through the API.
That is a competent inbound webhook, and if all you ever do with a message is read a field out of it the day it arrives, it will serve. The trouble starts the first time you need something back — because there is one thing their API will never return you.
The attachment you cannot fetch back
Inbound attachments arrive base64-encoded inside the webhook JSON. That is the only time you are given their contents. The inbound API will happily tell you afterwards that a message had an attachment called invoice-4021.pdf, that it was 84 kB and that it was a PDF. It will not give you the PDF.
The consequences show up in ordinary situations:
- Your handler crashed on the one message that mattered. The retry ladder saves you — until the retries are exhausted or the bug was a 200 that silently dropped the file. After that, the message body is recoverable and the invoice is not.
- A serverless function with a request limit. Base64 inflates by about a third, so a cumulative cap in the tens of megabytes lands on your endpoint as a request bigger than some platforms accept. The fix is your own storage, written before you can process anything.
- An agent that wants to read the attachment. If the bytes live only in whatever bucket your handler wrote them to, an agent needs credentials for your bucket and a convention for the key. The mail service cannot answer the question.
Why we hold them separately
Attachments are stored on arrival, apart from the message, and handed out as short-lived links — in the webhook payload, from the REST API, and as a tool call for agents. The reason is exactly the case above: the times you most want the file are the times your first attempt at handling it went wrong.
Side by side
Where the Postmark column says partly, the capability exists in an administration-shaped form rather than the shape an inbound-first application wants. The row to read twice is attachments.
| What you need | Inboundr | Postmark |
|---|---|---|
| The parsed message as JSON | An email.received payload with bodies, addresses, threading ids, auth verdicts and attachment descriptors. | A well-shaped JSON post with bodies, addresses and headers, plus a stripped-reply field that removes quoted history for you. |
| Retries when your endpoint fails | Exponential backoff, an idempotency id on every attempt, and the message stays fetchable over REST for the retention window. | A documented ladder of ten retries from a minute to six hours, plus an endpoint to retry a failed inbound message by hand. |
| Fetch the message body later | List and fetch stored messages and whole threads over REST, or over MCP as tool calls. | An inbound message search and a details endpoint returning bodies and headers, with a retention window that is configurable well past the default. |
| Fetch an attachment later | Attachments are stored separately and fetched by link or by a get_attachment tool call, for as long as the message is retained. | The inbound API returns each attachment's name, type, content id and length — but not its content. The bytes arrive base64 inside the original webhook and nowhere else, so if you did not keep them, they are gone. |
| Attachments that do not bloat the request | Descriptors in the payload and the bytes behind a short-lived link, so a large attachment does not decide whether your handler can accept the request. | Base64 inside the JSON, under a documented cumulative cap per message. Convenient at small sizes; a real constraint behind a function with a request size limit. |
| Filtering before delivery | Rules match on sender, recipient, subject or spam verdict and drop or divert the message, with a list of what was blocked so an over-broad rule is visible. | Inbound rules block an address or a domain outright, and blocked messages can be bypassed after the fact. It is a blocklist rather than a router — there is no rule that sends this to that endpoint. |
| Several destinations for one address | An address can point at more than one endpoint, so the same message posts to your app and forwards to a person. | An inbound stream has one webhook URL. Fan-out is something your handler does after the fact, on your own compute. |
| An agent that works its own inbox over MCP | A hosted MCP server scoped to mail: list, read, thread, send, fetch attachments and manage filtering rules on the same API key. | There is a first-party MCP server, and it is a good one — but it is built around sending: templates, delivery stats, bounces, suppressions. The inbound side of an agent's job still runs through their REST API and glue you write. |
| Built around the inbox rather than the send | Addresses, endpoints, threads, rules and retention are the product. Everything in it exists because mail arrived. | Inbound is a stream attached to a sending server. It works, and it is shaped by what a sending product needs — one webhook, account-level administration, and a meter built for outbound. |
The parsed message as JSON
- Inboundr
- An email.received payload with bodies, addresses, threading ids, auth verdicts and attachment descriptors.
- Postmark
- A well-shaped JSON post with bodies, addresses and headers, plus a stripped-reply field that removes quoted history for you.
Retries when your endpoint fails
- Inboundr
- Exponential backoff, an idempotency id on every attempt, and the message stays fetchable over REST for the retention window.
- Postmark
- A documented ladder of ten retries from a minute to six hours, plus an endpoint to retry a failed inbound message by hand.
Fetch the message body later
- Inboundr
- List and fetch stored messages and whole threads over REST, or over MCP as tool calls.
- Postmark
- An inbound message search and a details endpoint returning bodies and headers, with a retention window that is configurable well past the default.
Fetch an attachment later
- Inboundr
- Attachments are stored separately and fetched by link or by a get_attachment tool call, for as long as the message is retained.
- Postmark
- The inbound API returns each attachment's name, type, content id and length — but not its content. The bytes arrive base64 inside the original webhook and nowhere else, so if you did not keep them, they are gone.
Attachments that do not bloat the request
- Inboundr
- Descriptors in the payload and the bytes behind a short-lived link, so a large attachment does not decide whether your handler can accept the request.
- Postmark
- Base64 inside the JSON, under a documented cumulative cap per message. Convenient at small sizes; a real constraint behind a function with a request size limit.
Filtering before delivery
- Inboundr
- Rules match on sender, recipient, subject or spam verdict and drop or divert the message, with a list of what was blocked so an over-broad rule is visible.
- Postmark
- Inbound rules block an address or a domain outright, and blocked messages can be bypassed after the fact. It is a blocklist rather than a router — there is no rule that sends this to that endpoint.
Several destinations for one address
- Inboundr
- An address can point at more than one endpoint, so the same message posts to your app and forwards to a person.
- Postmark
- An inbound stream has one webhook URL. Fan-out is something your handler does after the fact, on your own compute.
An agent that works its own inbox over MCP
- Inboundr
- A hosted MCP server scoped to mail: list, read, thread, send, fetch attachments and manage filtering rules on the same API key.
- Postmark
- There is a first-party MCP server, and it is a good one — but it is built around sending: templates, delivery stats, bounces, suppressions. The inbound side of an agent's job still runs through their REST API and glue you write.
Built around the inbox rather than the send
- Inboundr
- Addresses, endpoints, threads, rules and retention are the product. Everything in it exists because mail arrived.
- Postmark
- Inbound is a stream attached to a sending server. It works, and it is shaped by what a sending product needs — one webhook, account-level administration, and a meter built for outbound.
When Postmark is the better choice
- You send far more than you receive. If inbound is nothing but replies to mail you sent, and you never need a file back, one account for both halves is the simpler purchase.
- You need inbound records queryable for years. Their retention can be configured well past any window on our plans. Note what that buys: the record and the body, still not the attachment.
Inbound as the product, not the adjunct
The structural difference is what an account is organised around. A Postmark server is a sending server that happens to have an inbound stream attached: one stream, one webhook URL, and routing decisions made in your code after the message arrives. Here the top-level object is the address, and an address points at endpoints — a webhook, a forwarding destination, storage only, or several at once.
That difference is most visible when requirements change:
- “Support should also land in a shared mailbox.” Here that is a second endpoint on the address. There it is a code change to your handler and a deploy.
- “Give every customer their own address.” Both products can do this — Postmark through the mailbox hash on one stream, Inboundr by creating addresses through the API or letting a catch-all absorb them. Postmark keeps one webhook; Inboundr lets different addresses go to different places.
- “The agent should stop reading newsletters.” A rule on subject or sender drops them before delivery. A blocklist can do the same for a known sender, but not for a pattern.
If an agent is the reader, the same account answers over MCP with no webhook involved at all:
# One endpoint, the same API key.
https://inboundr.net/api/mcp
list_emails newest first; filter by address, thread or direction
get_email full bodies, attachment metadata, auth verdicts
get_attachment the bytes, for as long as the message is retained
send_email pass inReplyTo to answer inside the threadSee the MCP tools for the full list, or how to give an agent its own address for what an agent does with them.
What each one costs
No figures on this page — both products change them, and a stale price in a comparison is worse than none. Check theirs at the source and ours here.
- Postmark bills on message volume across the account, with inbound counted alongside outbound. For a service whose mail is mostly outbound with replies coming back, that single meter is simple and fair.
- Inboundr bills on messages received, with an allowance and opt-in pay-as-you-go past it. Domains and addresses are not billable units, so a per-customer address scheme does not multiply the bill.
The honest summary: if outbound dominates, their meter suits you. If inbound dominates — support pipelines, agent inboxes, document intake — you are paying a sending product to do a receiving job.
Moving an inbound stream across
Both sides are DNS, so this is reversible at every step if you do it in this order:
- Add the domain in the Inboundr console and copy the MX and DKIM records. Publish the DKIM record now — it changes nothing about delivery — and leave the MX record alone.
- Recreate the routing. Every branch in your current handler that inspects the recipient is usually one address here, pointed at one endpoint.
- Port the payload mapping. The field names differ (
TextBodybecomestext, and attachment content becomes a link to fetch), and the signature check changes from credentials in the URL to an HMAC header. - Swap the MX record, with a low TTL set an hour beforehand. Mail in flight follows the old record until resolvers catch up.
- Send a test message with an attachment, and reply to one from your code. Attachments and threading are where a ported handler usually still carries an assumption from the old payload.
- Keep the Postmark inbound stream configured for a few days. Once the MX record has moved it receives nothing, and it is your rollback.
Anything already received stays there
Inbound messages already in their store remain queryable through their API for the retention you have configured. There is no backfill into this account — we only see what arrives after the MX change — so if you need the history, export it before you cancel.
Questions people ask
Is Postmark's inbound webhook worse than yours?
As a single POST, no — it is a good webhook, and one field in it strips quoted history in a way ours does not. The difference is everything after the POST: what is still addressable tomorrow, whether an attachment can be fetched at all, and whether routing lives on the address or in your handler.
Can I keep sending with them and receive here?
Yes, and it is the lowest-risk way to start: nothing about your sending setup changes, and inbound moves to a product built for it. Threading across two providers means passing the received message’s id into the In-Reply-To header of the mail you send from theirs — or sending the reply from here, where those headers are written for you.
Do inbound rules and your filtering rules do the same job?
Not quite. Postmark's inbound rules decide whether a message is accepted at all, keyed on an address or a domain, and a blocked message can be bypassed later. Inboundr's filtering rules also match on subject and spam verdict, and can divert rather than only drop — the difference between a blocklist and a router.
What about the mailbox hash — do you have that?
Plus-addressing works the same way here: the local part after the + reaches the address it was added to and comes through in the recipient field, so the same correlation trick works. You can also create real addresses per customer through the API, or let a catch-all take them.
Which one for an AI agent reading a mailbox?
Inboundr, and this is the clearest case on the page: the agent talks MCP to an inbox, reads threads, fetches attachments and edits its own filtering rules without any code in between. With Postmark an agent can send well, but reading inbound means wrappingPostmark's REST API yourself — and the attachments still are not there.
Claims about Postmark were checked against their public documentation on parsing an inbound email, their messages API reference, their MCP server and their pricing page. Read on 30 July 2026. If something here is out of date, tell us and we will correct it.