Build inbound email yourself, or ship it this afternoon
Receiving a message is a weekend. MIME from real clients, attachment storage, retries, threading and bounces are the quarter that follows. Skip to the finished pipeline.
Cloud pricing and service limits last checked 30 July 2026
The short version
- Receiving a message is a weekend. The project is everything after it, and that part is measured in weeks.
- The second week is MIME from real clients, attachments bigger than your request limit, a queue and retries, idempotency, spam policy, threading, bounces, and a way to answer “did we get their email?”
- None of it is hard. It is broad — and the tail never ends, because mail never stops changing.
- All of it is already running here, behind two DNS records. Build instead when inbound mail is your product, or when the data may not leave your estate.
What the weekend actually buys
The first version works, and quickly. Point an MX record at a cloud receiving service or an MTA you run, hand the message to a function, run it through a MIME library, write the result to a table. By Sunday evening you can send yourself an email and watch a row appear. It is a good feeling and it is not a false one — that really is the shape of the thing.
Nothing on this page disputes that estimate. What it disputes is that the estimate covers the project — because the demo and the pipeline differ by everything that happens once the message is not the one you sent yourself from your own laptop, and every item of that difference is already built here.
The second week
These arrive in roughly this order, and none of them is exotic — every one is a thing real senders do:
- MIME from real clients. A message whose HTML part is nested two multiparts deep. A charset header that lies. Encoded words in a subject line. An inline signature image that arrives looking exactly like an attachment. Each is a small fix; together they are the long tail that makes people say parsing email is harder than it looks.
- The 25 MB attachment, on the day your function has a 6 MB request limit. Now attachments need somewhere to live before your handler runs, a key convention, signed URLs to hand out, and a lifecycle rule so storage does not grow forever.
- Your handler was down. Without a queue and somewhere to hold the message, that mail is gone — and unlike a failed API call, the sender believes it was delivered. This is the single most common reason a homegrown pipeline gets rebuilt.
- Duplicates. Retries mean the same message can arrive twice, so every consumer needs an idempotency key and the discipline to use it.
- Spam, and then more spam. A published address is scraped within weeks. Verdicts have to be read, thresholds chosen, and a policy written for the messages that soft-fail — which will include mail from a real customer using a misconfigured corporate relay.
- Replies. The moment you answer a message you need a sending path, a verified domain, threading headers, and somewhere for bounces and complaints to go. That is usually where a second vendor enters the diagram.
- Operations.Someone has to answer “did we get their email?” at 4pm on a Friday, which means logs, a way to look a message up, and enough retention to answer about last week rather than the last hour.
None of this is difficult
That is exactly why it gets underestimated. There is no hard computer science here — just a long list of small, well-understood tasks, each needing a decision, a test and a maintainer for as long as the product lives. The cost is not difficulty. It is breadth, and breadth is what estimates miss.
Side by side
Every partlyin the right-hand column means “yes, once you build and maintain it”. That is a real answer, not a no — the question is only whether this is the list you want on your roadmap.
| What you need | Inboundr | building it yourself |
|---|---|---|
| Accept mail for your domain | Two DNS records and the domain is live, catch-all included. | A cloud receiving service, or an MTA you run. Genuinely a short job — this is the part that makes the whole project look small. |
| Turn real MIME into usable text | Text and HTML bodies, decoded and normalised, whatever the sending client did. | A library gets you 90% in an afternoon. The last 10% is nested multiparts, mislabelled charsets, encoded words in headers, inline images posing as attachments, and one desktop client from 2009 — and it is a long 10%. |
| Attachments stored and fetchable | Stored on arrival, handed out as short-lived links from the payload, the API or an agent tool call. | A bucket, a key convention, signed URLs, a lifecycle policy and a size limit on the way in. All standard work, none of it free, and all of it yours to keep working. |
| Delivery to your app that survives an outage | Retries with backoff, an idempotency id per attempt, and the message still fetchable afterwards. | A queue, a retry policy, a dead-letter path and somewhere to hold the message meanwhile — the difference between a demo and a pipeline, and the second week of the project. |
| Spam and authentication handled | SPF, DKIM and spam verdicts on every message, with rules that can drop or divert on them before delivery. | Verdicts are available from most receiving services. Acting on them — and deciding what a soft fail means for a message a customer is waiting for — is policy you write and then tune. |
| Replies that land in the same conversation | Name the message you are answering; In-Reply-To and References are written for you and the thread is queryable. | Threading is a documented standard and not hard to implement — it is just one more thing to model, store and get right, and email clients are unforgiving when it is wrong. |
| Sending, bounces and reputation | Replies go out on a verified domain, with bounces handled and surfaced. | Sending is usually a second vendor, plus bounce and complaint handling, plus the warm-up and DNS work behind a domain nobody has seen before. |
| An interface an agent can use | A hosted MCP server on the same key: list, read, thread, send, fetch attachments, manage rules. | Your database plus an internal API plus scoped credentials plus a tool schema you keep in step with both. Perfectly doable; it is simply a project of its own. |
| Working this afternoon | Two DNS records, an endpoint, a test message. Everything above is already running, and none of it is on your roadmap. | A demo by Sunday. The list above is the difference between the demo and something you would put a customer's mail through, and it is measured in weeks. |
Accept mail for your domain
- Inboundr
- Two DNS records and the domain is live, catch-all included.
- building it yourself
- A cloud receiving service, or an MTA you run. Genuinely a short job — this is the part that makes the whole project look small.
Turn real MIME into usable text
- Inboundr
- Text and HTML bodies, decoded and normalised, whatever the sending client did.
- building it yourself
- A library gets you 90% in an afternoon. The last 10% is nested multiparts, mislabelled charsets, encoded words in headers, inline images posing as attachments, and one desktop client from 2009 — and it is a long 10%.
Attachments stored and fetchable
- Inboundr
- Stored on arrival, handed out as short-lived links from the payload, the API or an agent tool call.
- building it yourself
- A bucket, a key convention, signed URLs, a lifecycle policy and a size limit on the way in. All standard work, none of it free, and all of it yours to keep working.
Delivery to your app that survives an outage
- Inboundr
- Retries with backoff, an idempotency id per attempt, and the message still fetchable afterwards.
- building it yourself
- A queue, a retry policy, a dead-letter path and somewhere to hold the message meanwhile — the difference between a demo and a pipeline, and the second week of the project.
Spam and authentication handled
- Inboundr
- SPF, DKIM and spam verdicts on every message, with rules that can drop or divert on them before delivery.
- building it yourself
- Verdicts are available from most receiving services. Acting on them — and deciding what a soft fail means for a message a customer is waiting for — is policy you write and then tune.
Replies that land in the same conversation
- Inboundr
- Name the message you are answering; In-Reply-To and References are written for you and the thread is queryable.
- building it yourself
- Threading is a documented standard and not hard to implement — it is just one more thing to model, store and get right, and email clients are unforgiving when it is wrong.
Sending, bounces and reputation
- Inboundr
- Replies go out on a verified domain, with bounces handled and surfaced.
- building it yourself
- Sending is usually a second vendor, plus bounce and complaint handling, plus the warm-up and DNS work behind a domain nobody has seen before.
An interface an agent can use
- Inboundr
- A hosted MCP server on the same key: list, read, thread, send, fetch attachments, manage rules.
- building it yourself
- Your database plus an internal API plus scoped credentials plus a tool schema you keep in step with both. Perfectly doable; it is simply a project of its own.
Working this afternoon
- Inboundr
- Two DNS records, an endpoint, a test message. Everything above is already running, and none of it is on your roadmap.
- building it yourself
- A demo by Sunday. The list above is the difference between the demo and something you would put a customer's mail through, and it is measured in weeks.
When building it yourself is the better choice
- Inbound mail is the product. If you are building a mail client or a shared inbox, receiving is the thing you differentiate on and you should own it.
- Mail may not touch a third party. Regulated environments, government work and on-premise deployments decide this for you, and no feature table changes it.
What it actually costs
The cloud bill for receiving mail is small — that is not where this comparison lives. The cost is engineering time, and it has two parts: the build, and the tail.
- The build. Parser hardening, attachment storage, queue and retries, idempotency, spam policy, threading, sending and bounces, plus the console or queries someone uses to answer questions about a message. Estimate it in weeks rather than days and you will be closer.
- The tail. Mail does not stop changing. Providers tighten authentication requirements, clients invent new ways to encode a body, a bucket policy expires, a queue backs up on a bank holiday. This is the part that never appears in the original estimate and never goes away.
Set that against a per-message price and the answer is usually obvious: unless you are at very high volume with trivial handling, building spends the one resource that is actually scarce. The tie-breaker is simple — is receiving email something your team wants to be good at, or something that has to work while they build the product?
If you are building anyway
Plenty of readers should, so here is the short list of things that most often get missed. It is worth having whether or not you ever use this product:
- Persist the raw message before you parse it. Every recoverable mistake downstream depends on still having the original.
- Make consumers idempotent from day one, keyed on a delivery id rather than the message id — senders reuse those more than you would like.
- Get attachments out of the request path immediately, with a size limit at the edge and a lifecycle policy on the bucket.
- Decide the spam policy explicitly, including what happens on a soft fail, and log the verdict with the message so a false positive can be explained later.
- Store
Message-ID,In-Reply-ToandReferenceson the way in, even before you support threading. Reconstructing threads later without them is not possible. - Build the “did we receive this?” lookup early. It is the tool your support team will use daily and the one nobody schedules.
For comparison, the same message handed over as data looks like this, and the three headers alongside it — x-inboundr-event, x-inboundr-delivery-id and x-inboundr-signature — are the second and third items on that list, done for you:
{
"event": "email.received",
"email": {
"id": "em_2f9c1a4b7d3e5601",
"threadId": "thr_8b21c0f4a95d",
"messageId": "<CAF9xq2@mail.example.com>",
"from": "customer@example.com",
"to": ["support@yourdomain.com"],
"subject": "Invoice 4021 looks wrong",
"text": "Hi — line 3 is billed twice…",
"html": "<p>Hi — line 3 is billed twice…</p>",
"attachments": [
{ "filename": "invoice-4021.pdf",
"contentType": "application/pdf",
"size": 84213 }
],
"verdicts": { "spam": "PASS", "spf": "PASS", "dkim": "PASS" },
"receivedAt": "2026-07-30T09:14:22.104Z"
}
}Questions people ask
Is this not just an argument to buy your product?
It is a page on our site, and the list in the second week is the part you can check independently — every item is a well-known problem that anyone who has run inbound mail will recognise. Ask someone who has. The only variable we cannot fill in is what your team’s weeks are worth.
Can I start with you and move to my own pipeline later?
Yes, and it is a reasonable sequence: the switch is an MX record. Messages are fetchable over the REST API for their retention window, so you can pull recent history out before you cut over. There is no export of mail older than the window, so plan the move rather than discovering the limit on the day.
What if I only need one address for one workflow?
Then the free plan is aimed exactly at you: one address, a webhook, no card. A weekend of your own code also works, and the thing that tips it is whether the mail is human or machine — machine-generated notifications are easy to own, and mail from people is where the long tail lives.
Does an agent change the calculation?
It adds one more layer to the build: an agent needs an interface, which means an internal API, credentials scoped to it, and a tool schema kept in step with both. If the reason you are receiving mail is that something autonomous should read it, that layer is part of the estimate. See giving an agent its own address for the shape of it.