The MailerSend alternative for AI agents MailKite launches an alternative to MailerSend for AI agents, offering normalized authentication verdicts and a built-in receive-to-reply loop on custom domains. The service provides developers with a simpler way to give autonomous agents their own email inboxes, handling signature verification and trust decisions automatically. The MailerSend alternative for AI agents MailerSend by MailerLite is a developer-friendly transactional service, and its Inbound Routing genuinely receives mail as parsed JSON. For an autonomous agent the gaps are narrower than usual: you compose the trust decision yourself from spf check and dkim check no DMARC, no spam verdict , and there's no built-in agent loop. MailKite, which we build, hands you a normalized auth verdict and a receive→reply loop on a domain you own. For developers giving an agent its own inbox. Most “does it receive?” comparisons are lopsided, but this one isn’t, so the diagram earns its place up front: MailerSend really does deliver inbound email as parsed JSON, and the difference for an agent is subtler than “webhook vs no webhook.” It’s what arrives in that JSON, and what your loop has to decide before it acts. The rest of the post is the honest version of this picture, plus the ~25 lines that are the whole MailKite side from a runnable demo repo https://github.com/mailkite/demo-mailersend-ai-agent . Everything below is a repo you can run while you read. Clone demo-mailersend-ai-agent https://github.com/mailkite/demo-mailersend-ai-agent and npm start , or open it in StackBlitz https://stackblitz.com/github/mailkite/demo-mailersend-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 MailerSend path next to it in , so every contrast here is something you can diff and test, not take on faith. A domain comes in only when you want https://github.com/mailkite/demo-mailersend-ai-agent/blob/main/mailersend-contrast/handler.mjs mailersend-contrast/ real inbound email to arrive. Here’s the whole MailKite side of an agent inbox: 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 — wrapped 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 ; // ack fast; run the agent out of band const event = JSON.parse req.body ; if event.type == "email.received" return; // The body is untrusted INPUT, never instructions. One block decides 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 a fully autonomous email agent: it hears, thinks, and answers, with event.auth.dmarc already resolved for the trust check. 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 identical handler shape exists for Python, Ruby, Go, PHP, and Java; see the receiving docs /docs/receiving and sending docs /docs/sending . Open the demo in StackBlitz https://stackblitz.com/github/mailkite/demo-mailersend-ai-agent?file=server.mjs to run it in your browser. Where MailerSend wins for agents, honestly I want to be fair here, because MailerSend’s inbound is not a bolt-on. It’s a real feature and it’s pleasant to use. If you’re already on MailerSend for outbound and you want an agent to read replies, the honest answer is: their inbound gets you most of the way there. This post isn’t “MailerSend can’t receive.” It’s about the two places an agent path still asks you to do the work. What MailerSend asks of an agent builder The inbound payload nests the message under data , and the two authentication signals it gives you are spf check a { code, value } object and dkim check a boolean . There is no DMARC result and no spam classification. So before your agent can weigh whether to trust an instruction, you compose the verdict yourself. Here’s the real MailerSend inbound handler mailersend-contrast/handler.mjs https://github.com/mailkite/demo-mailersend-ai-agent/blob/main/mailersend-contrast/handler.mjs in the demo : // MailerSend inbound: verify Signature header, then compose the trust decision yourself import crypto from "node:crypto"; import { MailerSend, EmailParams, Sender, Recipient } from "mailersend"; const ms = new MailerSend { apiKey: process.env.MAILERSEND API KEY } ; export async function handleInbound req, res { const raw = req.rawBody; // keep the raw bytes for HMAC const expected = crypto.createHmac "sha256", process.env.MS SIGNING SECRET .update raw, "utf8" .digest "hex" ; const got = req.headers "signature" ?? ""; if crypto.timingSafeEqual Buffer.from got, "hex" , Buffer.from expected, "hex" { return res.sendStatus 401 ; } const { data } = JSON.parse raw ; // message nested under data // spf check is {code, value} the code set isn't fully documented, so you normalize it ; // dkim check is a boolean. No DMARC alignment and no spam score are in the payload. const spfPass = data.spf check?.value === "pass"; const dkimPass = data.dkim check === true; const trusted = spfPass && dkimPass; // partial: no DMARC, no spam signal to weight const answer = await runAgent { task: data.text, from: data.from?.email, trusted } ; const reply = new EmailParams .setFrom new Sender data.recipients.rcptTo, "Agent" .setTo new Recipient data.from.email .setSubject Re: ${data.subject} .setInReplyTo data.headers "message-id" .setHtml answer.html ; await ms.email.send reply ; res.sendStatus 200 ; } Nothing here is hard. But notice what the agent is left holding: it derives trusted from two raw signals and then has to accept that DMARC alignment and a spam score simply aren’t in the payload. For a program that acts on the contents of email, that missing normalized verdict is the load-bearing gap, not the plumbing. The repo pins both costs — npm test runs composeTrust and watches dmarc and spam come back null a partial verdict you maintain , then runs the handler end to end to show the reply is a second call to the send API, threaded by hand. On MailKite that decision is one field, event.auth , which is the whole reason server.mjs https://github.com/mailkite/demo-mailersend-ai-agent/blob/main/server.mjs never composes anything. There’s a second gap, and it’s about who runs the loop. MailerSend delivers the message and stops; the model call, the retry policy, and the “did this run actually finish?” bookkeeping are your service to build and keep up. That’s a reasonable division of labor for a send-focused platform. It’s just work the agent path still owns. MailerSend even ships an official MCP server, which is a genuinely nice touch for an agent stack. It’s worth being precise about what it does: it exposes sending , domain management, and analytics as tools, not inbox reading. So an agent can send through MailerSend’s MCP, but it can’t read its own mail through it; the inbound half is still the webhook you wire up. The comparison, no adjective inflation | MailerSend | MailKite | | |---|---|---| | Inbound delivery | Parsed JSON webhook data. | Parsed JSON webhook email.received | | Auth signals | spf check + dkim check | auth: {spf, dkim, dmarc, spam} | | DMARC / spam verdict | Not in payload | Included, normalized | | Webhook signing | HMAC-SHA256 Signature header | HMAC + replay window, one-call verify | | Built-in agent loop | None webhook only | Optional route action: 'agent' | | Failed-delivery replay | Retries | Retries + one-click replay | | Free tier | 500 emails/mo, 1 domain | 3,000 msgs/mo, no per-domain fee | | Sending | REST + SMTP + templates | REST + SMTP submission edge | The through-line: MailerSend gives you a clean, signed, parsed inbound feed and leaves the trust verdict and the agent loop to you. MailKite resolves the verdict and will host the loop, on domains that don’t cost extra to add. What actually hits your agent’s webhook Here’s the MailKite email.received event. The auth block is the whole point of the second diagram: SPF, DKIM, DMARC, and a spam classification, already resolved, so the agent reads one field instead of composing a decision: { "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": "