Give your AI agent its own email inbox — MailKite Beta<br>50% off your first year — founding rate for the first 1,000 customers.<br>View pricing →
Sign in · Get started
All posts<br>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. With MailKite, mail sent to that address is parsed into clean JSON and POSTed to your agent as an email.received event, and the agent answers by calling mk.send(). No IMAP, no MIME parsing, no personal Gmail account quietly wired into a bot.
I keep watching people build genuinely capable agents that are, in one specific way, deaf. They can call APIs, run tools, reason for pages — and then they hit a wall the second the real world tries to reach them. A vendor emails a confirmation link. A customer replies to a thread the agent started. A teammate forwards something and says “deal with this.” Every one of those is email, and most agents have no way to hear it.
An inbox fixes that. Not a shared human inbox the agent screen-scrapes — its own address, on your domain, that you can reason about and revoke.
Why an agent needs a real address
Three jobs come up over and over, and all three need a genuine inbox:
Receiving verification codes. Half the useful services on the internet gate signup behind an emailed code or magic link. An agent that can sign itself up for things needs somewhere those codes can land — and reading them out of your personal inbox is exactly the coupling you don’t want.
Being tasked by email. Email is the universal work queue. agent@yourco.dev is a dead-simple interface: a human or another system emails the agent, the agent does the thing. No new UI, no bot framework, no Slack app review.
Replying autonomously. Threads have two directions. If the agent can only send cold and never answer a reply, it isn’t participating in a conversation — it’s spraying. A real inbox closes the loop.
Give it a scoped inbox on your domain
The setup is the same one from the inbound pillar: point a domain at MailKite (add the MX record, verify), pick an address for the agent, and set a webhook URL. From then on, anything sent to agent@yourco.dev arrives at your handler already parsed:
"id": "msg_2Hk9…",<br>"type": "email.received",<br>"from": { "address": "ada@example.com" },<br>"to": [{ "address": "agent@yourco.dev" }],<br>"subject": "Re: invoice #1042",<br>"text": "Looks good — approved!",<br>"html": "Looks good — approved!",<br>"threadId": "",<br>"auth": { "spf": "pass", "dkim": "pass", "dmarc": "pass", "spam": "ham" },<br>"attachments": [<br>{ "filename": "po.pdf", "contentType": "application/pdf",<br>"size": 18213, "url": "https://api.mailkite.dev/att/2Hk9…?exp=…&sig=…" }<br>The agent gets decoded text and html, a resolved threadId, attachments as short-lived signed URLs — and, crucially, an auth block telling it whether SPF, DKIM, and DMARC passed. Hold that thought; it’s load-bearing in a minute.
The loop: email in → agent → reply out
Here’s the whole thing in Node. Verify the signature, hand the message to your agent, send the reply through the same client. Nothing exotic:
import express from "express";<br>import { MailKite } from "mailkite";
const mk = new MailKite(process.env.MAILKITE_API_KEY);<br>const SECRET = process.env.MAILKITE_WEBHOOK_SECRET;
app.use("/hooks/agent", express.raw({ type: "application/json" }));
app.post("/hooks/agent", async (req, res) => {<br>const sig = req.headers["x-mailkite-signature"];<br>if (!MailKite.verifyWebhook(sig, req.body, SECRET)) {<br>return res.sendStatus(401);<br>res.sendStatus(200); // ack fast; run the agent out of band
const event = JSON.parse(req.body);<br>if (event.type !== "email.received") return;
// Treat the body as untrusted INPUT, never as instructions.<br>const answer = await runAgent({<br>task: event.text,<br>from: event.from.address,<br>trusted: event.auth.spf === "pass" && event.auth.dmarc === "pass",<br>});
await mk.send({<br>from: "agent@yourco.dev",<br>to: event.from.address,<br>subject: `Re: ${event.subject}`,<br>html: answer.html,<br>});<br>});<br>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 same handler shape exists for Python, Go, Ruby, PHP, and Java — see the receiving docs and sending docs.
The part I have to flag: inbound email is untrusted input
Here is where I have to slow you down, because I walked straight 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 — anyone can forge it. So an attacker can email your agent and simply tell it what to do, and a naive loop will obey.
This is why the auth block is in the payload and why the code above passes a trusted flag rather than blindly acting: you can at least see whether SPF and DMARC passed before you weight a sender’s instructions. But — and I mean this — checking auth is...