Guide
Anthropic Tool Use for Email (CLI)
Anthropic's tool use lets Claude request a named tool with structured input, then continue once you return the result. It's the clean way to give Claude email without an MCP server: define a few tool schemas and dispatch each call to the Nylas CLI. Claude decides what to do; the CLI does it across six providers and returns JSON. This guide builds the tool-use loop and keeps sends behind a human review step.
Written by Caleb Geene Director, Site Reliability Engineering
Command references used in this guide: nylas email list, nylas email search, and nylas email drafts create.
How do you give Claude email with tool use?
You give Claude email by declaring tools on the Messages API and dispatching each request to the Nylas CLI. A tool is a name, a description, and an input_schema; when Claude returns a tool_use block, your code runs the matching command and replies with a tool_result. Because nylas email list --json emits structured data, Claude receives clean JSON to reason over rather than HTML.
Authenticate the CLI once with nylas auth login; the stored grant is reused on every subprocess call, so your dispatcher never handles credentials. This is a lighter alternative to running an MCP server when you only need a few actions. Anthropic's tool-use contract — tool schemas in, tool_use out, tool_result back — is documented in the Anthropic tool use docs.
How do you define the tool schemas?
Define one tool per action with a precise description, since Claude selects tools from those descriptions. A list_unread tool takes an integer limit; a search_email tool takes a query string. Keep the set small — two read tools and a draft tool handle most inbox work — because a narrow toolset is easier for the model to use correctly and easier for you to audit.
The input_schema is standard JSON Schema, so required fields and types are enforced before your code runs. That validation, plus a thin dispatcher, means every action is one CLI command with explicit arguments you can log. Claude never sees a provider name — it calls search_email, and your code runs nylas email search.
tools = [
{
"name": "list_unread",
"description": "List unread emails as JSON across the connected mailbox.",
"input_schema": {
"type": "object",
"properties": {"limit": {"type": "integer"}},
},
},
{
"name": "search_email",
"description": "Search the mailbox with a provider-agnostic query, return JSON.",
"input_schema": {
"type": "object",
"properties": {"query": {"type": "string"}},
"required": ["query"],
},
},
]How do you run the tool-use loop?
Run the loop: send the message with the tools, and when the response's stop_reason is tool_use, run the requested command and append a tool_result block, then call the API again. Repeat until stop_reason is end_turn. A triage request usually completes in two or three round trips, with Claude reading JSON between each.
Use a current model such as claude-sonnet-4-6 for the loop. The dispatcher maps each tool name to a CLI command and returns stdout; that indirection is what lets one integration work across Gmail, Outlook, and four more providers without provider-specific code in your agent.
import subprocess, anthropic
client = anthropic.Anthropic()
def run_tool(name, args):
if name == "list_unread":
cmd = ["nylas", "email", "list", "--unread", "--json",
"--limit", str(args.get("limit", 10))]
elif name == "search_email":
cmd = ["nylas", "email", "search", args["query"], "--json", "--limit", "20"]
else:
return "unknown tool"
return subprocess.run(cmd, capture_output=True, text=True, check=True).stdout
msgs = [{"role": "user", "content": "Summarize my unread mail."}]
resp = client.messages.create(
model="claude-sonnet-4-6", max_tokens=1024, tools=tools, messages=msgs)
# While resp.stop_reason == "tool_use": run_tool() each block,
# append a tool_result, and call messages.create again.How do you keep sends safe?
Keep outbound actions behind a human. Expose a draft tool that runs nylas email drafts create — composing a message without sending it and returning a draft ID — instead of a send tool. A person reviews and sends, so a misclassification can't reach a customer. This is the single most important guardrail for a Claude email agent.
Claude reads untrusted content, and a prompt injection in a message body can try to inject an unwanted tool_use. Containment outside the model's reasoning — a review step, or connector-level rules — holds even when injected text tries to talk past it. For deterministic enforcement at the connector, see stopping a rogue agent at the connector layer.
Next steps
- Connect Claude to email — the MCP path instead of raw tool use
- OpenAI assistants email tools — function calling dispatched to the CLI
- Human-in-the-loop email agent — draft-and-approve guardrails
- MCP email server setup — expose the same actions as MCP tools
- Semantic Kernel email agent — the CLI as a native plugin
- Full command reference — every flag and subcommand documented