The honest SendGrid Inbound Parse alternative — MailKite Beta<br>50% off your first year — founding rate for the first 1,000 customers.<br>View pricing →
Sign in · Get started
All posts<br>SendGrid Inbound Parse is Twilio SendGrid’s inbound feature: it accepts mail sent to a domain or subdomain you’ve pointed at SendGrid via MX, and POSTs the message to your endpoint as multipart/form-data — a form upload with the headers, body text, and attachments as separate fields you parse yourself. MailKite is a parsed-inbound alternative: the same inbound mail arrives as a single JSON webhook with decoded text/html, SPF/DKIM/DMARC results, and attachments as short-lived signed URLs — no form-parsing, no charset guessing, and no daily send cap.
I want to be fair to SendGrid, because Inbound Parse is a real, widely-used feature and this is a comparison post, not a hit piece. SendGrid moves enormous volumes of mail reliably, Inbound Parse has existed for years, and for a simple “email us and we’ll POST it to a script” use case it works. If your needs are that modest, it’s a reasonable choice and you don’t need us.
This post is for when the modest case grows teeth.
What Inbound Parse actually gives you
Point your MX at mx.sendgrid.net, register a host in the Inbound Parse settings, and mail to that domain gets POSTed to your URL as multipart/form-data. You get fields like from, subject, text, html, a headers blob, and — if you tick the box — attachments as file parts plus a charsets field describing how each text field was encoded.
That last detail is the tell. The reason there’s a charsets field at all is that decoding is your job : SendGrid hands you bytes and a label, and you’re expected to re-decode each field into UTF-8 yourself. Miss it and you get the canonical bug — a £ arriving as £, an emoji as mojibake — because the field was latin-1 or ISO-2022-JP and you read it as UTF-8. This is the single most-reported Inbound Parse pain, and it’s structural: the format makes correct decoding opt-in.
Attachments compound it. They arrive as multipart file parts you extract from the request body, and developers repeatedly report attachments arriving mislabeled, re-encoded, or corrupt — filenames with the wrong charset, content types that don’t match, files that don’t round-trip. Again: not because SendGrid is careless, but because “parse a multipart form and re-decode every part” is handed back to you, and there are a hundred ways to get it subtly wrong.
And there’s one more edge that bites in production: if your endpoint returns a 4xx/5xx or has a DNS hiccup when Parse tries to POST, the message can be dropped rather than retried . Inbound you never see is the worst kind.
The honest comparison
No adjective inflation — here’s the shape of the difference:
SendGrid Inbound ParseMailKite inboundPayload formatmultipart/form-data you parseSingle JSON webhookBody decodingYou re-decode per charsets fieldtext/html pre-decoded to UTF-8AttachmentsMultipart file parts, inlineMetadata + short-lived signed urlSender authYou infer from raw headersauth{spf,dkim,dmarc,spam} in payloadOn endpoint errorCan drop with no retryAutomatic retriesFree tierSendGrid’s free tier changed in 20253,000 msgs/mo, no daily cap Still parse-work on topYes — form + decode + attachmentsNo — read the fields<br>The through-line: with Inbound Parse you own the form-parsing, the re-decoding, the attachment extraction, and the auth inference. With MailKite that work is done before the webhook reaches you.
What the JSON looks like
Same inbound email, delivered parsed:
"id": "msg_2Hk9…",<br>"type": "email.received",<br>"from": { "address": "ada@example.com" },<br>"to": [{ "address": "support@myapp.ai" }],<br>"subject": "Re: invoice #1042",<br>"text": "Looks good — approved!",<br>"html": "Looks good — approved!",<br>"threadId": "",<br>"auth": { "spf": "pass", "dkim": "pass", "dmarc": "pass", "spam": "ham" },<br>"attachments": [<br>{ "id": "msg_2Hk9…:0", "filename": "po.pdf", "contentType": "application/pdf",<br>"size": 18213, "url": "https://api.mailkite.dev/att/2Hk9…/0?exp=…&sig=…" }<br>No charsets field to reconcile — text and html are already UTF-8, so the £ is a £. Attachments are out of the payload as signed URLs. And auth is right there, which matters the moment you act on an email: a forged From: is an authorization decision, and having SPF/DKIM/DMARC in the payload means you don’t infer trust from raw headers or trust blindly.
The handler
// Express<br>import express from "express";<br>import { MailKite } from "mailkite";
const SECRET = process.env.MAILKITE_WEBHOOK_SECRET;
// Verify the RAW bytes — re-serializing breaks the HMAC.<br>app.use("/hooks/mailkite", express.raw({ type: "application/json" }));
app.post("/hooks/mailkite", (req, res) => {<br>const sig = req.headers["x-mailkite-signature"];<br>if (!MailKite.verifyWebhook(sig, req.body, SECRET)) return res.sendStatus(401);
const event = JSON.parse(req.body);<br>if (event.type === "email.received" && event.auth.spf === "pass")...