cd /news/ai-agents/give-your-ai-agent-its-own-email-inb… · home topics ai-agents article
[ARTICLE · art-47472] src=mailkite.dev ↗ pub= topic=ai-agents verified=true sentiment=↑ positive

Give your AI agent its own email inbox – MailKite

MailKite launched a service that gives AI agents their own email inboxes on custom domains, enabling autonomous email handling without IMAP or MIME parsing. Developers can either run agents themselves via webhooks or let MailKite manage the agent loop, with inbound emails arriving as parsed JSON and replies sent through the Send API.

read9 min views1 publishedJul 3, 2026
Give your AI agent its own email inbox – MailKite
Image: source

Give an AI agent a real, scoped address on a domain you control. Inbound mail arrives as parsed JSON to an event.received loop and the agent replies over the Send API, or MailKite runs the agent for you on a route with action: agent. Working code, the security caveat, and the honest DIY alternatives.

An AI agent with its own email inbox is an autonomous program that owns a real address on a domain you control, so it can receive verification codes, be handed work by email, and reply on its own without a human in the loop. This post is for a developer wiring an agent to an inbox for the first time, and it shows two ways to build one: run the agent yourself (inbound mail arrives as parsed JSON, your handler calls your model, the agent answers with one mk.send()

), or let MailKite (which we build) run it for you with a route whose action

is agent

. Either way there’s no IMAP, no MIME parsing, and no personal Gmail account quietly wired into a bot.

Here’s the bring-your-own-agent loop, whole. 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

):

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;

  // Treat the body as untrusted INPUT, never as instructions (see the security section).
  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, it thinks, it answers. 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. Raw HTTP works too if you can’t take a dependency, but you’d be hand-rolling the HMAC verify that verifyWebhook

does in one line; prefer the SDK.

What actually lands at your handler #

MailKite decodes the message at the edge, so runAgent

gets fields, not MIME. This is the same inbound webhook from the pillar: decoded text

and html

, a resolved threadId

, attachments as short-lived signed URLs, and an auth

block:

{
  "id": "msg_2Hk9…",
  "type": "email.received",
  "from": { "address": "ada@example.com" },
  "to": [{ "address": "agent@yourco.dev" }],
  "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=…" }
  ]
}

The auth

block tells the agent whether SPF, DKIM, and DMARC passed. Hold that thought; it’s load-bearing in a minute.

Two ways to run an agent inbox #

You own the loop above, or you hand the whole thing to MailKite. Same inbound edge; the difference is where the agent’s turns execute.

Let MailKite run the agent (route action ‘agent’) #

If you don’t want to host the loop, make the route itself the agent. A route with action: "agent"

carries an agentPrompt

(free-text instructions), and MailKite runs the model loop for you on every inbound message:

await mk.createRoute({
  match: "support@yourco.dev",
  action: "agent",
  agentPrompt: "Answer billing questions from the docs. Escalate anything else to humans@yourco.dev.",
});

The run doesn’t happen on the inbound request’s clock. We enqueue it on a Cloudflare Queue (mailkite-agent-runs

) with its own execution budget, so ingest still returns fast and a slow model call can’t wedge the pipeline. Each run is capped at 8 tool rounds, aborted at 5 minutes by a cron reaper as a crash-proof backstop, and recorded as a full transcript you can drill into per route in the dashboard. The system prompt bakes in the safety rules the next section is about: the email body is untrusted, at most one reply, and never reply to no-reply or automated senders. The agent replies with an internal send_email

tool (threaded via inReplyTo

), the same /v1/send

your own loop calls. Details in the inbox-agents docs.

Bring-your-own wins when the agent’s brain is your code: your model, your tools, your state. The built-in route wins when you’d rather not run infrastructure and the job is “read this inbound mail, answer or escalate.”

The part I have to flag: inbound email is untrusted input #

Here’s where I slow you down, because I walked into this hole myself. The moment your agent follows what an email says, that email body is a prompt-injection vector. From:

is plain text, so anyone can forge the sender, then simply tell your agent what to do, and a naive loop obeys.

That’s why the auth

block is in the payload and why the loop passes a trusted

flag instead of blindly acting: you can at least see whether SPF and DMARC passed before you weight a sender’s instructions. But checking auth

