{"slug": "email-as-an-agent-tool-mcp-the-sdk-or-your-own-webhook", "title": "Email as an agent tool: MCP, the SDK, or your own webhook", "summary": "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.", "body_md": "# Email as an agent tool: MCP, the SDK, or your own webhook\n\nThere 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.\n\n“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.\n\nThe fastest one to show is MCP, because it’s a single command and then email is just tools the model has:\n\n```\n# install the hosted MailKite MCP server (Claude Code shown; any MCP client works)\nclaude mcp add --transport http mailkite https://mcp.mailkite.dev/mcp\n```\n\nAfter 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);\n\n[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.\n\n## 1. MCP: email as tools the model calls\n\nUse 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.\n\n`mcp.mailkite.dev`\n\nwith OAuth, plus there's a Claude Code plugin.## 2. The SDK: email as deterministic code\n\nUse 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()`\n\nand the full receive-verify path, in the agent’s own language:\n\n``` js\n// deterministic: your code decides to send, not the model\nimport { MailKite } from \"mailkite\";\nconst mk = new MailKite(process.env.MAILKITE_API_KEY);\n\nasync function onRunComplete(run) {\n  await mk.send({\n    from: \"agent@yourco.dev\",\n    to: run.requestedBy,\n    subject: `Done: ${run.title}`,\n    html: renderReport(run),          // your template, your data\n  });\n}\n```\n\nThis 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()`\n\nreturns `{ id, status }`\n\nso 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.)\n\n## 3. Your own webhook: email the agent receives\n\nReceiving 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:\n\n``` python\nimport express from \"express\";\nimport { MailKite } from \"mailkite\";\n\nconst app = express();\nconst SECRET = process.env.MAILKITE_WEBHOOK_SECRET;\napp.use(\"/hooks/agent\", express.raw({ type: \"application/json\" }));\n\napp.post(\"/hooks/agent\", async (req, res) => {\n  // HMAC signature, replay window, constant-time compare — one call\n  if (!MailKite.verifyWebhook(req.headers[\"x-mailkite-signature\"], req.body, SECRET)) {\n    return res.sendStatus(401);\n  }\n  res.sendStatus(200);\n\n  const event = JSON.parse(req.body);\n  if (event.type !== \"email.received\") return;\n\n  // decoded text/html + an auth verdict — hand it to the model as untrusted INPUT\n  await runAgent({ task: event.text, from: event.from.address, auth: event.auth });\n});\n\napp.listen(3000);\n```\n\nNo public URL to host at all? A route with `action: 'agent'`\n\nruns 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.\n\n## Mixing them: the common shape\n\nMost 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()`\n\n(if your code always replies). Here’s the decision in one grid.\n\n## Where I won’t overclaim\n\nMCP 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()`\n\nyour 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.”\n\n## FAQ\n\n**Should an agent send email through MCP or the SDK?**\nThrough 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.\n\n**How do I install the MailKite MCP server?**\nIt’s hosted at `mcp.mailkite.dev`\n\nwith OAuth. In Claude Code: `claude mcp add --transport http mailkite https://mcp.mailkite.dev/mcp`\n\n. 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.\n\n**How does the agent receive email — is that MCP too?**\nReceiving 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()`\n\n. 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'`\n\nroute if you don’t want to host an endpoint).\n\n**Can I use the webhook and MCP together?**\nYes, and that’s the common shape: receive by webhook, then reply by an MCP tool call (model-chosen) or `mk.send()`\n\n(deterministic). The wirings compose — receiving and sending are separate decisions.\n\n**Do I need a dependency for any of this?**\nNo. The SDK (`mailkite`\n\non 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.\n\nReceiving 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\n\n[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\n\n[point a domain at MailKite](/docs/quickstart)and wire email to your agent.\n\n*Related: why we built programmable email for agents, the AgentMail alternative for AI agents, and agent inbox security by design.*", "url": "https://wpnews.pro/news/email-as-an-agent-tool-mcp-the-sdk-or-your-own-webhook", "canonical_source": "https://mailkite.dev/blog/email-as-an-agent-tool-mcp-sdk-webhook/", "published_at": "2026-07-04 00:00:00+00:00", "updated_at": "2026-07-04 15:01:00.538934+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "ai-tools"], "entities": ["MailKite", "MCP", "SDK", "Claude Code", "StackBlitz"], "alternates": {"html": "https://wpnews.pro/news/email-as-an-agent-tool-mcp-the-sdk-or-your-own-webhook", "markdown": "https://wpnews.pro/news/email-as-an-agent-tool-mcp-the-sdk-or-your-own-webhook.md", "text": "https://wpnews.pro/news/email-as-an-agent-tool-mcp-the-sdk-or-your-own-webhook.txt", "jsonld": "https://wpnews.pro/news/email-as-an-agent-tool-mcp-the-sdk-or-your-own-webhook.jsonld"}}