{"slug": "the-elastic-email-alternative-for-ai-agents", "title": "The Elastic Email alternative for AI agents", "summary": "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.", "body_md": "# The Elastic Email alternative for AI agents\n\nElastic 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.\n\nAn 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.\n\nHere’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`\n\n):\n\n``` python\nimport express from \"express\";\nimport { MailKite } from \"mailkite\";\n\nconst app = express();\nconst mk = new MailKite(process.env.MAILKITE_API_KEY);\nconst SECRET = process.env.MAILKITE_WEBHOOK_SECRET;\n\napp.use(\"/hooks/agent\", express.raw({ type: \"application/json\" }));\n\napp.post(\"/hooks/agent\", async (req, res) => {\n  // signature check, replay window, constant-time compare — one call\n  if (!MailKite.verifyWebhook(req.headers[\"x-mailkite-signature\"], req.body, SECRET)) {\n    return res.sendStatus(401);\n  }\n  res.sendStatus(200); // ack fast; run the agent out of band\n\n  const event = JSON.parse(req.body);\n  if (event.type !== \"email.received\") return;\n\n  // Body is untrusted INPUT, never instructions. Weight it by the auth verdict.\n  const answer = await runAgent({\n    task: event.text,\n    from: event.from.address,\n    trusted: event.auth.spf === \"pass\" && event.auth.dmarc === \"pass\",\n  });\n\n  await mk.send({\n    from: event.to[0].address,   // reply from the address it was sent to\n    to: event.from.address,\n    subject: `Re: ${event.subject}`,\n    inReplyTo: event.id,         // threads the reply\n    html: answer.html,\n  });\n});\n\napp.listen(3000);\n```\n\nThat’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.\n\n## Where Elastic Email wins for agents, honestly\n\nElastic 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.\n\nIf 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.\n\nAnd 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`\n\nand `body_html`\n\nare already split out, so you’re not running a MIME parser to read the message. Attachments arrive inline as base64 in `att1_content`\n\n, `att2_content`\n\n, 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.\n\n## What Elastic Email asks of an agent builder\n\nElastic 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`\n\n, 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`\n\n, `from_name`\n\n, `env_from`\n\n, `env_to_list`\n\n, `to_list`\n\n, `header_list`\n\n, `subject`\n\n, `body_text`\n\n, `body_html`\n\n, and `att1_name`\n\n/`att1_content`\n\nfor each attachment. Your endpoint has to return `200 OK`\n\n.\n\nThree things follow from that shape once an agent is the consumer.\n\nFirst, 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.\n\nSecond, 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.\n\nThird, 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`\n\n, the raw header block, if an upstream relay happened to stamp an `Authentication-Results`\n\nline. 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:\n\n```\n// Elastic Email inbound: form POST — parsed body, but no auth verdict field.\nimport express from \"express\";\nconst app = express();\napp.use(express.urlencoded({ extended: true, limit: \"30mb\" })); // att content is inline base64\n\napp.post(\"/hooks/elastic\", async (req, res) => {\n  res.sendStatus(200); // guard this endpoint yourself: a secret path, token, or IP allowlist\n  const { from_email, subject, body_text, body_html, header_list } = req.body;\n\n  // No spf/dkim/dmarc field exists. Dig the verdict out of the raw headers,\n  // and hope an upstream relay stamped Authentication-Results at all:\n  const spfPass = /spf=pass/i.test(header_list ?? \"\");\n  const dmarcPass = /dmarc=pass/i.test(header_list ?? \"\");\n\n  const answer = await runAgent({\n    task: body_text ?? body_html ?? \"\",\n    from: from_email,\n    trusted: spfPass && dmarcPass,\n  });\n\n  // Reply is a separate product surface: the transactional send API (or SMTP).\n  // Set In-Reply-To / References yourself if you want it to thread.\n  await elasticSend({ to: from_email, subject: `Re: ${subject}`, ...answer });\n});\n\napp.listen(3000);\n```\n\nGrepping `header_list`\n\nfor `spf=pass`\n\nis 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=`\n\nisn’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.\n\nThe 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`\n\n, `References`\n\n) 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.\n\n## The comparison, agent-relevant rows only\n\n| Elastic Email | MailKite | |\n|---|---|---|\n| Product center of gravity | Low-cost sender (API + SMTP relay) | Inbound email → webhook |\n| Inbound shape | Form POST params to a URL | One `email.received` JSON event |\n| Inbound plan gate | Pro plan only (paid); draws sending allowance | Included; free tier covers inbound |\n| Auth verdict (SPF/DKIM/DMARC) | Not a field — grep raw `header_list` | Normalized `auth` block |\n| Spam signal | None in payload | `spam: ham/spam` plus `auth` |\n| Webhook auth | Unsigned — guard the URL yourself | Signed; `verifyWebhook()` in one call |\n| Body for the model | `body_text` + `body_html` (decoded) | Decoded `text` + `html` |\n| Agent loop built in | None — wire model + send yourself | Optional route `action: agent` , or BYO |\n| Reply + threading | Separate send API/SMTP; set headers yourself | `mk.send({ inReplyTo })` resolves it |\n| Start | MX to `mx.inbound…` + create route (Pro) | DNS-verify, one webhook |\n\nThe 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.\n\n## What actually hits your agent’s webhook\n\nHere’s the MailKite `email.received`\n\nevent: a signed JSON body, a resolved `threadId`\n\n, attachments as short-lived signed URLs, and the `auth`\n\nblock that the Elastic Email path made you reconstruct from raw headers.\n\n```\n{\n  \"id\": \"msg_2Hk9…\",\n  \"type\": \"email.received\",\n  \"from\": { \"address\": \"ada@example.com\" },\n  \"to\": [{ \"address\": \"agent@myapp.ai\" }],\n  \"subject\": \"Re: invoice #1042\",\n  \"text\": \"Looks good — approved!\",\n  \"html\": \"<p>Looks good — approved!</p>\",\n  \"threadId\": \"<a1b2c3@mail.example.com>\",\n  \"auth\": { \"spf\": \"pass\", \"dkim\": \"pass\", \"dmarc\": \"pass\", \"spam\": \"ham\" },\n  \"attachments\": [\n    { \"id\": \"msg_2Hk9…:0\", \"filename\": \"po.pdf\", \"contentType\": \"application/pdf\",\n      \"size\": 18213, \"url\": \"https://api.mailkite.dev/att/2Hk9…/0?exp=…&sig=…\" }\n  ]\n}\n```\n\n`From:`\n\nis plain text and trivially forged, so an email body is untrusted input, a [prompt-injection vector](/blog/agent-inbox-security-by-design/), not instructions. The `auth`\n\nblock is how the agent decides *how much* to trust a sender before it acts, without string-parsing a header itself. It’s necessary, not sufficient; the real defense is bounding what a fooled agent can do, but starting from a normalized verdict beats starting from a regex over headers an attacker partly controls.\n\n## Two ways to run it on MailKite\n\nMailKite, which we build, gives an agent a scoped address on a domain you control (`agent@yourco.dev`\n\n), and runs the loop one of two ways. Bring your own: the inbound webhook hits your endpoint, your model runs, you reply with `mk.send()`\n\n, which is the code at the top. Or let MailKite run it: create a route whose `action`\n\nis `agent`\n\nwith an `agentPrompt`\n\n, and the model loop runs on a durable Cloudflare Queue with an `agent_runs`\n\nledger and a per-route transcript you can drill into, capped and reaped so a slow model call can’t wedge ingest. Either way there’s no IMAP on a bot, no shared personal Gmail, no OAuth token-refresh churn, and no MIME parsing. To start, DNS-verify the domain (SPF + DKIM to send, MX to receive); there’s no sandbox or approval wait, the free tier is 3,000 messages a month across inbound and outbound with no per-domain fee, and SMTP-only apps can send through the [submission edge](/docs/sending) on `:587`\n\n/`:465`\n\n. The companion repo [ demo-elastic-email-ai-agent](https://github.com/mailkite/demo-elastic-email-ai-agent) runs both loops end to end;\n\n[open it in StackBlitz](https://stackblitz.com/github/mailkite/demo-elastic-email-ai-agent?file=server.mjs)and fire a sample event in your browser.\n\n## FAQ\n\n**Can Elastic Email receive inbound email?**\nYes. Elastic Email’s Inbound Routing points your domain’s MX at `mx.inbound.elasticemail.com`\n\n, parses the message, and POSTs the fields (`from_email`\n\n, `subject`\n\n, `body_text`\n\n, `body_html`\n\n, `header_list`\n\n, `att1_content`\n\n, and more) to an HTTP URL you configure. MailKite delivers the same message as a single signed `email.received`\n\nJSON event with a normalized `auth`\n\nblock.\n\n**Does Elastic Email’s inbound payload include an SPF/DKIM/DMARC verdict?**\nNo. There’s no SPF, DKIM, DMARC, or spam field in the inbound POST. The only authentication signal is whatever an upstream relay stamped into the raw `header_list`\n\n, which you’d have to string-parse yourself. MailKite normalizes it into `auth: { spf, dkim, dmarc, spam }`\n\nat the edge.\n\n**Is Elastic Email’s inbound feature free?**\nNo. Inbound Email Processing is gated to the Pro plans (Email API Pro is $49/mo at the time of writing), not the free or Starter tiers, and inbound messages draw from your sending allowance. MailKite’s free tier of 3,000 messages/month covers inbound and outbound with no per-domain fee. Confirm current numbers on each pricing page before you commit.\n\n**Is Elastic Email good for AI agents?**\nFor a send-heavy agent where price is the deciding factor, yes: it’s one of the cheapest senders around and speaks both API and SMTP. For an agent whose core job is receive→decide→reply safely, you’ll rebuild the trust verdict, the webhook authentication, the reply threading, and the loop. MailKite ships those.\n\n**Can I keep sending on Elastic Email and run the agent on MailKite?**\nYes. They’re not exclusive. Keep bulk or transactional sending on Elastic Email for the price, and point the agent’s address (a subdomain or a separate domain) at MailKite so it receives signed JSON with an `auth`\n\nblock and replies over the Send API.\n\nIf your agent is fishing an SPF result out of a raw header block before it dares trust an email, that’s the seam. Clone the [demo repo](https://github.com/mailkite/demo-elastic-email-ai-agent) (or [run it in your browser](https://stackblitz.com/github/mailkite/demo-elastic-email-ai-agent?file=server.mjs)), then [point a domain at MailKite](/docs/quickstart) and your agent’s next inbound email arrives parsed, authenticated, and ready to answer.\n\n*Related: the pillar on giving your agent its own inbox, and agent inbox security by design.*", "url": "https://wpnews.pro/news/the-elastic-email-alternative-for-ai-agents", "canonical_source": "https://mailkite.dev/blog/elastic-email-for-ai-agents/", "published_at": "2026-07-04 00:00:00+00:00", "updated_at": "2026-07-04 01:30:18.172988+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools"], "entities": ["Elastic Email", "MailKite", "Node.js", "Express"], "alternates": {"html": "https://wpnews.pro/news/the-elastic-email-alternative-for-ai-agents", "markdown": "https://wpnews.pro/news/the-elastic-email-alternative-for-ai-agents.md", "text": "https://wpnews.pro/news/the-elastic-email-alternative-for-ai-agents.txt", "jsonld": "https://wpnews.pro/news/the-elastic-email-alternative-for-ai-agents.jsonld"}}