The Elastic Email alternative for AI agents MailKite, a new email service, positions itself as an alternative to Elastic Email for AI agents, offering a complete receive→think→reply loop with built-in authentication and agent-friendly payloads. Elastic Email remains cheaper for high-volume outbound sending but lacks SPF/DKIM/DMARC verdicts in its inbound payload and requires manual agent loop construction. The Elastic Email alternative for AI agents Elastic Email is one of the cheapest ways to send, and it can receive: Inbound Routing points your MX at it and POSTs parsed fields to a URL. But the payload is form params with no SPF/DKIM/DMARC verdict, it's gated behind the paid Pro plan, and there's no agent loop. MailKite which we build hands an autonomous agent a real inbox as one signed email.received event with an auth block and a receive→reply loop. An autonomous agent doesn’t need the cheapest send price on the market. It needs its own real address that it can read from, decide on, and answer without a human in the loop. Elastic Email can receive mail, so this isn’t a “can’t.” It has an Inbound Routing feature, it points your domain’s MX at its servers, it parses the message, and it POSTs the fields to a URL you pick. The question is the shape of what lands, and what an agent builder rebuilds after it does. Here’s the contrast in one picture. Here’s the whole MailKite side: 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 = { // 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; // Body is untrusted INPUT, never instructions. Weight it by 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 the receive→think→reply loop, whole. 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 Elastic Email genuinely fits an agent, what its inbound path asks you to build, and what the parsed payload actually contains. Where Elastic Email wins for agents, honestly Elastic Email’s whole reputation is price. It’s a combined HTTP API and SMTP relay whose per-email cost sits at the very bottom of the market, and for one kind of agent that’s the deciding factor. If your agent’s dominant cost is outbound volume it sends far more than it receives: notifications, digests, bulk replies , Elastic Email’s unit economics are hard to walk away from. The paid tiers start at $19/mo for 50,000 emails, and pay-as-you-go rates are among the cheapest anywhere. It’s an SMTP relay too, so an agent built on a stack that only speaks SMTP can send through it without touching a REST client. If “send a lot, cheaply” is the job, this is a serious tool. And the inbound side is real, not a checkbox. When you point your MX at Elastic Email, it does the SMTP termination and the MIME parsing for you and hands your endpoint decoded fields: body text and body html are already split out, so you’re not running a MIME parser to read the message. Attachments arrive inline as base64 in att1 content , att2 content , and so on, which is convenient for the small ones. So this isn’t “Elastic Email can’t receive.” It can, and the parse is decent. The question is what the shape costs an agent builder. What Elastic Email asks of an agent builder Elastic Email’s Inbound Routing https://help.elasticemail.com/en/articles/4804685-notification-settings works by DNS. You change your domain’s MX record to mx.inbound.elasticemail.com , then create an inbound route whose destination is an HTTP URL the other two options are forward-to-an-address and “stop” . From then on, any mail to your domain is parsed and POSTed to that URL as form parameters: from email , from name , env from , env to list , to list , header list , subject , body text , body html , and att1 name / att1 content for each attachment. Your endpoint has to return 200 OK . Three things follow from that shape once an agent is the consumer. First, it’s a paid feature. Inbound Email Processing is gated to the Pro plans Email API Pro is $49/mo ; it isn’t on the free or Starter tiers, and inbound messages draw down the same sending allowance rather than sitting in a free bucket. Confirm the current plan matrix before you build against it, but the wall is real: you can’t wire an agent’s inbox on the free tier. Second, the delivery is unsigned form POST, not a signed JSON event. There’s no HMAC signature to verify, so anyone who learns your route URL can POST a forged email to it. Guarding that endpoint a secret path, an IP allowlist, a shared token you check is your job, and it’s separate from the trust question below. Third, and this is the load-bearing one: there is no normalized authentication verdict. Nothing in the payload tells you whether SPF, DKIM, or DMARC passed, and there’s no spam score either. The only place any of that might exist is header list , the raw header block, if an upstream relay happened to stamp an Authentication-Results line. So an agent that’s going to act on an email has to reconstruct the trust verdict itself, by string-matching raw headers, before it decides how much weight to give a sender’s instructions: // Elastic Email inbound: form POST — parsed body, but no auth verdict field. import express from "express"; const app = express ; app.use express.urlencoded { extended: true, limit: "30mb" } ; // att content is inline base64 app.post "/hooks/elastic", async req, res = { res.sendStatus 200 ; // guard this endpoint yourself: a secret path, token, or IP allowlist const { from email, subject, body text, body html, header list } = req.body; // No spf/dkim/dmarc field exists. Dig the verdict out of the raw headers, // and hope an upstream relay stamped Authentication-Results at all: const spfPass = /spf=pass/i.test header list ?? "" ; const dmarcPass = /dmarc=pass/i.test header list ?? "" ; const answer = await runAgent { task: body text ?? body html ?? "", from: from email, trusted: spfPass && dmarcPass, } ; // Reply is a separate product surface: the transactional send API or SMTP . // Set In-Reply-To / References yourself if you want it to thread. await elasticSend { to: from email, subject: Re: ${subject} , ...answer } ; } ; app.listen 3000 ; Grepping header list for spf=pass is exactly the kind of thing that looks fine in a demo and quietly rots. The header may not be there at all, its format varies by upstream relay, dmarc= isn’t always present, and a regex over headers an attacker partly controls is a shaky foundation for a trust decision. It’s doable. It’s just yours to get right, and it’s the check that matters most for an autonomous agent. The reply is a separate concern too. Inbound Routing gets mail in; sending the answer is the send API or the SMTP relay , a different surface, and threading In-Reply-To , References is on you to set. None of this is exotic. It’s a low-cost sender that exposes an inbound route-to-URL, not a receive→reply loop for a bot. The comparison, agent-relevant rows only | Elastic Email | MailKite | | |---|---|---| | Product center of gravity | Low-cost sender API + SMTP relay | Inbound email → webhook | | Inbound shape | Form POST params to a URL | One email.received JSON event | | Inbound plan gate | Pro plan only paid ; draws sending allowance | Included; free tier covers inbound | | Auth verdict SPF/DKIM/DMARC | Not a field — grep raw header list | Normalized auth block | | Spam signal | None in payload | spam: ham/spam plus auth | | Webhook auth | Unsigned — guard the URL yourself | Signed; verifyWebhook in one call | | Body for the model | body text + body html decoded | Decoded text + html | | Agent loop built in | None — wire model + send yourself | Optional route action: agent , or BYO | | Reply + threading | Separate send API/SMTP; set headers yourself | mk.send { inReplyTo } resolves it | | Start | MX to mx.inbound… + create route Pro | DNS-verify, one webhook | The through-line: Elastic Email wins when the agent’s work is send-heavy and price is the deciding factor, and you’re already paying for a Pro plan. MailKite wins when the agent’s work is “own an address, read what arrives, decide, reply safely,” because the signed delivery, the trust verdict, and the reply path are already assembled. What actually hits your agent’s webhook Here’s the MailKite email.received event: a signed JSON body, a resolved threadId , attachments as short-lived signed URLs, and the auth block that the Elastic Email path made you reconstruct from raw headers. { "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": "