cd /news/ai-agents/the-postmark-alternative-for-ai-agen… · home topics ai-agents article
[ARTICLE · art-47497] src=mailkite.dev ↗ pub= topic=ai-agents verified=true sentiment=· neutral

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.

read9 min views1 publishedJul 4, 2026
The Postmark alternative for AI agents
Image: source

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

):

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 and sending docs. The companion repo is demo-postmark-ai-agent, and you can

open it in StackBlitzto 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 d 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. 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": "<p>Looks good — approved!</p>",
  "threadId": "<a1b2c3@mail.example.com>",
  "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 request is signed, the agent knows the payload is really from MailKite before it reads a byte; because auth

is decoded, it can weigh the sender’s instructions without regexing headers. Treat the body as data, never as instructions. See webhook security.

Or don’t run the loop at all #

If you’d rather not host the receive-think-reply loop, MailKite (which we build) can run it for you. Point a route at action: "agent"

with a free-text prompt, and each inbound message runs the model loop on a durable queue, capped at a few tool rounds, aborted by a reaper if it hangs, and recorded as a transcript you can drill into:

await mk.createRoute({
  match: "agent@myapp.ai",
  action: "agent",
  agentPrompt: "Answer billing questions from the docs. Escalate anything else to humans@myapp.ai.",
});

The system prompt bakes in the safety rules: the body is untrusted, at most one reply, never reply to automated senders. There’s no equivalent to this on Postmark; inbound there ends at “here’s the parsed JSON, the rest is yours.” Details in the inbox-agents docs.

FAQ #

Can Postmark receive and parse inbound email for an agent? Yes, and it does it well. Postmark parses inbound MIME to clean JSON (FromFull

, TextBody

, StrippedTextReply

, decoded attachments) and POSTs it to your webhook. The caveats are that inbound processing starts on the Pro plan, and the payload is unsigned with no decoded SPF/DKIM/DMARC verdict, so an agent’s trust check is more work.

Does the Postmark inbound webhook have a signature I can verify? No. Postmark doesn’t sign inbound webhooks with an HMAC; the documented way to authenticate the request is Basic Auth credentials embedded in the webhook URL plus IP allowlisting. MailKite signs every webhook and gives you a one-call verifyWebhook

that also enforces a replay window.

How does an agent get SPF/DKIM/DMARC results from Postmark? By reading them out of the Headers

array (the raw Authentication-Results

and Received-SPF

values) and parsing the strings yourself. MailKite delivers a decoded auth: { spf, dkim, dmarc, spam }

block as a top-level field.

Is Postmark a good fit for AI agent traffic? For sending, its transactional deliverability is strong. For an autonomous inbox, the friction is that inbound needs a paid plan, accounts go through a manual transactional-focused review, and the loop is entirely yours to build. If those don’t bother you and you’re already on Postmark, its inbound parse is fine to build on.

Can I keep sending on Postmark and just receive on MailKite? Yes. The webhook is plain HTTPS and the API is REST, so you can leave your outbound on Postmark and point a domain’s MX at MailKite for the agent inbox. You’re replacing the receive-and-reply half, not your sending setup. Point a domain at MailKite to try it.

Postmark’s inbound is good enough that the honest pitch isn’t “parsed JSON,” because you already get that. It’s the free inbound tier, the signed webhook, the decoded auth

field, and the option to let MailKite run the agent loop for you. Clone demo-postmark-ai-agent (or

run it in your browser), then

point a domain at MailKite.

Related: the pillar on giving your agent an inbox, agent inbox security by design, and the full MailKite vs Postmark inbound comparison.

── more in #ai-agents 4 stories · sorted by recency
── more on @mailkite 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/the-postmark-alterna…] indexed:0 read:9min 2026-07-04 ·