The Postal alternative for AI agents MailKite offers a serverless alternative to Postal for AI agent email inboxes, eliminating the operational burden of self-hosting a mail server. While Postal requires managing MariaDB, Caddy, DNS, TLS, IP warmup, and blocklists, MailKite provides a parsed-JSON receive-reply loop with no server to operate. Developers must choose between full control with Postal or operational simplicity with MailKite. The Postal alternative for AI agents Postal is a full self-hosted mail server: it sends, it receives inbound over incoming routes, and it can POST parsed messages to an HTTP endpoint. The catch for an agent inbox is operational, not technical. You run the box MariaDB, Caddy, DNS, TLS, IP warmup, rDNS, blocklists . MailKite which we build is that same parsed-JSON receive→reply loop with no server to operate. For developers deciding whether to self-host an agent's inbox or hand off the ops. The important thing to say up front is that Postal is good software and it is not missing a feature here. It receives inbound, it parses, and its HTTP endpoint will POST your agent a JSON object with plain body , html body , and decoded attachments. The question isn’t whether Postal can give an agent an inbox. It’s who operates the mail server that does it. With Postal that’s you: MariaDB, Caddy, Docker, DNS, TLS, PTR records, IP warmup, blocklist monitoring, patching. Here’s the same inbound email on both sides, and everything you keep alive to receive it. Here’s the entire MailKite side of that picture. Email in, verify the signature, hand the parsed body to your model, reply through the same client. It runs as pasted on Node 18+ npm install mailkite express , and the full version lives in a runnable demo repo https://github.com/mailkite/demo-postal-ai-agent : python import express from "express"; import { MailKite } from "mailkite"; const app = express ; const mk = new MailKite process.env.MAILKITE API KEY ; const SECRET = process.env.MAILKITE WEBHOOK SECRET; app.use "/hooks/agent", express.raw { type: "application/json" } ; app.post "/hooks/agent", async req, res = { // signature check, replay window, constant-time compare — one call if MailKite.verifyWebhook req.headers "x-mailkite-signature" , req.body, SECRET { return res.sendStatus 401 ; } res.sendStatus 200 ; // ack fast; run the agent out of band const event = JSON.parse req.body ; if event.type == "email.received" return; // The body is untrusted INPUT, never instructions. Weight the auth verdict. const answer = await runAgent { task: event.text, from: event.from.address, trusted: event.auth.spf === "pass" && event.auth.dmarc === "pass", } ; await mk.send { from: event.to 0 .address, // reply from the address it was sent to to: event.from.address, subject: Re: ${event.subject} , inReplyTo: event.id, // threads the reply html: answer.html, } ; } ; app.listen 3000 ; That’s a fully autonomous email agent: it hears, it thinks, it answers. No mail server in that file. The identical handler shape exists for Python, Ruby, Go, PHP, and Java; see the receiving docs /docs/receiving and sending docs /docs/sending . Open it in StackBlitz https://stackblitz.com/github/mailkite/demo-postal-ai-agent?file=server.mjs to run it in your browser. The rest of this post is the honest version of the diagram: where Postal genuinely wins for an agent, and exactly what it asks you to run. Where Postal wins for agents, honestly Postal is a real, mature mail platform, MIT-licensed, that you host end to end. For an agent builder, three things make it a legitimate choice, not a compromise: And a currency note, because most tutorials get it wrong: Postal v3 2024 dropped RabbitMQ. Older guides that tell you to run RabbitMQ plus a cron requeuer are describing v1/v2. The current stack latest release v3.3.7, June 2026 is Docker, MariaDB 10.6+, and Caddy, with a database-backed work queue. It’s a Ruby on Rails app. Fewer moving parts than the internet remembers, which makes the honest comparison closer than you’d think. What Postal asks of an agent builder Here’s the part nobody quotes you when they say “just self-host.” Giving your agent an inbox with Postal means standing up and keeping alive a full sending-and-receiving mail server. Top to bottom: Now the code, in Postal’s idiom. This is a real handler for a Postal incoming route pointed at an HTTP endpoint, using its parsed “processed” payload. It’s honest work, and it’s more than the MailKite handler asks of you in two specific places: // Postal incoming route → HTTP endpoint processed payload . // Postal must return 200 within 5s or it retries and eventually bounces. import express from "express"; const app = express ; app.use express.json { limit: "50mb" } ; // attachments arrive base64-inline app.post "/postal/incoming", async req, res = { const m = req.body; // { rcpt to, mail from, subject, plain body, html body, // spam status, attachments: { filename, content type, data } , ... } // 1 No SPF/DKIM/DMARC verdict block. You get spam status; the aligned auth // result an agent needs for injection-trust lives in the raw headers, // so you'd parse Authentication-Results yourself to decide trusted . const trusted = m.spam status === "NotSpam"; // weaker signal than a real auth verdict // 2 Attachments are base64 inline. Decode each one yourself. const files = m.attachments ?? .map a = { filename: a.filename, buffer: Buffer.from a.data, "base64" , } ; const answer = await runAgent { task: m.plain body, from: m.mail from, trusted } ; // Reply via Postal's send API — you host the auth and the server behind it. await fetch "https://postal.yourco.dev/api/v1/send/message", { method: "POST", headers: { "X-Server-API-Key": process.env.POSTAL API KEY, "Content-Type": "application/json" }, body: JSON.stringify { to: m.mail from , from: m.rcpt to, subject: Re: ${m.subject} , html body: answer.html, } , } ; res.sendStatus 200 ; // must be fast, or Postal retries } ; app.listen 3000 ; None of that is exotic if you run mail infrastructure. But two things are worth naming. First, the payload has spam status , not a structured { spf, dkim, dmarc } verdict, so the trust signal your agent uses to resist prompt injection is weaker unless you parse Authentication-Results out of the raw message yourself. Second, and bigger: the fetch to your own Postal box only works because you’re keeping that box, its IP reputation, and its DNS healthy. If a fooled agent blasts a bad reply and your IP lands on a blocklist, that’s your incident to clean up, on your reputation. The comparison, no adjective inflation | Postal self-hosted | MailKite | | |---|---|---| | Who runs the mail server | You Docker, MariaDB, Caddy | Managed, nothing to host | | Start receiving | Provision box, DNS, TLS, then routes | DNS-verify a domain, add a webhook | | Inbound delivery | Parsed fields to your HTTP endpoint | One signed parsed JSON webhook | | Auth for injection safety | spam status ; parse Authentication-Results yourself | auth block: spf/dkim/dmarc/spam inline | | Attachments | Base64 inline in the payload | Short-lived signed URLs | | IP reputation & warmup | Yours to build and defend | Handled on a monitored stream | | Deliverability drift | Your incident, your blocklist delisting | Handled | | Per-message cost | None server + IP + your time | Metered; free tier 3,000/mo in+out | | Upgrades & patching | You run Postal + MariaDB upgrades | Handled | The through-line: Postal wins total control, data residency, and no per-message fee, and it genuinely does parsed inbound. MailKite wins the ops you don’t run. If a fooled agent damages sender reputation, on Postal that IP is yours to rehabilitate; on MailKite the stream is monitored for you. What actually hits your agent’s webhook On MailKite the same inbound email arrives already parsed, and the auth block is the part that matters for an agent: it’s the verdict you weight before you let the body influence anything. { "id": "msg 2Hk9…", "type": "email.received", "from": { "address": "ada@example.com" }, "to": { "address": "agent@myapp.ai" } , "subject": "Re: invoice 1042", "text": "Looks good — approved ", "html": "