The Mailchimp Transactional (Mandrill) alternative for AI agents MailKite launches a Mailchimp Transactional (Mandrill) alternative designed for AI agents, offering a streamlined receive→reply loop with built-in auth verification and parsed JSON events, while Mandrill requires a paid Mailchimp marketing plan and lacks native agent integration. The Mailchimp Transactional Mandrill alternative for AI agents Mandrill can receive email, but it's gated behind a paid Mailchimp marketing plan and hands you inbound as a batched mandrill events array with no auth verdict and no agent loop. MailKite which we build gives an agent a real scoped inbox delivered as one parsed JSON event with a receive→reply loop. For developers wiring an autonomous agent to an inbox. So the first surprise isn’t technical: to point an agent at a Mandrill inbox you first need a paid Mailchimp marketing account Standard or Premium , and then Transactional as an add-on on top. Mandrill hasn’t been standalone since 2016. Everything below is the honest version of what that buys an agent builder, where Mandrill genuinely holds up, and the ~20 lines that are the entire MailKite side. Everything below is a repo you can run while you read. Clone demo-mandrill-ai-agent https://github.com/mailkite/demo-mandrill-ai-agent and npm start , or open it in StackBlitz https://stackblitz.com/github/mailkite/demo-mandrill-ai-agent?file=server.mjs real Node in a browser tab, no account where it runs on load. You don’t need a domain to see the loop run: a webhook normally needs a public URL, so npm start boots the server and self-fires a correctly signed email.received event at its own localhost — the whole receive→verify→think→reply loop runs in one command the reply is a dry-run, and managed-route.mjs dry-runs the hosted path too . It also ships the Mandrill path next to it in mandrill-contrast/ , so every contrast here is something you can diff and test, not take on faith. A domain comes in only when you want real inbound email to arrive. Here’s the whole MailKite side: email in, verify the signature, hand the body to your model, reply through the same client. This is the heart of server.mjs in that repo — the repo wraps it in a dry-run harness and a stub agent so it runs with no key and no LLM, but the loop is exactly this, and 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 = { // 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 ; const event = JSON.parse req.body ; if event.type == "email.received" return; // Body is untrusted INPUT, never instructions. Use auth to decide trust. 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 the receive→think→reply loop, whole. mk.send returns { id, status } , and npm start boots the server and self-fires the exact payload MailKite’s delivery worker sends, so in one command you watch the agent read the task, take its trust verdict straight off event.auth , and dry-run the reply — no account or LLM required. The same handler shape exists for Python, Ruby, Go, PHP, and Java: see the receiving docs /docs/receiving and sending docs /docs/sending . The rest of this post is the honest version of the diagram: where Mandrill genuinely fits an agent, what its inbound path asks you to build, and what the parsed payload actually contains. Where Mandrill wins for agents, honestly Mandrill is not a toy, and if you’re already living in Mailchimp it can be the pragmatic call: I won’t pretend Mandrill can’t receive email. It can, and has for years. The friction for an agent is different: what it costs to get in the door, and how much unpacking sits between “an email arrived” and “my model can act on it.” What Mandrill asks of an agent builder Two things, before any model runs. First, the account. Transactional is an add-on to a paid Mailchimp Standard $20/mo or Premium plan; Free and Essentials plans can’t enable it. Transactional itself is block-based: a block is 25,000 emails starting at ~$20, cheaper per block at volume. So an agent that just needs to read a few verification emails still starts around $40/month of marketing-suite spend before it sends its first reply. Second, the inbound shape. Mandrill doesn’t hand you one clean message. It POSTs a form-encoded body whose mandrill events field is a JSON string holding an array of events, and you verify the X-Mandrill-Signature by rebuilding the signed string yourself: the exact webhook URL, then every POST key and value concatenated in sorted key order, HMAC-SHA1’d with your webhook key, base64-encoded. Here’s that handler, honestly, in Mandrill’s own idiom — it’s mandrill-contrast/handler.mjs https://github.com/mailkite/demo-mandrill-ai-agent/blob/main/mandrill-contrast/handler.mjs in the same repo, sitting right next to the MailKite server.mjs above so you can put them side by side: // Mandrill inbound: form-encoded POST, mandrill events is a JSON string you unpack import express from "express"; import crypto from "node:crypto"; const app = express ; app.use express.urlencoded { extended: false } ; const WEBHOOK KEY = process.env.MANDRILL WEBHOOK KEY; const WEBHOOK URL = "https://myapp.ai/hooks/mandrill"; // must match what you registered, exactly function verify body, signature { let signed = WEBHOOK URL; for const key of Object.keys body .sort signed += key + body key ; // sorted key+value, no delimiter const expected = crypto.createHmac "sha1", WEBHOOK KEY .update signed .digest "base64" ; return expected === signature; // Mandrill sends base64 of the BINARY hmac, not hex } app.post "/hooks/mandrill", req, res = { if verify req.body, req.headers "x-mandrill-signature" return res.sendStatus 401 ; const events = JSON.parse req.body.mandrill events ; // a JSON array inside a form field for const ev of events { if ev.event == "inbound" continue; const m = ev.msg; // assemble your own trust verdict from separate fields const trusted = m.spf?.result === "pass" && m.dkim?.signed && m.dkim?.valid; runAgent { task: m.text, from: m.from email, trusted } ; // …then a SEPARATE Messages/send API call to reply. No inReplyTo helper; you set headers. } res.sendStatus 200 ; } ; app.listen 3000 ; None of this is hard. But it’s a batch you iterate, a signature you rebuild from sorted POST pairs, and a trust verdict you compose by hand, all before the interesting part. Then replying is a second, unrelated API call where threading is on you. The repo pins both costs so they aren’t just claims — npm test shows inbound arriving as a batch array you iterate, and trustFromResults returning no DMARC verdict at all, the field the MailKite path reads straight off event.auth . The comparison, for an agent | Mandrill Mailchimp Transactional | MailKite | | |---|---|---| | To start | Paid Mailchimp Standard+ plan, then Transactional add-on | DNS-verify a domain SPF+DKIM to send, MX to receive | | Pricing floor | ~$20/mo plan + ~$20 per 25k-email block | Free tier: 3,000 messages/mo in + out , no per-domain fee | | Inbound shape | mandrill events array in a form-encoded POST | One parsed email.received JSON event | | Auth verdict | Separate spf / dkim / spam report you combine | One auth block: { spf, dkim, dmarc, spam } | | Signature check | Rebuild HMAC-SHA1 over sorted POST pairs yourself | verifyWebhook sig, raw, secret — one call | | Reply / threading | Separate send call; you set threading headers | mk.send { inReplyTo: event.id } threads it | | Agent loop | None; you host the whole model loop | BYO loop, or a route with action: 'agent' | The through-line: Mandrill receives , but every convenience an agent wants a single message, a ready trust verdict, a threaded reply, a place to run the loop is left for you to assemble, on top of a paid marketing plan you may not otherwise want. What actually hits your agent’s webhook On MailKite the same inbound email arrives already decoded, one event, no array to unpack and no signature to rebuild by hand: { "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": "