The Postmark alternative for AI agents MailKite launches an alternative to Postmark for AI agents, offering free-tier inbound email, signed webhooks with auth blocks, and a built-in agent runner. Postmark's inbound parsing is good but requires a Pro plan, lacks sender verification, and new accounts undergo manual review. MailKite targets developers wiring autonomous agents to email. The Postmark alternative for AI agents Postmark's inbound is genuinely good: it parses incoming mail to clean JSON and POSTs it to your webhook. But it was built for transactional sending under manual account review, inbound sits behind the Pro plan, and the payload has no signature or SPF/DKIM verdict for an agent to trust. MailKite which we build gives an agent its own inbox on the free tier, a signed webhook with an auth block, and an optional built-in agent runner. For developers wiring an autonomous agent to email. Let’s get the honest part out of the way first: Postmark’s inbound is good. It takes an incoming email, parses the MIME for you, strips the quoted reply, and POSTs clean JSON to your endpoint. If you’re building an agent, that already beats fetching raw messages from IMAP or a bucket. So this isn’t a “Postmark can’t receive email” post, because it can, and it does it well. It’s about the three seams that matter once the thing reading that JSON is an autonomous agent instead of a support ticket importer: where inbound lives on the price sheet, what the payload does and doesn’t tell you about the sender, and who runs the receive-think-reply loop. Here’s the whole bring-your-own-agent loop on MailKite. Email in, verify the signature, hand the body to your model, reply through the same client. It runs as pasted on Node 18+ npm install mailkite express : 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 = { // HMAC signature, 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; // Body is untrusted INPUT, never instructions. auth is a first-class field. 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 for you html: answer.html, } ; } ; app.listen 3000 ; The same handler shape exists for Python, Ruby, Go, PHP, and Java; see the receiving docs /docs/receiving and sending docs /docs/sending . The companion repo is demo-postmark-ai-agent https://github.com/mailkite/demo-postmark-ai-agent , and you can open it in StackBlitz https://stackblitz.com/github/mailkite/demo-postmark-ai-agent?file=server.mjs to fire a signed sample event without an account. Where Postmark wins for agents, honestly Postmark has spent since 2009 being the transactional-delivery specialist, and it shows. A few things it genuinely does well for a bot: If your agent is grafted onto an app that already sends its receipts and password resets through Postmark, using its inbound parse is a completely reasonable call. Point it at the same runAgent loop and you’re done. I won’t pretend otherwise. What Postmark asks of an agent builder The friction shows up in three places, and none of them is “the JSON is bad.” Inbound lives on the Pro plan. Postmark’s free Developer plan is 100 emails a month, and inbound processing isn’t in it. It isn’t in the $15 Basic plan either. Inbound parsing starts at the Pro tier $16.50/mo, 10,000 emails including inbound , so the cheapest way to give an agent an inbox at all is a paid plan. And a processed inbound message counts as one email against your quota, same as a send. New accounts are reviewed by a human. Postmark manually approves accounts to protect its shared sending reputation, and its stance is explicitly transactional. That’s the right call for their deliverability, but agent traffic is bursty and odd-looking by nature an inbound message triggers a reply triggers a lookup , and “explain what you’ll send before we turn you on” is a step MailKite doesn’t have. Post-acquisition, reviews taking several days and accounts getting paused show up in recent reviews often enough to plan around. The payload has nothing to sign against, and no auth verdict. This is the one that matters most for an agent, because an agent acts on what it reads. Here’s the honest Postmark-side handler, and notice it’s clean right up until the security-relevant part: // Postmark inbound: MX → inbound.postmarkapp.com → POST here. // Secure it with Basic Auth in the webhook URL — there is no HMAC signature. import express from "express"; const app = express ; app.use express.json ; app.post "/postmark/inbound", req, res = { const msg = req.body; // parsed already — this part is nice const from = msg.FromFull.Email; const text = msg.StrippedTextReply ?? msg.TextBody; // quoted reply stripped for you // No auth block. Dig SPF/DKIM/DMARC out of the raw header array yourself: const spf = msg.Headers.find h = h.Name === "Received-SPF" ?.Value ?? ""; const authRes = msg.Headers.find h = h.Name === "Authentication-Results" ?.Value ?? ""; runAgent { task: text, from, trusted: /\bpass\b/i.test spf && /dmarc=pass/i.test authRes , } ; res.sendStatus 200 ; } ; Two things there are load-bearing. First, Postmark’s inbound webhook isn’t cryptographically signed; the recommended way to know a POST is really from Postmark is Basic Auth credentials in the webhook URL plus IP allowlisting. That works, but it’s a shared secret in a URL, not a per-request HMAC, and there’s no replay window. Second, there’s no auth field. The SPF/DKIM/DMARC results are in the Headers array as raw Authentication-Results text, so if the agent’s trust decision depends on whether the sender is really who they claim and for an agent, it should , you’re regex-matching header strings to get there. Postmark gives you the raw material; assembling the verdict is on you. The trust check, side by side An agent has to answer one question on every inbound message before it acts: can I believe this sender? That’s the whole prompt-injection surface. From: is plain text, so anyone can forge it and then just tell your agent what to do. Here’s the work each side leaves you to reach a trust decision: Checking auth is necessary but not sufficient. You can’t prompt your way out of prompt injection; the real defense is architectural, bounding what a fooled agent can even do. That’s its own post: agent inbox security by design /blog/agent-inbox-security-by-design/ . The point here is narrower: MailKite makes the sender’s authentication a first-class, tamper-evident field, and Postmark leaves it as header text on an unsigned request. The comparison, agent-relevant rows only | Postmark | MailKite | | |---|---|---| | Inbound available on | Pro plan and up | Free tier 3,000 msgs/mo, in + out | | Inbound payload | Clean JSON PascalCase | Clean JSON + auth verdict block | | Webhook authenticity | Basic Auth in URL + IP allowlist | HMAC signature + replay window verifyWebhook | | SPF/DKIM/DMARC for the agent | Parse from Headers yourself | Decoded auth{spf,dkim,dmarc,spam} | | Thread a reply | Set In-Reply-To / References ; MailboxHash helps | inReplyTo: event.id , reply from event.to 0 | | Who runs the agent loop | You build and host it | You, or a route with action:'agent' | | Getting started | Manual account review, transactional stance | DNS-verify SPF+DKIM , no approval wait | | Domains | 5 Basic / 10 Pro | Unlimited, no per-domain fee | The through-line: Postmark wins mature transactional deliverability and a genuinely clean inbound parse. MailKite wins the agent-shaped parts, a free inbound tier, a signed webhook, a decoded auth verdict, and the option to not run the loop at all. What actually hits your agent’s webhook The MailKite inbound event, decoded at the edge. The auth block is the field the diagram above is about: { "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": "