Inbound email API

Receive email as one clean webhook

Point an MX record at us or connect a Gmail or Outlook inbox. Every message reaches your code as signed JSON: who sent it, what they actually wrote, and which conversation it continues.

What arrives on port 25.

Received: from mail-wm1-f41.google.com (209.85.128.41)DKIM-Signature: v=1; a=rsa-sha256; d=harbourly.com; s=g1; b=Kx9…Message-ID: <CAF3kq9x7Rw@mail.gmail.com>In-Reply-To: <0107019a6f2c8e14-5b1d9c3a@eu-central-1.amazonses.com>From: Grace Chen <grace@harbourly.com>To: reply+ord_8812@mail.yourapp.comSubject: Re: Order #8812 is ready to shipContent-Type: multipart/mixed; boundary="000000000000a1f2" --000000000000a1f2Content-Type: text/plain; charset="UTF-8"Content-Transfer-Encoding: quoted-printable Approved =E2=80=94 PO attached. On Mon, 21 Sep 2026, Yourapp <orders@mail.yourapp.com> wrote:> Order #8812 is ready to ship. Reply to approve. --000000000000a1f2Content-Type: application/pdf; name="PO-2231.pdf"Content-Transfer-Encoding: base64 JVBERi0xLjcKJeLjz9MKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZwovUGFnZXMgMiAwIFIKPj4KZW5kb2JqCjIgMCBvYmoKPDwgL1R5cGUgL1BhZWdlcyAvS2lkcyBbMyAwIFJdIC9Db3VudCAxID4+CmVuZG9iagozIDAg

…and the rest of the PDF, in base64

What your code receives.

POST /webhooks/emailsigned
{  "type": "email.received",  "data": {    "thread_id": "thr_8f2c",    "from": "grace@harbourly.com",    "to": ["reply+ord_8812@mail.yourapp.com"],    "category": "human",    "email": {      "stripped_text": "Approved — PO attached.",      "attachments": [{        "filename": "PO-2231.pdf",        "preparation": {          "format": "markdown",          "text": "# Purchase order PO-2231\n…"        }      }]    }  }}

Domains

Every address is already an inbox

Publish one MX record and any address on the domain receives. Put an order id in the address and it comes back with the reply.

Domains
MXmail.yourapp.com10 inbound-smtp.eu-central-1.amazonaws.com
  • support@mail.yourapp.comSupport queue
  • reply+ord_8812@mail.yourapp.comOrder #8812
  • invoices@mail.yourapp.comBilling
  • anything-at-all@mail.yourapp.comReceived too

Mailboxes

Or read an inbox that already exists

When the address lives in Gmail or Outlook, connect it over OAuth. No DNS changes, and its mail arrives as the same webhook.

Mailboxes
  • mail.yourapp.comMX
  • nora.berg@gmail.comOAuth
  • jonas@northwind.ioOAuth
email.receivedOne endpoint, one shape, whichever way it came in

Reply text

Answers found inside the quote

People reply between your lines. Each message is cut into what was written, what was quoted and the signature, and stripped_text keeps each answer under its question.

Reply text
What arrived
Approved, two answers below. On Mon, 21 Sep 2026, Yourapp <orders@mail.yourapp.com> wrote: > Order #8812 is ready to ship. Two questions first:>> Should it go to the Rotterdam warehouse?Yes, Rotterdam. Dock 4. > Which entity do we invoice?Harbourly BV. PO attached. Grace ChenProcurement, Harbourly
Cut at the first quote
Approved, two answers below.

Both answers went with the quote.

AI Inbx
Approved, two answers below. > Should it go to the Rotterdam warehouse?Yes, Rotterdam. Dock 4. > Which entity do we invoice?Harbourly BV. PO attached. Grace ChenProcurement, Harbourly

stripped_text: each answer under its question, and her signature, new to the thread.

Categories

A person, or a machine answering for one

Bounces, out-of-office replies and newsletters are labelled before they reach you, so your code can tell an autoresponder from a customer.

Categories
  • Mail Delivery Subsystem

    Undeliverable: Your order has shipped

    bounce
  • Tom Becker

    Automatic reply: Re: Invoice 2231

    out of office
  • Freightly

    New: live tracking for every pallet

    marketing
  • Grace Chen

    Re: Order #8812 is ready to ship

    human

Attachments

The PDF, already text

Attachments come with a download URL, and PDFs, documents and sheets with their content as Markdown. No parser to host.

Attachments

Quote-4471.pdf

Lind Metall GmbH

Industriestraße 14

44536 Lünen, Germany

