{"slug": "the-mailjet-alternative-for-ai-agents", "title": "The Mailjet alternative for AI agents", "summary": "MailKite launches an email API designed for AI agents, offering parsed JSON with SPF/DKIM/DMARC verification and a receive-reply loop, contrasting with Mailjet's Parse API which requires manual route setup and lacks a trust verdict. The service provides a webhook-based agent loop that verifies email signatures before processing, enabling autonomous email agents to act on trusted inbound mail.", "body_md": "# The Mailjet alternative for AI agents\n\nMailjet (a Sinch company) can receive inbound mail through its Parse API, but you create the parseroute by hand, map its own field names, and there's no single trust verdict for the body. MailKite (which we build) gives an agent a real inbox: parsed JSON with an spf/dkim/dmarc block and a receive→reply loop. For developers wiring an autonomous email agent.\n\nStart from what an autonomous agent actually needs: its own scoped address, mail arriving as data it can read, and a signal telling it whether that mail can be trusted before it acts. Mailjet was built to *send* well, and its inbound path, the Parse API, is a capable bolt-on rather than an agent surface. This post is the honest version of that gap: where Mailjet wins, the exact inbound shape it hands you, and the roughly 20 lines that are the entire MailKite side.\n\nEverything below is a repo you can run while you read. Clone [ demo-mailjet-ai-agent](https://github.com/mailkite/demo-mailjet-ai-agent) and\n\n`npm start`\n\n, or [open it in StackBlitz](https://stackblitz.com/github/mailkite/demo-mailjet-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\n\n`npm start`\n\nboots the server and self-fires a correctly *signed*\n\n`email.received`\n\nevent 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`\n\ndry-runs the hosted path too). It also ships the Mailjet path next to it in `mailjet-contrast/`\n\n, 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.\n\nHere’s the whole MailKite loop: email in, verify the signature, hand the body to your model, reply through the same client. This is the heart of `server.mjs`\n\nin 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`\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  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. Trust comes from auth, not From.\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 a fully autonomous email agent: it hears, thinks, and answers, with the trust decision made from the `auth`\n\nblock instead of the forgeable `From`\n\n. `npm start`\n\nboots this 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 verdict straight off `event.auth`\n\n, 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).\n\n## Where Mailjet wins for agents, honestly\n\nMailjet is a real ESP with a real inbound story, and a few things genuinely count in its favor.\n\n## What Mailjet asks of an agent builder\n\nInbound on Mailjet is the Parse API, and the setup is deliberate: you create a *parseroute* with an API call. One `POST /v3/REST/parseroute`\n\nwith a webhook `Url`\n\nreturns an address on `@parse-in1.mailjet.com`\n\n(or you can bind your own domain by pointing an MX record at `parse.mailjet.com`\n\n). Every inbound email to that address is then POSTed to your `Url`\n\nas `application/json`\n\n.\n\nThe payload is Mailjet’s own field vocabulary, not a normalized one. You get `Sender`\n\n, `Recipient`\n\n, `From`\n\n, `To`\n\n, `Cc`\n\n, `Subject`\n\n, `Date`\n\n, a `Headers`\n\nobject, a `Parts`\n\narray, `Text-part`\n\nand `Html-part`\n\nfor the body, `SpamAssassinScore`\n\n, and attachments as base64 in `AttachmentN`\n\nfields. What you don’t get is a single trust verdict. There’s no `spf`\n\n, `dkim`\n\n, or `dmarc`\n\nfield. The `DKIM-Signature`\n\nsits raw inside `Headers`\n\n, and if the agent’s decision to act depends on whether the sender is really the sender, that verification is your code. Here’s the honest inbound handler — it’s `mailjet-contrast/handler.mjs`\n\nin the repo, sitting right next to the MailKite `server.mjs`\n\nabove so you can put them side by side:\n\n```\n// Mailjet Parse API inbound: you create the parseroute, then map + verify yourself.\nimport express from \"express\";\nconst app = express();\n\napp.use(\"/mailjet/parse\", express.json({ limit: \"30mb\" }));\n\napp.post(\"/mailjet/parse\", (req, res) => {\n  const m = req.body; // Mailjet's field names, not yours\n\n  const email = {\n    from: m.From,                 // \"Ada <ada@example.com>\" — parse the address out\n    to: m.Recipient,\n    subject: m.Subject,\n    text: m[\"Text-part\"],         // hyphenated keys\n    html: m[\"Html-part\"],\n    spamScore: m.SpamAssassinScore,\n  };\n\n  // No spf/dkim/dmarc verdict in the payload. If trust matters, do it yourself:\n  const dkimHeader = m.Headers?.[\"DKIM-Signature\"]; // raw signature string\n  const trusted = verifyDkim(dkimHeader, m.Headers); // you write/import this\n\n  // Attachments arrive base64 under Attachment1, Attachment2, … in Parts.\n  // Decode and map before your agent ever sees a file.\n\n  res.sendStatus(200);\n  runAgent({ task: email.text, from: email.from, trusted });\n});\n\napp.listen(3000);\n```\n\nNone of this is exotic. But between “an email arrived” and “the agent can safely decide what to do about it” sits a parseroute you provision by API, a field-name mapping layer, and a DKIM/SPF verification step the payload doesn’t do for you. Mailjet also has no built-in agent runtime: it’s a send-first ESP, so the model loop, the reply threading, and the guardrails are all yours to host.\n\nThe repo makes that fragility concrete. `npm test`\n\nfeeds the header-grep Outlook’s `Received-SPF: Pass (…)`\n\n— a genuine SPF pass with no `spf=pass`\n\ntoken in `Authentication-Results`\n\n— and watches it return `false`\n\n, scoring a legitimately authenticated sender as untrusted. A second test makes the other half explicit: the one number Mailjet *does* hand you, `SpamAssassinScore`\n\n, is spam likelihood, not an authentication pass. Both are shaky ground for a *trust* decision, which is the check that matters most for an autonomous agent.\n\n## The comparison, agent-first\n\n| Mailjet Parse API | MailKite | |\n|---|---|---|\n| Give an agent an inbox | Create a parseroute via API, get a parse-in1 address | Point a domain, pick an address, set a webhook |\n| Inbound delivery | JSON with Mailjet’s field names | Parsed JSON, stable `email.received` shape |\n| Auth verdict in payload | SpamAssassin score only; no SPF/DKIM/DMARC verdict | `auth: { spf, dkim, dmarc, spam }` |\n| Trust decision | Verify DKIM/SPF yourself from raw Headers | Read the `auth` block |\n| Reply threading | You build `In-Reply-To` and reply-from logic | `inReplyTo` + reply from `event.to[0]` |\n| Agent runtime | None; host the loop yourself | Bring your own, or a route with `action: 'agent'` |\n| Data residency | EU by default (Frankfurt, Belgium) | Aligned SPF/DKIM/DMARC on send |\n| Free tier | 6,000/mo, 200/day cap | 3,000 messages/mo, in + out |\n\nThe through-line: Mailjet can receive, and its EU posture and free tier are real advantages. MailKite’s edge is the shape it hands an agent, a stable parsed event with a trust verdict already computed, and a place to run the loop.\n\n## What actually hits your agent’s webhook\n\nMailKite decodes the message at the edge, so your handler gets fields and a verdict, not MIME and 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\nDon’t take the shape on faith — fire one and watch it come back, `auth`\n\nblock and all. Spoof the SPF/DKIM to `fail`\n\nand see the verdict flip; that’s the field the Mailjet Parse path makes you reconstruct from raw headers:\n\nThe `auth`\n\nblock is the load-bearing difference for an agent. `From:`\n\nis plain text and trivially forged, so a naive loop that reads the body as instructions is a prompt-injection target. The verdict lets you gate on whether SPF, DKIM, and DMARC passed before you weight a sender at all. Checking it is necessary, not sufficient; the real defense is architectural, and it’s the whole subject of [the agent-security post](/blog/agent-inbox-security-by-design/).\n\n`auth`\n\nblock.MailKite, which we build, gives an agent a real inbox as its native shape: a scoped address on a domain you control, inbound decoded to the JSON above, and two ways to run the loop — both in the repo. Bring your own (`server.mjs`\n\n, the handler up top) when the brain is your code, or let MailKite run it (`managed-route.mjs`\n\n): set a route whose `action`\n\nis `agent`\n\nwith an `agentPrompt`\n\nand the model loop runs on a durable queue, capped, reaped on timeout, and recorded as a transcript you can drill into. Same parsed edge, same trust verdict, either way. If you cloned [ demo-mailjet-ai-agent](https://github.com/mailkite/demo-mailjet-ai-agent),\n\n`managed-route.mjs`\n\ndry-runs the route creation and `mailjet-contrast/handler.mjs`\n\nis the same job the Mailjet way — `npm test`\n\nruns both sides so you can watch the header-grep miss a real pass before you decide which shape you want.## FAQ\n\n**Can Mailjet receive inbound email for an agent?**\nYes. Mailjet’s Parse API receives inbound: you create a parseroute (`POST /v3/REST/parseroute`\n\n) pointing an address to your webhook, and Mailjet POSTs each inbound email as JSON with fields like `From`\n\n, `Subject`\n\n, `Text-part`\n\n, `Html-part`\n\n, and `SpamAssassinScore`\n\n. The agent gap is that it uses Mailjet’s field naming and doesn’t include a normalized SPF/DKIM/DMARC verdict, so trust decisions are your code.\n\n**Does the Mailjet Parse API include SPF, DKIM, or DMARC results?**\nNot as a normalized verdict. The payload includes a SpamAssassin score, and the raw `DKIM-Signature`\n\nlives inside the `Headers`\n\nobject, but there’s no `spf`\n\n/`dkim`\n\n/`dmarc`\n\nfield. If your agent’s action depends on sender authenticity, you verify it yourself. MailKite ships that verdict in an `auth`\n\nblock on every inbound event.\n\n**Is Mailjet a good fit if I need EU data residency?**\nIt’s one of its strengths. Mailjet was founded in Paris, stores data in the EU (Frankfurt and Belgium), holds ISO 27001, and was an early GDPR-certified ESP, now under Sinch. If EU residency is a hard requirement and you’re comfortable assembling the agent inbound layer, that’s a genuine reason to pick it.\n\n**Does Mailjet have a built-in agent or inbox-agent runtime?**\nNo. Mailjet is a send-first transactional and marketing ESP; there’s no built-in model loop for inbound. You host the receive→think→reply loop yourself. MailKite offers both a bring-your-own webhook loop and a managed `action: 'agent'`\n\nroute that runs the loop for you.\n\n**How do I move an agent from Mailjet Parse to MailKite?**\nPoint a domain at MailKite (SPF + DKIM to send, MX to receive), pick an address like `agent@yourco.dev`\n\n, and set the webhook to the loop at the top of this post. Inbound arrives as the `email.received`\n\nJSON with an `auth`\n\nblock, and replies thread with `inReplyTo`\n\n. No sandbox or approval wait; see the [quickstart](/docs/quickstart).\n\nMailjet can receive, and its EU footing is real. But an autonomous agent wants its inbound already parsed into a stable shape with a trust verdict attached, and a place to run the loop. Clone the [demo repo](https://github.com/mailkite/demo-mailjet-ai-agent) (or [run it in your browser](https://stackblitz.com/github/mailkite/demo-mailjet-ai-agent?file=server.mjs)), then [point a domain at MailKite](/docs/quickstart) and your agent reads and answers its own mail.\n\n*Related: the pillar on giving your agent an inbox and agent inbox security by design.*", "url": "https://wpnews.pro/news/the-mailjet-alternative-for-ai-agents", "canonical_source": "https://mailkite.dev/blog/mailjet-for-ai-agents/", "published_at": "2026-07-04 00:00:00+00:00", "updated_at": "2026-07-07 01:43:03.299099+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "ai-infrastructure"], "entities": ["MailKite", "Mailjet", "Sinch", "Parse API", "SPF", "DKIM", "DMARC", "StackBlitz"], "alternates": {"html": "https://wpnews.pro/news/the-mailjet-alternative-for-ai-agents", "markdown": "https://wpnews.pro/news/the-mailjet-alternative-for-ai-agents.md", "text": "https://wpnews.pro/news/the-mailjet-alternative-for-ai-agents.txt", "jsonld": "https://wpnews.pro/news/the-mailjet-alternative-for-ai-agents.jsonld"}}