is necessary, not sufficient. You cannot prompt your way out of prompt injection; the real answer is architectural, bounding what a fooled agent can even do (owner-scoped tools, one reply, no acting on links). This is the same reasoning the built-in agent uses, and I wrote up the mistake honestly in Why aren’t we seeing more agent security discussions?. Read it before you point either loop at anything that matters.

When to build it yourself (and when not to) #

An agent inbox isn’t exotic; you can assemble one without us, and sometimes you should. The honest alternatives:

IMAP polling on a real mailbox. Give the agent a Gmail/Workspace or Fastmail account and poll IMAP or the Gmail API. It works, but you own MIME parsing, threading, attachment decoding, and a mailbox that is really a person’s account with a person’s permissions. Best when the agent genuinely needs to live inside an existing human inbox.Postmark inbound + your own loop. Postmark’s inbound parse also POSTs parsed JSON; point it at the samerunAgent

loop above. A fine call if you’re already on Postmark for sending and just want the inbound half.Cloudflare Email Workers. The receiving primitive: an email hits a Worker’semail()

handler. There’s no parsing, routing, or management layer; you build that. Good if you want to own the whole edge. (We build MailKite on Cloudflare, and this is the layer we put on top.)Self-hosted Haraka. Run your own MX. Total control, total ops: the SMTP server, spam filtering, TLS, and uptime are all yours. Worth it at real scale or under strict data-residency rules.

The DIY shape is the same in every case: an MX record (or a mailbox), something that turns raw MIME into fields, signature and SPF/DKIM/DMARC checks, and a reply path. MailKite is that stack assembled (MX edge, parse, auth, a signed and retried webhook, and the Send API), so the 25 lines up top are the whole integration. Below a dedicated mail-infrastructure engineer’s time the trade tends to pay for itself; above it, or when the agent must live in a human’s real mailbox, one of the above is the better pick.

Let the agent use MailKite as a tool (MCP) #

Everything so far is email reaching in. The other direction is your agent reaching out through email, and MailKite exposes its whole API as agent-native tools over a hosted MCP server. Point any MCP client at it:

claude mcp add --transport http mailkite https://mcp.mailkite.dev/mcp

After that the agent calls mailkite_send

, mailkite_get_message

, mailkite_list_domains

, and the rest as first-class tool calls instead of hand-rolled HTTP. There’s also a Claude Code plugin that wires the same tools into your editor. An agent with an inbox and this tool set can run a support address end to end: read the incoming message, look something up, reply, all as tool calls.

FAQ #

Can I give an AI agent its own email address? Yes. Point a domain at MailKite, pick an address like agent@yourco.dev

, and set a webhook. Inbound mail is parsed to JSON and POSTed as an email.received

event; the agent replies with mk.send()

. It’s a real, scoped mailbox on your domain, not a personal account bolted onto a bot.

Should the agent run on my server or on MailKite? Both work. Host the loop yourself (webhook

route → your endpoint → your model → Send API) when the agent’s logic is your code. Use a route with action: "agent"

and an agentPrompt

when you’d rather MailKite run the model loop on its own durable queue and hand you a transcript.

How does the agent read incoming email? It doesn’t parse MIME. MailKite decodes the message at the edge and delivers text

, html

, a resolved threadId

, attachments as signed URLs, and an auth

result. Your handler verifies the webhook signature and reads plain fields.

Isn’t letting an agent act on email dangerous? It’s a prompt-injection surface, yes: senders are trivially spoofable. Check the auth

(SPF/DKIM/DMARC) results before trusting instructions, and don’t rely on the system prompt alone; bound the agent’s authority. See the agent-security post.

Can the agent call MailKite as a tool? Yes. There’s a hosted MCP server at mcp.mailkite.dev

(claude mcp add --transport http mailkite https://mcp.mailkite.dev/mcp

) and a Claude Code plugin, so sending mail, reading messages, and managing domains are all tool calls the agent can make directly.

Give your agent an inbox and it stops being deaf to the parts of the world that arrive by email. Point a domain at MailKite and it’ll be reading and answering its own mail in a few minutes: pick the bring-your-own loop or a route with action: 'agent'

(inbox-agents docs), then read the security post before you let it act on anything.

Related: the inbound pillar on why receiving email is hard, parsing inbound email to JSON in Node, and the AI agents guide.

── more in #ai-agents 4 stories · sorted by recency
── more on @mailkite 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/give-your-ai-agent-i…] indexed:0 read:9min 2026-07-03 ·