MailKite
Get your API key
All guides Inbound email parsing

Brevo inbound parsing

Inbound parsing turns the email your users send into structured data your app can act on: a reply becomes a support ticket, a forwarded PDF becomes a row in your database, an incoming message becomes a task for an agent. This guide walks through Brevo's inbound parse webhook end to end, then shows the same flow on MailKite.

What you'll need

  • A domain you control, with access to its DNS records.
  • A public HTTPS endpoint to receive the webhook (a tunnel like ngrok works while testing).
  • A Brevo account and API key for the first half.

Part 1 — Parse inbound email with Brevo

1. Point a subdomain at Brevo

Inbound parsing works on a dedicated receiving subdomain — commonly reply.yourdomain.com. Add two MX records so mail sent there is delivered to Brevo's servers instead of your own:

DNS — reply.yourdomain.com
Host                    Type   Priority   Value
reply.yourdomain.com MX 10 inbound1.sendinblue.com.
reply.yourdomain.com MX 20 inbound2.sendinblue.com.

DNS can take a few hours to propagate, so add this early. Any address on that subdomain — support@reply.yourdomain.com, ada@reply.yourdomain.com — will now flow into Brevo.

2. Register the inbound webhook

Tell Brevo where to POST parsed messages. Create an inbound webhook bound to your receiving domain:

register webhook
curl -X POST https://api.brevo.com/v3/webhooks \
-H "api-key: $BREVO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "inbound",
"events": ["inboundEmailProcessed"],
"url": "https://yourapp.com/hooks/brevo",
"domain": "reply.yourdomain.com"
}'

Brevo now calls your url with an inboundEmailProcessed event every time a message arrives.

3. Parse the payload

Brevo POSTs a JSON body with an items array — one entry per message in the batch. Each entry carries the sender, recipients, subject, both body formats, a signature-stripped ExtractedMarkdownMessage, a SpamScore, and any attachments:

POST /hooks/brevo
{
"items": [
{
"From": { "Name": "Ada Lovelace", "Address": "ada@example.com" },
"To": [{ "Address": "support@reply.yourdomain.com" }],
"Subject": "Can't update my card",
"RawTextBody": "Hi — my payment keeps failing…",
"RawHtmlBody": "<p>Hi — my payment keeps failing…</p>",
"ExtractedMarkdownMessage": "Hi — my payment keeps failing…",
"Attachments": [
{
"Name": "screenshot.png",
"ContentType": "image/png",
"ContentLength": 20418,
"DownloadToken": "eyJ0eXAiOi…"
}
],
"SpamScore": 0.7
}
]
}

Loop the batch, pull the fields you care about, and fetch attachments by token:

handle-inbound.js
import express from "express";

const app = express();
app.use(express.json());

app.post("/hooks/brevo", async (req, res) => {
// Brevo batches messages — one webhook can carry several.
for (const email of req.body.items ?? []) {
const body = email.ExtractedMarkdownMessage ?? email.RawTextBody;
console.log("from", email.From.Address, "·", email.Subject);
// ...open a ticket, reply, hand to an agent.

for (const att of email.Attachments ?? []) {
// Attachments aren't inlined — fetch each one with its DownloadToken:
// GET https://api.brevo.com/v3/inbound/attachments/${att.DownloadToken}
// (send your api-key header).
}
}
res.sendStatus(200);
});

That's inbound parsing on Brevo: MX to the receiving subdomain, an inbound webhook, and a handler that walks items and downloads attachments with their DownloadToken.

Part 2 — The same flow on MailKite

When we built inbound into MailKite, we wanted three things to be true: one record to receive on your whole domain, a payload you don't have to reshape, and a webhook you can actually trust. Here's the same parse-a-reply flow.

1. Point your domain at MailKite

One MX record makes every address on the domain receivable — no dedicated subdomain, no second record to prioritise:

DNS — yourdomain.com
Host              Type   Priority   Value
yourdomain.com MX 10 mx.mailkite.dev

The domain verifies as soon as that record resolves. See Domains & DNS for the SPF/DKIM/DMARC records that let you send from it too.

2. Register your webhook

Add your endpoint URL in the dashboard or via the API and MailKite POSTs an email.received event the moment mail arrives. Want specific addresses to hit specific endpoints? Add a route — otherwise a catch-all sends the whole domain to one webhook.

3. Parse the payload

The message arrives already decoded — flat from, to, subject, text, and html fields, edge auth verdicts (SPF/DKIM/DMARC/spam), and attachments as signed, time-limited links rather than tokens you have to redeem:

POST /hooks/mailkite
{
"id": "msg_2Hk9…",
"type": "email.received",
"from": { "address": "ada@example.com", "name": "Ada Lovelace" },
"to": [{ "address": "support@myapp.ai", "name": "Support" }],
"subject": "Re: invoice #1042",
"text": "Looks good — approved!",
"html": "<p>Looks good — approved!</p>",
"threadId": "<a1b2c3@mail.example.com>",
"receivedAt": 1785196800000,
"receivedAtIso": "2026-07-28T00:00:00.000Z",
"auth": { "spf": "pass", "dkim": "pass", "dmarc": "pass", "spam": "ham" },
"attachments": [
{
"id": "msg_2Hk9…:0",
"filename": "po.pdf",
"contentType": "application/pdf",
"size": 18213,
"url": "https://api.mailkite.dev/att/2Hk9…/0?exp=…&sig=…"
}
]
}

Because the URL is public, MailKite signs every request. Verify the signature over the raw body first, then handle the event — one message per call, no batch to unwrap:

handle-inbound
import express from "express";
import { MailKite } from "mailkite";

const SECRET = process.env.MAILKITE_WEBHOOK_SECRET;
const app = express();
app.use("/hooks/mailkite", express.raw({ type: "application/json" }));

app.post("/hooks/mailkite", (req, res) => {
if (!MailKite.verifyWebhook(req.headers["x-mailkite-signature"], req.body, SECRET)) {
return res.sendStatus(401);
}
const event = JSON.parse(req.body.toString("utf8"));
// ...handle event.type === "email.received"
// Confirm receipt: returns the JSON body {"status":"ok"}.
res.type("application/json").send(MailKite.replyOk());
});
Install Docs →

The verifyWebhook helper recomputes the HMAC and rejects forged or stale events — see Verifying signatures for the header format and a no-SDK version. Fields and attachment links are documented in full under Inbound webhooks.

A little about MailKite

MailKite is programmable email for developers and AI agents. Publish one MX record and every address on your domain becomes a signed webhook of clean, decoded JSON — no IMAP, no polling, no MIME wrangling. The same API sends mail, and any inbox can be handed straight to an agent that reads and replies on its own. Inbound and receiving are on the free tier.

Next: Inbound webhooks for routing, retries, and attachments, or the Quickstart to get a verified domain and API key.