Skip to main content
AI8 min read

How Claude Uses Senddio to Send Messages

A complete walkthrough of the Senddio MCP server — what tools Claude gets, how agent-scoped keys restrict channels, how to debug messages from AI, and how rate limits surface in Claude's tool loop.

S
Senddio Team
Engineering · October 1, 2026

Claude can send messages. Not through a plugin, not through a webhook, not through a convoluted server-side bridge — through a Model Context Protocol (MCP) server that exposes Senddio's messaging API as native tools. When the connection is configured, Claude has five tools in its context and a scoped API key that determines exactly what it is allowed to send.

This post walks through the full setup, explains how the agent-key system works, and shows what happens in the Senddio dashboard when Claude sends a message.

What MCP is and why it matters here

The Model Context Protocol is Anthropic's open standard for connecting AI models to external tools and data sources. An MCP server is a local process — not a hosted service, not a third-party plugin — that speaks the MCP wire protocol and exposes functions Claude can call natively.

From Claude's perspective, an MCP tool is indistinguishable from any other tool in its context. When you ask Claude to send a notification, it calls send_message the same way it would call a web search or a calculator. There is no prompt engineering required; you do not need to tell Claude how to use the API. The tool definitions carry parameter schemas and descriptions that Claude reads and reasons about automatically.

For messaging specifically, this matters because it means Claude can handle multi-step flows. It can send a message, check its delivery status, and decide what to do if delivery fails — all within a single conversation turn, without you writing any orchestration logic.

The five tools

The @senddio/mcp-server package exposes five tools:

ToolWhat it doesRequired fields
send_message Universal send — routes to any channel channel, to (array), body; from and subject for email
get_message_status Returns delivery status and event timeline message_id (ID returned by send_message)
list_recent_messages Lists recent outbound messages, filterable by channel or tag —
list_inbound_messages Lists inbound replies (email replies, WhatsApp replies) —
get_delivery_stats Aggregate delivery statistics for a period —

The MCP server supports all six channels, including push. For push, supply a registration token issued by your app and a notification title. Scope each agent key to the channels it needs.

Setting up the MCP server

Step 1 — Install the package

npm install -g @senddio/mcp-server
# or use npx — no global install needed

Step 2 — Create an agent-scoped API key

In the Senddio dashboard, go to API Keys → New key. Set:

  • Type: Agent key (sdo_mcp_ prefix)
  • Agent name: claude
  • Channels: select only the channels you want Claude to use
  • Expiry: optional — set a TTL if you want automatic rotation

Copy the key. It is shown exactly once.

Step 3 — Add the server to Claude Desktop

Open your Claude Desktop configuration file. On macOS it lives at ~/Library/Application Support/Claude/claude_desktop_config.json. Add the Senddio server to the mcpServers block:

{
  "mcpServers": {
    "senddio": {
      "command": "npx",
      "args": ["-y", "@senddio/mcp-server"],
      "env": {
        "SENDDIO_API_KEY": "sdo_mcp_your_key_here"
      }
    }
  }
}

Save the file and restart Claude Desktop. The tools appear in Claude's context on the next session. You will see a hammer icon in the Claude interface when MCP tools are available.

How agent-scoped keys work

An sdo_mcp_ key is structurally different from a production key in three ways:

Channel scope. When you create the key, you select which channels it can access. A key scoped to ["email", "slack"] cannot be used to send SMS, even if Claude constructs a valid SMS request. The restriction is enforced by the API when it authenticates the key — the MCP server has no mechanism to override it, and Claude has no way to work around it.

Agent attribution. The agentName field on the key (set to claude during creation) is copied to every message the key sends as Message.sentByAgent. This is how the dashboard knows a message came from Claude rather than a human. The attribution is automatic — Claude does not need to declare itself in the message payload.

Revocability. You can revoke an agent key independently of your main API credentials. If you need to cut Claude's access, delete the key. In-flight requests using the revoked key will fail with a 401. Claude's MCP tools will stop working until a new key is configured.

What a Claude conversation looks like

Once configured, you can ask Claude things like:

"Send a Slack message to the #deploys channel saying the release is complete."

Claude calls send_message with channel slack, the channel ID you have configured, and the message body. The tool returns a message ID and status: queued. If you want Claude to confirm delivery, follow up:

"Check whether that message was delivered."

Claude calls get_message_status with the message ID from the previous call. It reads the status and reports back in natural language.

You can also ask Claude to summarise recent activity:

"How many messages has this workspace sent in the last 24 hours and what was the delivery rate?"

Claude calls get_delivery_stats, reads the response, and synthesises a summary. No dashboard lookup required.

What shows up in the dashboard

Every message Claude sends appears in the Senddio logs with a teal AI badge in place of the user avatar. The row shows the channel, recipient, delivery status, and agent name (claude). You can filter the logs to sentByAgent = claude to isolate Claude's traffic from human-initiated sends.

This separation is operationally useful. If Claude is running an automated notification loop, you want to see its send volume in real time — especially if you are on a plan with a daily limit. The filter lets you answer "how many of today's 300 free-tier emails did Claude send?" in seconds.

