The Resend alternative for AI agents
Resend has first-class sending DX and, since November 2025, real inbound email. But its inbound webhook is metadata only: an agent fetches the body with a second API call and derives SPF/DKIM trust itself. MailKite (which we build) pushes the parsed body and an auth verdict in one event, so an agent's receive→reply loop is a single round trip. For developers giving an autonomous agent its own inbox.
Concretely: on Resend, email.received fires with the sender, subject, and attachment stubs, and then you call resend.emails.receiving.get(email_id) to pull the actual text and html. Two hops before your model reads a word, and the SPF/DKIM/DMARC result an agent needs to decide whether to trust the message isn’t in the event at all. For a human-facing app that’s a shrug. For an autonomous agent running a receive→think→reply loop, every extra hop and every “derive it yourself” is a place the loop can stall or misjudge a spoofed sender. Here’s that gap in one picture, then the whole MailKite side in about 25 lines.
Everything below is a repo you can run while you read. Clone demo-resend-ai-agent and npm start, or open it in StackBlitz (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 Resend inbound path next to it in resend-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 bring-your-own-agent loop, whole. Email in, verify the signature, hand the parsed 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):
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 and the auth verdict are already in the event — no second fetch
const answer = await runAgent({
task: event.text, // parsed body, right here
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, it thinks, it answers, with the body and the SPF/DKIM/DMARC result both in the event that woke it up. 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. mk.send() returns { id, status } so you can log the outbound message, and the identical handler shape exists for Python, Ruby, Go, PHP, and Java; see the receiving docs and sending docs. Open it in StackBlitz to run real Node in a browser tab.
Where Resend wins for agents, honestly
Resend has the best sending developer experience in this category, and I’m not going to pretend otherwise. If your agent’s job is mostly to send, Resend is a genuinely great pick:
This post isn’t “Resend is bad.” It’s “Resend was built send-first, and its inbound half asks the agent builder to do the assembly.”
What Resend asks of an agent builder
Resend’s inbound is real and shipped, but the shape matters for an agent. The webhook is metadata only. Resend’s own docs say it plainly: “Webhooks do not include the email body, headers, or attachments, only their metadata.” So the receive step is a verify, then a fetch, then your own trust check, in Resend’s idiom — it’s resend-contrast/handler.mjs in the same repo, sitting right next to the MailKite server.mjs above so you can put them side by side:
// Resend inbound: verify the Svix signature, then FETCH the body (it isn't in the webhook)
import { Resend } from "resend";
import { Webhook } from "svix"; // Resend signs inbound webhooks with Svix
const resend = new Resend(process.env.RESEND_API_KEY);
const wh = new Webhook(process.env.RESEND_WEBHOOK_SECRET);
app.post("/hooks/inbound", async (req, res) => {
const evt = wh.verify(req.rawBody, {
"svix-id": req.headers["svix-id"],
"svix-timestamp": req.headers["svix-timestamp"],
"svix-signature": req.headers["svix-signature"],
});
res.sendStatus(200);
if (evt.type !== "email.received") return;
// the event carries metadata: from, to, subject, attachment stubs — no body.
// pull the stored message to get text/html:
const { data: mail } = await resend.emails.receiving.get(evt.data.email_id);
// and there's no SPF/DKIM/DMARC verdict in the payload — you derive trust yourself
await runAgent({ task: mail.text, from: evt.data.from });
});
Nothing here is hard. But notice what the agent path accumulates: a second network call before the model sees the body, an attachment fetch after that if the message has one, and a trust decision you make from raw headers because the event doesn’t hand you an SPF/DKIM/DMARC result. The repo makes both costs concrete — npm test runs this handler with a spy standing in for the fetch and asserts the receive step takes two network hops before the model reads a word, and that the inbound event carries no auth field at all, so the trust check is yours to assemble. Stack the inbound steps up and the assembly is visible:
The comparison, no adjective inflation
| Resend | MailKite | |
|---|---|---|
| Inbound email | Yes, shipped Nov 2025 | Yes |
| Body in the webhook | Metadata only; fetch via API | Parsed text + html in the event |
| SPF/DKIM/DMARC in payload | Not in the inbound event | auth block inline |
| Round trips to read one email | Webhook + a GET | One pushed event |
| Reply threading | Send API | inReplyTo, returns { id, status } |
| Sandbox / approval | None, instant production | None, DNS-verify then send |
| Domains on free tier | 1 | No per-domain fee |
| Free tier | 3,000/mo, 100/day cap | 3,000/mo (in + out) |
| Run the agent loop for you | You host it (MCP for tools) | Route action: 'agent' on a queue |
| React templating | React Email (they maintain it) | Bring your own / templates |
The through-line: Resend wins send-side DX, React Email, and instant production, and if you’re sending-first it’s a great tool. MailKite wins the receive side an agent leans on: the parsed body and a trust verdict arrive in one event, so the loop is a single round trip. Two of those rows (a fleet of agents each on its own domain hits Resend’s one-domain free tier; MailKite has no per-domain fee) also matter once you run more than one agent.
What actually hits your agent’s webhook
The same inbound email, delivered parsed, with the trust result already resolved. No second fetch, no header parsing:
{
"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": "<p>Looks good — approved!</p>",
"threadId": "<a1b2c3@mail.example.com>",
"auth": { "spf": "pass", "dkim": "pass", "dmarc": "pass", "spam": "ham" },
"attachments": [
{ "id": "msg_2Hk9…:0", "filename": "po.pdf", "contentType": "application/pdf",
"size": 18213, "url": "https://api.mailkite.dev/att/2Hk9…/0?exp=…&sig=…" }
]
}
Don’t take the shape on faith — fire one and watch it come back, text body and auth block and all. Spoof the SPF/DKIM to fail and see the verdict flip; that’s the field the Resend path makes you derive from raw headers on a second fetch:
That auth block is load-bearing for an agent. From: is plain text and trivially spoofable, so the email body is untrusted input, never instructions; the moment your agent follows what a message says, that body is a prompt-injection vector. Checking auth before you weight a sender’s instructions is necessary but not sufficient. The real fix is architectural, and it’s the whole point of agent inbox security by design: read that before either loop acts on anything that matters.
One more option: let MailKite run the agent
Both paths above have you hosting the model loop. On MailKite you can also make the route itself the agent — that’s managed-route.mjs in the repo, which dry-runs the route creation unless you set a key. A route with action: "agent" carries an agentPrompt, and MailKite, which we build, runs the model loop for you on a durable Cloudflare Queue with its own execution budget, a per-run tool-round cap, a five-minute reaper, and a full transcript you can drill into per route:
await mk.createRoute({
match: "support@myapp.ai",
action: "agent",
agentPrompt: "Answer billing questions from the docs. Escalate anything else to humans@myapp.ai.",
});
The system prompt bakes in the safety rules: the body is untrusted, at most one reply, never reply to no-reply senders. Bring-your-own (server.mjs) wins when the agent’s brain is your code; the built-in route (managed-route.mjs) wins when the job is “read this inbound mail, answer or escalate” and you’d rather not run the infrastructure. If you cloned demo-resend-ai-agent at the top, managed-route.mjs dry-runs the hosted path and resend-contrast/handler.mjs is the same job the Resend way — npm test runs both sides so you can watch the metadata-only webhook force a second fetch before you decide which shape you want.
FAQ
Does Resend support inbound email?
Yes, since November 2025. An email.received webhook fires when mail arrives at your Resend-managed or MX-configured domain. The important detail for an agent: the webhook carries metadata only (sender, subject, attachment stubs). To read the body you call the received-emails API with the email_id. MailKite delivers the parsed body in the webhook itself.
What’s the actual difference for an AI agent?
Round trips and trust. On Resend the agent gets a metadata webhook, fetches the body on a second call, and derives SPF/DKIM/DMARC itself. On MailKite the parsed text, html, a resolved threadId, and an auth verdict arrive in one signed event, so the receive step is a single hop and the trust signal is already there.
Is Resend a good choice for sending from an agent? Yes. Resend’s send API and React Email are excellent, production access is immediate with no sandbox, and there’s an official MCP server. If your agent is send-first, Resend is a strong pick. The gap this post is about is the inbound half of an autonomous loop.
Can I keep using React Email with MailKite?
Yes. React Email compiles to plain HTML that works with any provider, so render your components to a string and pass it as html to mk.send(). You’re not locked out of Resend’s authoring tools by receiving on MailKite.
Do I have to wait for approval to start on MailKite? No, same as Resend on this point. Verify your domain over DNS (SPF + DKIM to send, MX to receive) and you can send to anyone. There’s no sandbox and no per-domain fee; the free tier is 3,000 messages a month across inbound and outbound. See the quickstart.
If Resend has your agent fetching every inbound body on a second call and reconstructing SPF/DKIM by hand, there’s a shorter loop. Clone the demo repo (or run it in your browser), then point a domain at MailKite and your agent’s next inbound email arrives parsed, authenticated, and ready to answer.
Related: the pillar on giving your agent an inbox, agent inbox security by design, and the full MailKite vs Resend comparison.