BlogUpdated 4 November 2026 · 5 min read

MCP vs SDK vs API for AI agent email

Should an AI agent get email through an MCP server, an SDK, the REST API or a CLI, and what does each cost?

The short answer

Use MCP when a model in somebody else's client, such as Claude Code, Cursor or ChatGPT, should decide when to read and send. Use an SDK when your own code runs the loop and calls the model, the REST API when you want no dependency or another language, and the CLI when the agent has a shell. With Pidgeon all four are the same API underneath, so the same task meets the same keys, limits and refusals whichever way in you choose.

The task

Every section below does the same small job: wait for the next email at the agent's address, then reply to it. Two calls, and both are the ones an email agent makes most.

MCP: the model holds the tools

Add the hosted server to your client. The first call sends you to Pidgeon to sign in and choose what the agent can do and which address it can reach:

claude mcp add --transport http pidgeon https://pidgeon.ai/api/mcp

From then on the model makes the calls. Asked to answer the next email, it calls await_message, then reply_to_message with the id it got back:

[
  {
    "tool": "await_message",
    "arguments": { "identity_id": "0c939bd7-2d69-4150-a2ac-de994f0e4887", "timeout": 50 }
  },
  {
    "tool": "reply_to_message",
    "arguments": {
      "id": "af08b0a1-284f-464a-a36e-8e15bb7c2eb8",
      "text": "Thanks, the access code worked."
    }
  }
]

Choose it when the agent already lives in an MCP client, or when you want the model to decide which of many actions to take. Three things come with it that you would otherwise write. Message bodies arrive as the Firewall's clean text unless the model asks for the original. A send with no idempotency_key gets one derived from the call, so a model retrying the same reply in the same hour does not send it twice. And each tool's description tells the model what to do when it is refused, including never re-sending after a wait times out.

The cost is control. The model chooses the calls, so the key is the access control: give it the narrowest one the job needs. A key created at read cannot send, however the prompt is phrased. Every tool description also takes room in the model's context.

If you build on the Vercel AI SDK, @pidgeon-ai/ai-sdk turns the same tools into AI SDK tools with the same names. There are guides for LangChain, the OpenAI Agents SDK, the Claude Agent SDK and others under integrations.

The SDK: your code runs the loop

import { client, messagesAwait, messagesReply } from '@pidgeon-ai/sdk';

client.setConfig({ auth: () => process.env.PIDGEON_API_KEY! });

const { data } = await messagesAwait({
  query: { identity_id: identityId, timeout: 50 },
  throwOnError: true,
});

const message = data.data.message;
if (message) {
  await messagesReply({
    path: { id: message.id },
    headers: { 'Idempotency-Key': `reply-${message.id}` },
    body: { text: await draftReply(message) },
    throwOnError: true,
  });
}

Choose it when your program decides what happens and calls the model for the words. The calls are typed from the contract, and the TypeScript package adds the two things a generator cannot: verifyWebhook, for the signature on incoming events, and streamEvents, a held-open read of the event log. The Python package, pip install pidgeon, names every operation the same way (messages_await, messages_reply).

The cost is that you write what MCP gives you. The SDK makes no idempotency key of its own, because a random one protects nothing and one derived from content would swallow a send you meant. So you choose the key, as above.

The REST API: no dependency

curl -G https://pidgeon.ai/api/v1/messages/await \
  -H "Authorization: Bearer $PIDGEON_API_KEY" \
  -d identity_id=$IDENTITY_ID \
  -d timeout=50

curl https://pidgeon.ai/api/v1/messages/$MESSAGE_ID/reply \
  -H "Authorization: Bearer $PIDGEON_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: reply-$MESSAGE_ID" \
  -d '{ "text": "Thanks, the access code worked." }'

Choose it when you work in a language without an SDK, in a workflow tool that makes HTTP requests, or when you want to see every byte. The contract is published at pidgeon.ai/openapi.json, and every error is { error: { code, message, field? } } with a code to branch on. For a server, a webhook on message.received replaces the wait.

The cost is the plumbing: retries, signatures and timeouts are yours. A long wait also needs an HTTP client that allows it, up to 280 seconds.

The CLI: an agent with a shell

pidgeon messages await --identity_id $IDENTITY_ID --timeout 50
pidgeon messages reply --id $MESSAGE_ID --text "Thanks, the access code worked." \
  --idempotency-key reply-$MESSAGE_ID

Choose it when the agent is a coding agent that already runs commands, in a script, or in CI. The output is the API's JSON on stdout, so jq works, and a refusal exits 1 with the API's code and sentence on stderr. Every operation is a command, generated from the same contract, so the CLI cannot offer one the API does not have.

The cost: a command is a process, so it is slower than a call from inside a program. Without --idempotency-key each run makes up its own key, and prints it when a send may have gone out without an answer, so a script can retry with it. A second run that does not reuse it is a second send, so pass the key yourself when a script might run twice.

Choosing

You haveUse
A model in Claude Code, Cursor, ChatGPT or CodexMCP
Your own TypeScript or Python agent loopThe SDK
Another language, or a workflow toolThe REST API
An agent or script with a terminalThe CLI

They mix. A common shape is a webhook into your own code with the SDK, and MCP for a person asking Claude about the same inbox. All four use the same API keys, so the key decides what each can reach.

Start

Sign up and create an address. Then MCP, the SDKs, the CLI, or the API reference. Or paste this into your coding agent and let it set itself up:

Read pidgeon.ai/skill.md and get yourself an email address.
01

Asked often

When should an agent use MCP for email?

When the agent is a model in an MCP client such as Claude Code, Cursor or ChatGPT and should choose the calls itself. The key it holds is the access control, so make it the narrowest the job needs.

Do the MCP server and the SDKs expose the same operations?

Not all of them. The SDKs and the CLI are generated from the API contract and cover every operation; the MCP server offers a chosen set of tools, each naming the endpoint it calls and refused the way that endpoint refuses.

Which one makes an idempotency key for me?

The MCP and AI SDK tools derive one from the call when none is passed, and the CLI makes one per run. The SDKs make none; pass your own.

02

Keep reading

All posts

Give your agents an identity

Pidgeon is building the trust layer of the agentic web.

Register your agents, it’s free