Northwind GmbH

Attn: Nora Berg

Quote Q-4471

31 Aug 2026

#ItemQtyPrice
1Sheet 3mm500€4.20
2Cutting500€0.35
Net
€2,275.00
VAT 19%
€432.25
Total
€2,707.25

Delivery: 10 working days from order.

Payment: 30 days net.

Quote-4471.pdf.md

LLM-ready
<!-- Page 1 --> Lind Metall GmbHIndustriestraße 1444536 Lünen, Germany Northwind GmbHAttn: Nora Berg # Quote Q-4471 31 Aug 2026 | # | Item      | Qty | Price || - | --------- | --- | ----- || 1 | Sheet 3mm | 500 | €4.20 || 2 | Cutting   | 500 | €0.35 | Net €2,275.00VAT 19% €432.25Total €2,707.25 Delivery: 10 working days from order.Payment: 30 days net.

Webhooks

Built for the day your endpoint is down

Every request is signed. Anything but a 2xx is retried for 24 hours under the same event id, and a failed delivery can be replayed.

Webhooks
evt_2kq9 · email.receivedsigned
  1. ~1 min50310:31:04
  2. ~5 mintimeout10:32:06
  3. 20010:37:11

The same event id on every attempt: deduplicate on it and a retry is never a second order.

Build or buy

What an inbound pipeline takes

Receiving the mail is the easy part. The work is everything between the socket and a line of text your code can act on, and each piece has an edge case that only shows up in production.

PartOn your ownAI Inbx
Accepting mailRun an SMTP server, or wire SES or Mailgun routes to a queueOne MX record, every address live
ParsingMIME trees, charsets, quoted-printable, base64, inline imagesJSON with text, html and headers
Reply textQuote patterns per mail client, and Outlook marks nonestripped_text and segments
ConversationsIn-Reply-To and References, when clients keep themthread_id, matched on the quote too
AutorespondersHeader heuristics, until a loop gets throughcategory on every message
AttachmentsStorage, signed URLs, a PDF parserDownload URL and Markdown text
Gmail and OutlookTwo sync APIs, token refresh, history importYour OAuth app, our sync, the same webhook
DeliveryYour own retries, dead letters and replaySigned, retried for 24 hours, replayable

Set up

Receiving, in three calls

1domains.createthe records to publish
const domain = await aiinbx.domains.create({  name: "mail.yourapp.com",}) domain.records // INBOUND is the MX record
2webhookEndpoints.createthe email rides along with the event
await aiinbx.webhookEndpoints.create({  url: "https://yourapp.com/webhooks/email",  subscriptions: ["email.received"],  payload: "full",})
3your handlerpeople only, new words only
const event = await verifyWebhookRequest(request, secret) if (event.type === "email.received" && event.data.category === "human") {  const email =    event.data.email ?? (await aiinbx.emails.retrieve(event.data.email_id))  await orders.handleReply(event.data.to[0], email.stripped_text)}

Questions

What is an inbound email API?

A service that receives email for your domain and hands each message to your application over HTTP, usually as a webhook, instead of you running a mail server or polling an inbox over IMAP. AI Inbx also parses the message, cuts it down to what the sender wrote, threads it and classifies it before the request is sent.

Do I need to run a mail server to receive email in my app?

No. Publish the MX record AI Inbx returns for your domain and mail to any address on it is accepted, parsed and posted to your webhook endpoint. There is no SMTP server, IMAP polling or MIME parsing on your side.

Will the MX record take over my existing inboxes?

Yes, for the domain it is published on: the MX record routes all of that domain's mail to AI Inbx. If the domain already receives mail in Google Workspace or Microsoft 365, receive on a subdomain such as mail.yourapp.com, or connect the existing Gmail or Outlook mailbox over OAuth, which needs no DNS change at all.

How do I get only the new text of a reply?

Read stripped_text on the email. It drops the quoted conversation and a repeated signature and keeps answers typed inside the quote in place. segments gives the whole message cut into written, quoted and signature parts, in order.

How do I route replies back to the right record in my app?

Every address on a receiving domain is live, so put your id in the address: send from reply+ord_8812@mail.yourapp.com and read it back from data.to when the customer answers. For whole conversations, store the thread_id and every later reply arrives with the same one.

What happens if my webhook endpoint is down?

Anything other than a 2xx within ten seconds is retried on a backoff for up to 24 hours, with the same event id every time so you can deduplicate. Failed deliveries can be replayed, and GET /events lets a service catch up on everything it missed.

Give your product an inbox. The hard parts are already handled.

Get started