Email as an agent tool: MCP, the SDK, or your own webhook MailKite offers three ways to integrate email into AI agents: MCP for model-decided tool calls, SDK for deterministic code-driven sending, and webhooks for receiving emails. Developers can choose based on whether the agent or the program should control email actions, with runnable code examples provided for all three approaches. Email as an agent tool: MCP, the SDK, or your own webhook There are three ways to give an agent email, and they answer different questions. Install the MCP server and the model sends and reads mail as tool calls it decides to make. Hand it the SDK and email is deterministic code your agent runs. Point a signed webhook at it and the agent receives mail the moment it arrives. This is when to use each — with runnable code for all three, and how to mix them. For developers wiring an agent to email with MailKite. “Give the agent email” isn’t one decision — it’s two, receiving and sending, and for sending there’s a real choice between the model deciding to send a tool call and your code deciding a function call . Pick wrong and you either hard-code something the agent should choose, or hand the model a capability that should have been deterministic. Here are the three wirings and what each one is for. The fastest one to show is MCP, because it’s a single command and then email is just tools the model has: install the hosted MailKite MCP server Claude Code shown; any MCP client works claude mcp add --transport http mailkite https://mcp.mailkite.dev/mcp After that the model can send, read messages, and manage domains as tool calls it decides to make — no glue code. The runnable versions of all three wirings, including a webhook receiver and a mixed receive-by-webhook-send-by-SDK loop, are in demo-email-agent-tool https://github.com/mailkite/demo-email-agent-tool ; open it in StackBlitz https://stackblitz.com/github/mailkite/demo-email-agent-tool?file=server.mjs . Here’s each wiring and exactly when it’s the right one. 1. MCP: email as tools the model calls Use MCP when the agent should decide whether and when to touch email. You install the server once and the model gets a set of tools — send a message, list or read inbound, manage domains and routes — and calls them as part of its reasoning, the same way it calls any other tool. There’s no code path in your app that says “send now”; the model chooses. mcp.mailkite.dev with OAuth, plus there's a Claude Code plugin. 2. The SDK: email as deterministic code Use the SDK when your program decides. If the rule is “when a run finishes, email the result,” that’s not a judgment call the model should make on the fly — it’s a line of code. The SDK gives you mk.send and the full receive-verify path, in the agent’s own language: js // deterministic: your code decides to send, not the model import { MailKite } from "mailkite"; const mk = new MailKite process.env.MAILKITE API KEY ; async function onRunComplete run { await mk.send { from: "agent@yourco.dev", to: run.requestedBy, subject: Done: ${run.title} , html: renderReport run , // your template, your data } ; } This is also the right choice when you want a send to be testable, logged, and rate-limited by your own code rather than left to the model’s discretion. mk.send returns { id, status } so you can record the outbound message. Can’t take a dependency? Raw HTTP against the REST API works too — prefer the SDK, drop to raw only if you must. 3. Your own webhook: email the agent receives Receiving is always the webhook, whichever way you send. A signed JSON payload arrives the instant mail hits the agent’s address; you verify it in one call and hand the decoded body to the model: python import express from "express"; import { MailKite } from "mailkite"; const app = express ; const SECRET = process.env.MAILKITE WEBHOOK SECRET; app.use "/hooks/agent", express.raw { type: "application/json" } ; app.post "/hooks/agent", async req, res = { // HMAC signature, 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 ; const event = JSON.parse req.body ; if event.type == "email.received" return; // decoded text/html + an auth verdict — hand it to the model as untrusted INPUT await runAgent { task: event.text, from: event.from.address, auth: event.auth } ; } ; app.listen 3000 ; No public URL to host at all? A route with action: 'agent' runs the receive-think-reply loop on a managed queue instead — covered in why we built programmable email for agents /blog/programmable-email-for-agents/ . But when you host the agent yourself, this handler is the receive half of every wiring above. Mixing them: the common shape Most real agents combine wirings. The typical one is receive by webhook, send by whichever fits the decision : inbound arrives at your handler, the model reasons over it, and it replies either through an MCP tool call if the model is choosing to reply or through mk.send if your code always replies . Here’s the decision in one grid. Where I won’t overclaim MCP is not automatically the right answer just because you’re building an agent. Handing the model a send tool means giving it discretion over who gets email and when, and for a lot of flows that discretion is a liability, not a feature — a deterministic mk.send your code controls is easier to test, log, rate-limit, and reason about. Reach for MCP when the agent genuinely owns the decision; reach for the SDK when you do. And if you can’t take a dependency at all, the raw REST API is there — the SDK just saves you the HMAC and the boilerplate. The point isn’t “use all three,” it’s “these are the three, and they answer different questions.” FAQ Should an agent send email through MCP or the SDK? Through MCP when the model should decide whether and when to send — it’s one of the tools it reasons with. Through the SDK when your program decides at a known point “email the result when the run finishes” . Model-chosen sends want discretion; deterministic sends want code you can test and rate-limit. How do I install the MailKite MCP server? It’s hosted at mcp.mailkite.dev with OAuth. In Claude Code: claude mcp add --transport http mailkite https://mcp.mailkite.dev/mcp . Any MCP-speaking client can connect the same way, and there’s also a Claude Code plugin. After that, sending, reading messages, and managing domains are tool calls the agent can make. How does the agent receive email — is that MCP too? Receiving is the webhook: a signed JSON payload arrives at your handler the moment mail hits the agent’s address, and you verify it with MailKite.verifyWebhook . The model can also read the inbox on demand through MCP tools, but push delivery of new mail is the webhook or a managed action: 'agent' route if you don’t want to host an endpoint . Can I use the webhook and MCP together? Yes, and that’s the common shape: receive by webhook, then reply by an MCP tool call model-chosen or mk.send deterministic . The wirings compose — receiving and sending are separate decisions. Do I need a dependency for any of this? No. The SDK mailkite on npm/PyPI/RubyGems, plus Go, PHP, Java, and a CLI saves you the HMAC verification and request boilerplate, but the REST API works over plain HTTPS if you’d rather not add one. Prefer the SDK; drop to raw HTTP only if you must. Receiving is the webhook; sending is a tool the model calls or code your program runs — pick by who owns the decision. Clone demo-email-agent-tool https://github.com/mailkite/demo-email-agent-tool or run it in your browser https://stackblitz.com/github/mailkite/demo-email-agent-tool?file=server.mjs for all three wirings and the mixed loop, then point a domain at MailKite /docs/quickstart and wire email to your agent. Related: why we built programmable email for agents, the AgentMail alternative for AI agents, and agent inbox security by design.