MailKite
Start free
All posts
Gabe 16 min read

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.

Resend vs MailKite
Resend vs MailKite — the same job (an inbox for your AI agent), two approaches.

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.

Resend sender Resend inboundreceives + stores webhookmetadata only GET body2nd API call your agent The body and attachments arrive on the second call. The SPF/DKIM/DMARC verdict isn't in the event either, so the agent derives sender trust itself. MailKite sender MX edgeparse + auth JSON webhookbody + auth inline your agent One signed event carries the parsed body, a resolved threadId, and an SPF/DKIM/DMARC verdict.
Reading one inbound email as an agent: a webhook plus a fetch on Resend, one pushed event on MailKite. Same input, one fewer round trip and a trust verdict already resolved.

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. It runs as pasted on Node 18+ (npm install mailkite express), and the full version lives in a runnable demo repo:

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. 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:

  • Send DX and React Email. `resend.emails.send({ from, to, subject, html })` is about as clean as it gets, and Resend builds and maintains React Email (roughly two million weekly npm downloads), so an agent can render templated HTML from components instead of string-concatenating markup. The `react` prop is Node-only, but it's a real edge for send-heavy agents.
  • Instant production, no sandbox. Resend's docs are blunt about it: free accounts have full production access immediately, no sandbox, no approval, no waiting period. The only gate is DNS domain verification. MailKite works the same way, and after SES and SendGrid this is the correct default.
  • An official MCP server. Resend shipped a hosted MCP server (April 2026) exposing send, batch, contacts, domains, and read access to received emails as tools. If you want an agent that manages a Resend account, it's first-class. (It gives the agent access to an inbox; it doesn't provision an isolated inbox per agent.)
  • Svix-signed webhooks with idempotency. Webhooks are signed via Svix (`svix-id`, `svix-timestamp`, `svix-signature`), verified with `resend.webhooks.verify()`, and `svix-id` doubles as a de-dup key. Solid, standard, correct.

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:

// 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. Stack the inbound steps up and the assembly is visible:

email.received webhookmetadata only: from, subject receiving.get(email_id)2nd round trip to fetch the body derive SPF/DKIM trustnot in the payload run your modelthe actual agent turn resend.emails.send(...)reply On MailKite the top three boxesare one pushed event with anauth verdict. Your loop is think + reply.
The Resend inbound loop for an agent, stage by stage. MailKite collapses the fetch and the trust-derivation into the event that already woke your handler.

The comparison, no adjective inflation

ResendMailKite
Inbound emailYes, shipped Nov 2025Yes
Body in the webhookMetadata only; fetch via APIParsed text + html in the event
SPF/DKIM/DMARC in payloadNot in the inbound eventauth block inline
Round trips to read one emailWebhook + a GETOne pushed event
Reply threadingSend APIinReplyTo, returns { id, status }
Sandbox / approvalNone, instant productionNone, DNS-verify then send
Domains on free tier1No per-domain fee
Free tier3,000/mo, 100/day cap3,000/mo (in + out)
Run the agent loop for youYou host it (MCP for tools)Route action: 'agent' on a queue
React templatingReact 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=…" }
  ]
}

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. 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 wins when the agent’s brain is your code; the built-in route wins when the job is “read this inbound mail, answer or escalate” and you’d rather not run the infrastructure.

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.

Discuss this post: Hacker News Share on X

Related posts