Inbox agents Beta
MailKite's job is the plumbing — inbound email becomes clean JSON at your webhook, and one API sends the reply. Inbox agents are the optional AI layer on top: they read each message, decide what it means, and act — replying in-thread or calling any MailKite tool (send, open a route, look up past mail…) the way your prompt tells them to. It's the same agent that powers the assistant in your dashboard, pointed at inbound mail. Switch one on per address, or leave a domain as a plain email→webhook pipe — email stays the product; AI is a capability you opt into.
Two ways to put AI on inbound
Both are first-class. Pick per address.
| Approach | Who runs the agent | Status |
|---|---|---|
| Bring your own agent | You do — inbound webhook → your model/loop → the Send API to reply in-thread. See Agent inboxes & MCP. | Available today |
| Built-in inbox agent | MailKite does — point a route at action: "agent" with a prompt; it runs in our pipeline, replies, and can call any tool. No server, no loop to host. | Beta |
This is the inverse of Agent inboxes & MCP, which gives your agent an email identity. Inbox agents are about MailKite acting on the mail that arrives — and the two compose.
Configure one
There's no separate object to manage: an inbox agent is a
route whose action is agent, carrying
the agentPrompt that programs it. Point an address at it and you're
done — no webhook endpoint to host.
Dashboard → Routes → [ + Add route ]
Match support@myapp.ai
Receiver Agent
Prompt Answer billing & account questions for myapp.ai.
Reply in-thread. Escalate anything you can't
resolve to humans@myapp.ai.
Click [ Add route ]Set up support@myapp.ai so an AI agent answers billing and account
questions, replies in-thread, and escalates the rest to humans@myapp.ai.import { MailKite } from "mailkite";
const mk = new MailKite(process.env.MAILKITE_API_KEY);
// One route turns an address into an AI inbox agent. The prompt is the program;
// the agent can call any MailKite tool (reply, open a route, look up past mail).
await mk.createRoute({
match: "support@myapp.ai",
action: "agent",
agentPrompt:
"Answer billing and account questions for myapp.ai. Reply in-thread. " +
"Escalate anything you can't resolve to humans@myapp.ai.",
});import os
from mailkite import MailKite
mk = MailKite(os.environ["MAILKITE_API_KEY"])
mk.createRoute({
"match": "support@myapp.ai",
"action": "agent",
"agentPrompt": (
"Answer billing and account questions for myapp.ai. Reply in-thread. "
"Escalate anything you can't resolve to humans@myapp.ai."
),
})<?php
$mk = new \MailKite\Client(getenv('MAILKITE_API_KEY'));
$mk->createRoute([
'match' => 'support@myapp.ai',
'action' => 'agent',
'agentPrompt' => "Answer billing and account questions for myapp.ai. "
. "Reply in-thread. Escalate anything you can't resolve to humans@myapp.ai.",
]);MailKite mk = new MailKite(System.getenv("MAILKITE_API_KEY"));
mk.createRoute(Map.of(
"match", "support@myapp.ai",
"action", "agent",
"agentPrompt", "Answer billing and account questions for myapp.ai. "
+ "Reply in-thread. Escalate anything you can't resolve to humans@myapp.ai."
));mk := mailkite.New(os.Getenv("MAILKITE_API_KEY"))
mk.CreateRoute(map[string]any{
"match": "support@myapp.ai",
"action": "agent",
"agentPrompt": "Answer billing and account questions for myapp.ai. Reply in-thread. Escalate anything you can't resolve to humans@myapp.ai.",
})require "mailkite"
mk = Mailkite::Client.new(ENV["MAILKITE_API_KEY"])
mk.createRoute(
"match" => "support@myapp.ai",
"action" => "agent",
"agentPrompt" => "Answer billing and account questions for myapp.ai. " \
"Reply in-thread. Escalate anything you can't resolve to humans@myapp.ai."
)curl https://api.mailkite.dev/api/routes \
-H "Authorization: Bearer <session-token>" \
-H "Content-Type: application/json" \
-d '{
"match": "support@myapp.ai",
"action": "agent",
"agentPrompt": "Answer billing and account questions for myapp.ai. Reply in-thread. Escalate anything you cannot resolve to humans@myapp.ai."
}'
When mail arrives at support@myapp.ai, MailKite runs the agent with
your prompt and the parsed message. It reasons, calls whatever
tools it needs, and (per the prompt) replies in-thread from
the receiving address. The run happens in the background, so inbound stays fast.
Tools the agent can call
An inbox agent isn't a one-shot prompt — it's a tool-using agent. As it reasons
over a message it can call the same operations the
MCP server and your dashboard
assistant use, all scoped to your account: send and reply
(send_email), list and read past messages, create and list routes,
add and verify domains, set webhooks. Your agentPrompt is what tells
it which to use and when — the prompt is the program.
So “reply to billing questions; for refund requests, open a route to
refunds@ and let the team know” becomes real calls to
send_email and create_route — no glue code.
Under the hood the agent drives MailKite through the same REST API the
SDK wraps (auth, validation, and limits
reused), so it can never do anything your own API calls couldn't.
Guardrails. The email body is treated as untrusted input (instructions inside it are data, not commands); the agent won't reply to no-reply/automated senders, sends at most one reply per message, and escalates when unsure. It only ever acts on your account.
The dashboard assistant
The same agent runs as a chat in your dashboard (the Assistant tab). Ask it to add a domain, show DNS, wire a webhook, set up an inbox-agent route, or find a message — it calls the same tools and streams its work. It's the fastest way to try a prompt before you put it on a route.
Planned: structured spam, tags & escalation
Today everything is expressed through the free-text agentPrompt plus
tools, which already covers triage, replies, and escalation. First-class,
typed configuration is the next step:
| Field | Values | Purpose |
|---|---|---|
agentPrompt | string · available | The agent's instructions — its job, tone, and rules. The one field you set today. |
reply.mode | auto · draft · off · planned | Send the reply, hold it for human approval, or never reply. |
spam.action | quarantine · tag · planned | Drop junk before the model runs, or just label it. |
tags | string[] · planned | A first-class label vocabulary, emitted on the webhook. |
escalation | { to, when } · planned | Typed hand-off target and trigger (today: describe it in the prompt). |
When that lands, an agent route can also forward the inbound event to your own
endpoint, enriched with the spam verdict, tags, and what the agent did — so your
code stays in the loop. The planned shape:
email.received (planned fields) {
"type": "email.received",
"from": { "address": "ada@example.com" },
"to": [{ "address": "support@myapp.ai" }],
"subject": "Can't update my card",
"text": "Hi — my payment keeps failing…",
// ↓ planned: added when first-class spam/tagging runs in front of the agent
"spam": { "verdict": "ham", "score": 0.03 },
"tags": ["billing"],
"agent": { "id": "agt_…", "action": "replied", "replyId": "msg_…" }
}
Bring your own instead
Already have an agent? Skip the built-in one: route the address to a
webhook and run your own loop — read the parsed
JSON, reason over it, and reply with the SDK's
send + inReplyTo. The full pattern, in seven
languages, is in Agent
inboxes & MCP.
What it costs
The built-in inbox agent runs on Claude, and pricing is simple and
pay-as-you-go: $0.10 per AI action — one agent run over one
inbound email — the same on every paid plan, with no bundles or token math.
Prefer to run on your own key? Add your Anthropic API key in the dashboard and
AI is free — we never bill it. Bringing your own agent over a
plain webhook is free too; you just pay your own model provider. See
Pricing for the full breakdown.
Next: Agent inboxes & MCP for the
bring-your-own pattern, or Inbound webhooks for
the raw event your agent reasons over.