The SendGrid Inbound Parse alternative that keeps your mail
Inbound Parse posts a form once and forgets it. Inboundr stores every message, signs every delivery, retries until your app is ready, and lets you fetch it all back.
SendGrid Inbound Parse’s documentation and pricing last read 30 July 2026
The short version
- Inbound Parse POSTs a multipart form once and its job is finished: no inbound store behind it, and no documented API to fetch a received message again.
- A bad deploy, a handler that acknowledges too early, or a parsing bug found on Thursday all cost you the mail — permanently.
- Here every message is stored on arrival, signed with an HMAC header, retried with backoff, and fetchable over REST or MCP for the whole retention window.
- Attachments live behind short-lived links instead of inside a 30 MB request, and rules drop unwanted mail before your endpoint is ever called.
What Inbound Parse does
You point a subdomain’s MX record at SendGrid, tell the parse setting which URL to call, and every message that arrives is broken up and POSTed to you as a form: from, to, subject, text, html, the full header block, the envelope, SPF and DKIM results, a count of attachments and a JSON blob describing them, with the files themselves as parts of the same multipart request. There is a switch to receive the raw MIME message instead, in a single field.
It works, and for a long time it was the only way to get mail into an application without running an MTA. It also dates from that era, and one sentence in its design decides everything else about it: the POST is the whole product.
One delivery, and no second chance
Nothing behind the webhook keeps the message. That single fact is responsible for most of the difference between the two columns in the table below, and it shows up in ordinary operational life:
- A deploy goes out at the wrong moment. Your endpoint returns 502 for ninety seconds. Whatever arrived in that window is not queryable afterwards, because there is no inbound store to query.
- A handler that returns 200 too early. The classic shape — acknowledge, then process, then throw. The provider considers the message delivered, and it was, and it is gone.
- A parsing bug you notice on Thursday. With storage behind the webhook you re-fetch the week and replay. Without it, the fix only applies to mail that has not arrived yet.
- Anyone who knows your URL can post to it. Without a signature over the body, the endpoint’s only defence is the secrecy of its address, and URLs leak into logs, proxies and error trackers.
You can close every one of these — by building them
Store the raw request before you parse it, put a queue in front, embed credentials in the URL, keep a dead-letter table, write the replay script. That is a small, dull, load-bearing service that you now own and page on. Everything in that list is already here, and the hosted part of their offering is the MX record.
Side by side
Read the SendGrid Inbound Parsecolumn as “what arrives, and what remains afterwards”. Where it says partly, the capability is there but sits in the request rather than in a service you can call back.
| What you need | Inboundr | SendGrid Inbound Parse |
|---|---|---|
| Receive mail on a domain you own | An MX record and a DKIM record on the domain or subdomain you pick, and every address on it resolves, catch-all included. | Point a subdomain's MX record at them and give the parse setting a destination URL. Long-established and dependable at the receiving end. |
| The message as JSON | An application/json body with text and HTML, addresses, threading ids, auth verdicts and attachment descriptors. | multipart/form-data with the parsed pieces as form fields — or, if you tick the raw option, the whole MIME message in one field for you to parse. Both are workable; neither is a JSON API. |
| Fetch the message again later | Stored on arrival, listable and fetchable over REST or MCP for the plan's retention window. | The parse webhook is the only delivery. Their documentation describes no inbound store and no endpoint that returns a received message's contents, so a POST you failed to handle is not recoverable from them. |
| Proof the request came from your provider | An HMAC signature over the body and a timestamp, in a header, plus a delivery id for idempotency. | The Inbound Parse documentation describes no signature over the request; the usual practice is credentials embedded in the destination URL. Their signed event webhook is the outbound one, which is a different product. |
| Attachments your code can fetch | Stored separately and handed out as short-lived links, so the payload stays small and the file survives a failed handler. | Attachments ride in the multipart request, under a documented per-message parse limit around 30 MB. In the request or nowhere: there is no API to fetch one afterwards. |
| Filtering before delivery | Rules on sender, recipient, subject or spam verdict drop or divert the message before your endpoint is called, and blocked messages are listed. | A spam check can be enabled, which adds a score and a report to the fields your endpoint receives. The decision is still yours to make, after you have already been called. |
| Several destinations for one address | An address can point at more than one endpoint — post to your app and forward to a person from the same message. | A parse setting maps a hostname to one URL. Anything else is fan-out written in your handler. |
| A threaded reply from code | Name the message you are answering; In-Reply-To and References are written for you and the reply lands in the same conversation. | Sending is the other half of their product and is good at it, but inbound and outbound do not know about each other: the threading headers are yours to carry across. |
| An agent that works its own inbox | A hosted MCP server on the same key: list, read, thread, send, fetch attachments, manage rules. | There is no inbox to address. Parse ends at your endpoint, so anything an agent reads is something you stored and exposed yourself. |
Receive mail on a domain you own
- Inboundr
- An MX record and a DKIM record on the domain or subdomain you pick, and every address on it resolves, catch-all included.
- SendGrid Inbound Parse
- Point a subdomain's MX record at them and give the parse setting a destination URL. Long-established and dependable at the receiving end.
The message as JSON
- Inboundr
- An application/json body with text and HTML, addresses, threading ids, auth verdicts and attachment descriptors.
- SendGrid Inbound Parse
- multipart/form-data with the parsed pieces as form fields — or, if you tick the raw option, the whole MIME message in one field for you to parse. Both are workable; neither is a JSON API.
Fetch the message again later
- Inboundr
- Stored on arrival, listable and fetchable over REST or MCP for the plan's retention window.
- SendGrid Inbound Parse
- The parse webhook is the only delivery. Their documentation describes no inbound store and no endpoint that returns a received message's contents, so a POST you failed to handle is not recoverable from them.
Proof the request came from your provider
- Inboundr
- An HMAC signature over the body and a timestamp, in a header, plus a delivery id for idempotency.
- SendGrid Inbound Parse
- The Inbound Parse documentation describes no signature over the request; the usual practice is credentials embedded in the destination URL. Their signed event webhook is the outbound one, which is a different product.
Attachments your code can fetch
- Inboundr
- Stored separately and handed out as short-lived links, so the payload stays small and the file survives a failed handler.
- SendGrid Inbound Parse
- Attachments ride in the multipart request, under a documented per-message parse limit around 30 MB. In the request or nowhere: there is no API to fetch one afterwards.
Filtering before delivery
- Inboundr
- Rules on sender, recipient, subject or spam verdict drop or divert the message before your endpoint is called, and blocked messages are listed.
- SendGrid Inbound Parse
- A spam check can be enabled, which adds a score and a report to the fields your endpoint receives. The decision is still yours to make, after you have already been called.
Several destinations for one address
- Inboundr
- An address can point at more than one endpoint — post to your app and forward to a person from the same message.
- SendGrid Inbound Parse
- A parse setting maps a hostname to one URL. Anything else is fan-out written in your handler.
A threaded reply from code
- Inboundr
- Name the message you are answering; In-Reply-To and References are written for you and the reply lands in the same conversation.
- SendGrid Inbound Parse
- Sending is the other half of their product and is good at it, but inbound and outbound do not know about each other: the threading headers are yours to carry across.
An agent that works its own inbox
- Inboundr
- A hosted MCP server on the same key: list, read, thread, send, fetch attachments, manage rules.
- SendGrid Inbound Parse
- There is no inbox to address. Parse ends at your endpoint, so anything an agent reads is something you stored and exposed yourself.
When SendGrid Inbound Parse is the better choice
- Losing the occasional message genuinely does not matter. At very high volume where each message needs one field read and thrown away, fire-and-forget is the cheaper architecture and storage buys you nothing.
- You want raw MIME and own a parser already. Their raw option hands over the original bytes untouched, which is the right answer if an auditor requires exactly that.
What changes in your handler
Concretely, the parse handler starts by reconstructing a message from form fields:
export async function POST(req: Request) {
// No signature to verify: the URL's secrecy is the auth.
const form = await req.formData();
const text = form.get("text");
const info = JSON.parse(form.get("attachment-info") ?? "{}");
// The files are parts of this same request. If you want them
// after this function returns, you store them now — yourself.
for (const key of Object.keys(info)) {
await bucket.put(info[key].filename, form.get(key));
}
await handle(text); // if this throws, the message is gone
}And the same message delivered as data that outlives the request:
{
"event": "email.received",
"email": {
"id": "em_2f9c1a4b7d3e5601",
"threadId": "thr_8b21c0f4a95d",
"from": "customer@example.com",
"to": ["support@yourdomain.com"],
"subject": "Invoice 4021 looks wrong",
"text": "Hi — line 3 is billed twice…",
"attachments": [
{ "filename": "invoice-4021.pdf",
"contentType": "application/pdf",
"size": 84213 }
],
"verdicts": { "spam": "PASS", "spf": "PASS", "dkim": "PASS" },
"receivedAt": "2026-07-30T09:14:22.104Z"
}
}Three headers come with it: x-inboundr-event, x-inboundr-delivery-id for idempotency, and x-inboundr-signature, an HMAC over the body and a timestamp. A failed delivery is retried with backoff, and the message stays fetchable over REST — or over MCP, if the reader is an agent — for its retention window.
What each one costs
No prices here on purpose: they change, and a stale figure is worse than none. Check theirs at the source and ours here.
- SendGrid meters sending. Inbound Parse rides along with the plan, so if you are already a sending customer the sticker price of receiving is zero — and the real price is the pipeline described above, paid in engineering rather than invoice.
- Inboundr meters messages received, with an allowance and overage past it, and the storage, retries, attachment handling and filtering are what the meter is for.
The comparison tips on one question: how much would it cost you to lose an hour of inbound mail, or to be unable to replay a week after fixing a bug? If the answer is “not much”, parse is cheaper and that is the end of it.
Moving a parse hostname across
The cutover is one MX record, and it can be staged so that nothing is dark in between:
- Add the same subdomain in the Inboundr console — the one whose MX record currently points at them — and copy the MX and DKIM records shown.
- Publish the DKIM record. It does not affect delivery, so this is safe while parse is still live.
- Point your existing handler at the new payload, or write a small adapter: form fields become JSON, and
attachment-infobecomes a list of descriptors with links to fetch. - Add the signature check. This is new — with parse there was nothing to verify — and it is the step people skip, so do it before the endpoint is live rather than after.
- Swap the MX record, having lowered its TTL an hour beforehand. Mail in flight follows the old record until resolvers catch up, and lands at your old handler, which should stay deployed.
- Send a test with an attachment, then deliberately return a 500 from your endpoint and watch the delivery retry in the console. That is the behaviour you moved for; confirm it.
Questions people ask
Does Inbound Parse retry a failed POST?
Their documentation does not describe a retry schedule for Inbound Parse, and there is no inbound store to re-deliver from, so the safe assumption when designing a handler is that you get one attempt. Write the request to durable storage before you do anything else with it.
Can I keep sending with SendGrid and receive here?
Yes — sending and receiving are separated by DNS records, not by contract. Their MX record for the receiving subdomain is replaced and everything about your sending setup stays as it is. Threading a reply sent through them means carrying the received message’s id into its In-Reply-To header yourself.
Is the raw MIME option not the safer choice?
It is the more faithful one — you get the original message rather than someone’s interpretation of it. It also means you own a MIME parser, and real mail from real clients is where MIME parsers go to die: nested multiparts, mislabelled charsets, Apple’s inline attachments, quoted-printable in headers. It is a good option to have and a poor default to depend on.
How do I authenticate the requests today?
The common pattern is HTTP basic credentials embedded in the destination URL, since the docs describe no signature over the body. It works, with the caveat that the secret then lives in a URL, and URLs end up in access logs and error reports. An HMAC header over the payload is the shape you want here.
Would I be replacing anything else?
Usually the small service around the webhook rather than the webhook: the queue, the raw-request table, the attachment bucket and the replay script. If you have not built those yet, you are comparing parse against this product. If you have, you are comparing the maintenance of them.
Claims about SendGrid Inbound Parsewere checked against Twilio SendGrid’s public documentation for setting up the Inbound Parse webhook, their inbound email reference and their pricing page. Where this page says their documentation does not describe something, that is a statement about the documentation as it stood. Read on 30 July 2026. If something here is out of date, tell us and we will correct it.