The AI badge also appears on the message detail drawer. When you open a specific message, you see the full delivery timeline alongside the agent attribution. This makes debugging straightforward: if Claude sent a malformed subject line or the wrong recipient, you can see the exact payload it sent without digging through application logs.

Rate limits and how Claude handles them

Claude is not exempt from your organisation's rate limits. If your plan allows 300 emails per day and Claude sends 280 of them, the next send call will return:

{
  "error": "RATE_LIMIT_EXCEEDED",
  "message": "Daily email limit of 300 reached. Quota resets at midnight UTC.",
  "code": "rate_limit_exceeded",
  "resetAt": 1720051200
}

Claude reads this structured error and can surface it in the conversation, schedule a retry, or escalate to you — depending on how you have framed the task. The important thing is that the error is machine-readable, not a vague HTTP 429. Claude can reason about the reset timestamp and decide intelligently whether to wait or stop.

The rate-limit headers on every response — X-RateLimit-Remaining and X-RateLimit-Reset — are also available to the MCP server, so Claude can proactively warn you when the remaining quota is low rather than waiting for a hard failure.

Security considerations

Because the MCP server runs locally, the API key is stored in your Claude Desktop configuration file — not transmitted to Anthropic's servers. Claude reads the environment variable when it initialises the MCP server process; the key never appears in the conversation context or in any logged tool call.

Scope your agent keys conservatively. If Claude's job is to send Slack notifications, give it a key scoped to ["slack"] only. If something goes wrong and the key is somehow exposed, the blast radius is limited to Slack sends — nothing else.

Audit regularly. The Senddio logs show every message the key has sent. If you see unexpected sends, revoke the key immediately from the dashboard.

Using Senddio with Claude in code (not Desktop)

If you are building a Claude-powered application using the Anthropic API rather than Claude Desktop, you can integrate Senddio as a tool directly in your code. The pattern is the same: create an sdo_mcp_ key, define the tool schemas in your Anthropic API call, and implement the tool handler to call the Senddio API.

Set SENDDIO_API_KEY on your backend. For email, set SENDDIO_EMAIL_FROM to an address on a domain verified in Senddio. For SMS, set SENDDIO_SMS_FROM to your provisioned number. Set ANTHROPIC_MODEL to a model enabled for your Anthropic account. The example translates the tool's email body into the API's text field.

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

const tools = [
  {
    name: "send_message",
    description: "Send a message via Senddio. Supported channels: email, sms, slack, telegram.",
    input_schema: {
      type: "object",
      properties: {
        channel: { type: "string", enum: ["email", "sms", "slack", "telegram"] },
        to: { type: "string", description: "Recipient address, phone number, or channel ID" },
        body: { type: "string", description: "Message body" },
        from: { type: "string", description: "Verified email sender or provisioned SMS number" },
        subject: { type: "string", description: "Email subject (required for email channel)" }
      },
      required: ["channel", "to", "body"]
    }
  }
];

async function handleToolCall(toolName, toolInput) {
  if (toolName === "send_message") {
    const payload = toolInput.channel === "email"
      ? {
          channel: "email",
          from: toolInput.from ?? process.env.SENDDIO_EMAIL_FROM,
          to: toolInput.to,
          subject: toolInput.subject,
          text: toolInput.body,
          messageType: "transactional"
        }
      : toolInput.channel === "sms"
        ? { channel: "sms", from: toolInput.from ?? process.env.SENDDIO_SMS_FROM, to: toolInput.to, body: toolInput.body }
        : { channel: toolInput.channel, to: toolInput.to, body: toolInput.body };
    const res = await fetch("https://api.senddio.com/v1/send", {
      method: "POST",
      headers: {
        "Authorization": "Bearer " + process.env.SENDDIO_API_KEY,
        "Content-Type": "application/json"
      },
      body: JSON.stringify(payload)
    });
    const result = await res.json();
    if (!res.ok) throw new Error(result.message ?? "Senddio rejected the request");
    return result;
  }
}

// Use tools in your Anthropic API call
const response = await client.messages.create({
  model: process.env.ANTHROPIC_MODEL,
  max_tokens: 1024,
  tools,
  messages: [{ role: "user", content: "Send a Slack notification that the batch job completed." }]
});

Summary

  • The Senddio MCP server gives Claude five messaging tools: send, status check, recent messages, inbound messages, delivery stats.
  • Agent keys use the sdo_mcp_ prefix, carry an agentName, and enforce channel scope server-side.
  • Every message Claude sends gets an AI badge in the dashboard and is filterable by agent name.
  • Rate limit errors return structured JSON Claude can reason about — including the reset timestamp.
  • Key material stays local; scope keys to the minimum channels required.
  • The same pattern works in the Anthropic SDK for application-level integrations.
Published
October 1, 2026
Share on XAll posts

Try Senddio free.

One API, six channels. 300 emails per day on the free plan — no credit card.

Read the API docsStart free