Documentation
This guide wires a Claude Agent SDK (TypeScript) app to a governed inbox. Everything below is sandbox-first: nothing here enables external send, promotes a token to production, or widens its scope — those are human actions.
1. Get an agent token
Your account API key (from signup) is a human credential. The agent gets its own scoped token by provisioning a sandbox inbox:
curl -X POST https://mbag.ai/api/v1/mcp/sandbox-inboxes \
-H "Authorization: Bearer $YOUR_ACCOUNT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"label": "My Agent"}'The response includes sandbox_token — shown exactly once; only its hash is
stored. That token is scoped to exactly that inbox, with sandbox capabilities.
2. The key comes from the environment, always
const API_KEY = process.env.MAILBUTTONS_API_KEY;
if (!API_KEY) throw new Error("Set MAILBUTTONS_API_KEY (a scoped sandbox token).");Never inline the token into source, a tool definition, or a prompt. Set
MAILBUTTONS_API_URL=https://mbag.ai alongside it.
3. Attach the MCP server
Point the SDK at the Mailbuttons MCP server so the model calls
mailbuttons_* tools directly, launched over stdio:
import { query } from "@anthropic-ai/claude-agent-sdk";
const result = query({
prompt: userTurn,
options: {
mcpServers: {
mailbuttons: {
command: "npx",
args: ["-y", "@mailbuttons/mcp-server"],
// The token is inherited from the parent env — not passed inline.
env: { MAILBUTTONS_API_KEY: process.env.MAILBUTTONS_API_KEY! },
},
},
// Let the agent read and send, but route sends through the governed tool.
allowedTools: [
"mcp__mailbuttons__mailbuttons_list_messages",
"mcp__mailbuttons__mailbuttons_get_message",
"mcp__mailbuttons__mailbuttons_get_thread",
"mcp__mailbuttons__mailbuttons_send_email",
],
},
});4. Handle governed send outcomes
A send never "just sends." blocked and draft_pending_approval are normal
results, not errors — do not retry to force delivery:
status |
meaning | what to do |
|---|---|---|
sent / queued |
delivered | done |
blocked |
recipient fails policy; policy.matched_rule says which |
surface the rule; don't retry |
draft_pending_approval |
external recipient, token lacks send_external |
a human must approve; parked as a draft |
If the agent (or a malicious inbound message) tries to email an outside address, this is exactly the wall it hits. That is the product working as designed.
5. Going live
When the integration is ready, the agent calls
mailbuttons_request_promotion with the inbox id and the capabilities it
wants (e.g. send_external). That returns an approval_url and changes
nothing on its own — a human approves it. The agent cannot approve its own
request.
Reference implementation
A complete scheduling agent built this way:
github.com/mailbuttons/claude-scheduling-agent-ts
(Python variant: claude-scheduling-agent-py).