Guide
AI Agent CLI for Email and Calendar
An AI agent CLI turns shell commands into reliable LLM tools. This guide shows how to use the Nylas CLI as your email and calendar tool backend instead of writing OAuth flows and provider-specific API clients. One subprocess call per tool works across Gmail, Outlook, Exchange, Yahoo, iCloud, and IMAP.
Written by Pouya Sanooei Software Engineer
Reviewed by Hazik
What is an AI agent CLI for email and calendar?
An AI agent CLI is a command-line tool that an LLM can call through subprocess wrappers or MCP. For email and calendar tasks, the CLI becomes the tool backend: it lists messages, searches mail, sends replies, reads calendars, creates meetings, and returns JSON the agent can parse.
This pattern keeps the tool boundary small. Instead of writing OAuth clients and provider-specific API code, you define each capability as one CLI call with structured output. The agent decides when to invoke each command based on the user's request.
The tool-use pattern — define tools, let the LLM decide when to call them — is now supported by every major provider: OpenAI's function calling API (June 2023), Anthropic's tool use API (April 2024), and Google's function calling in Gemini (December 2023). The interface differs slightly, but the core loop is identical: send tool definitions, receive tool calls, return results.
Email and calendar are natural fits for this pattern. Your agent needs to read messages, send replies, check availability, create events. A custom Gmail OAuth integration requires roughly 300 lines of boilerplate just for token management. The Nylas CLI eliminates that: run nylas email list or nylas calendar events list from your tool handlers with one subprocess call per tool, and get back JSON covering Gmail, Outlook, Exchange, Yahoo, iCloud, and IMAP.
1. Install and authenticate
Authentication is a one-time step that stores your credentials locally so every subsequent CLI call works without prompts. The Nylas CLI stores OAuth tokens in ~/.config/nylas/ and supports 6 providers — Gmail, Outlook, Exchange, Yahoo, iCloud, and IMAP — through a single auth flow. After running nylas auth login, verify the connection with nylas auth whoami before wiring tools into your agent.
# Install
brew install nylas/nylas-cli/nylas
# Authenticate (one-time)
nylas auth login
# Verify
nylas auth whoami
nylas email list --limit 32. The tool pattern
The tool pattern is a Python function that wraps a CLI command in a subprocess.run() call and returns the stdout as structured JSON. Every agent framework — OpenAI, Anthropic, Google — expects tools as callable functions with typed parameters. Each function below maps to exactly one CLI command, keeping tool implementations under 10 lines each.
import subprocess
import json
def list_emails(limit=10, unread_only=False):
"""List recent emails from the authenticated mailbox."""
cmd = ["nylas", "email", "list", "--limit", str(limit), "--json"]
if unread_only:
cmd.append("--unread")
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
return f"Error: {result.stderr}"
return result.stdout
def send_email(to, subject, body):
"""Send an email. Requires --yes to skip confirmation."""
result = subprocess.run(
["nylas", "email", "send", "--to", to, "--subject", subject, "--body", body, "--yes"],
capture_output=True,
text=True
)
if result.returncode != 0:
return f"Error: {result.stderr}"
return "Email sent successfully."When your agent calls list_emails(), the CLI returns structured JSON with sender, subject, snippet, timestamp, and folder data. The --json flag on nylas email list produces an array of message objects. Each object includes a 200-character snippet the LLM can use to decide whether to read the full message.
[
{
"id": "a1b2c3d4e5f6g7h8",
"subject": "Re: API design review",
"from": [{"name": "Sarah Chen", "email": "sarah@example.com"}],
"to": [{"name": "Alex Rivera", "email": "alex@example.com"}],
"snippet": "I've updated the endpoint spec. The breaking change is in the auth middleware...",
"date": "2026-03-25T14:22:18-04:00",
"unread": true,
"folders": ["INBOX"]
},
{
"id": "b2c3d4e5f6g7h8i9",
"subject": "Deployment complete: staging-v2.4.0",
"from": [{"name": "CI Bot", "email": "ci@example.com"}],
"to": [{"name": "Alex Rivera", "email": "alex@example.com"}],
"snippet": "All 47 tests passed. Deployment to staging completed in 3m 22s.",
"date": "2026-03-25T13:15:00-04:00",
"unread": false,
"folders": ["INBOX"]
}
]When send_email() runs nylas email send --yes --json, the CLI returns a confirmation object with the message ID, recipients, and timestamp. The --yes flag skips interactive confirmation, which is required for non-interactive agent loops where stdin isn't available.
{
"id": "msg_c3d4e5f6g7h8i9j0",
"subject": "Re: API design review",
"from": [{"name": "Alex Rivera", "email": "alex@example.com"}],
"to": [{"name": "Sarah Chen", "email": "sarah@example.com"}],
"date": "2026-03-25T14:25:00-04:00",
"object": "message"
}3. Tool definitions for the LLM
Tool definitions are JSON schemas that tell the LLM what each function does, what parameters it accepts, and which parameters are required. The LLM reads these definitions to decide when and how to call each tool. According to OpenAI's function calling docs, well-written descriptions reduce hallucinated tool calls by up to 40%. The format below follows OpenAI's schema; Anthropic and Google use similar structures.
tools = [
{
"type": "function",
"function": {
"name": "list_emails",
"description": "List recent emails from the user's inbox. Use unread_only=True to filter unread only.",
"parameters": {
"type": "object",
"properties": {
"limit": {"type": "integer", "description": "Max number of emails to return", "default": 10},
"unread_only": {"type": "boolean", "description": "Only return unread emails", "default": False}
}
}
}
},
{
"type": "function",
"function": {
"name": "send_email",
"description": "Send an email. Use for replies or new messages.",
"parameters": {
"type": "object",
"properties": {
"to": {"type": "string", "description": "Recipient email address"},
"subject": {"type": "string", "description": "Email subject"},
"body": {"type": "string", "description": "Email body (plain text)"}
},
"required": ["to", "subject", "body"]
}
}
}
]4. Wire tools into your agent loop
The agent loop is a while loop that sends messages to the LLM, checks for tool calls in the response, executes those tools, appends the results to the conversation context, and calls the LLM again. This loop repeats until the LLM returns a plain text response with no tool calls. A typical email agent processes 3-5 tool calls per user request — for example, listing emails, reading one, then drafting a reply.
import json
from openai import OpenAI
client = OpenAI()
context = []
def call():
return client.chat.completions.create(
model="gpt-4o",
messages=context,
tools=tools,
tool_choice="auto"
)
def handle_tool_call(item):
name = item.function.name
args = json.loads(item.function.arguments or "{}")
if name == "list_emails":
result = list_emails(**args)
elif name == "send_email":
result = send_email(**args)
else:
result = "Unknown tool"
return {
"role": "tool",
"tool_call_id": item.id,
"content": result
}
def process(user_input):
context.append({"role": "user", "content": user_input})
while True:
response = call()
message = response.choices[0].message
tool_calls = message.tool_calls or []
if not tool_calls:
final = message.content or ""
context.append({"role": "assistant", "content": final})
return final
context.append({
"role": "assistant",
"content": message.content or "",
"tool_calls": [
{
"id": tc.id,
"type": "function",
"function": {
"name": tc.function.name,
"arguments": tc.function.arguments,
},
}
for tc in tool_calls
],
})
for item in tool_calls:
context.append(handle_tool_call(item))5. Add calendar tools
Calendar tools follow the same subprocess pattern as email tools. The Nylas CLI exposes 3 calendar operations — listing events, creating events, and finding available meeting times — each as a single command that returns JSON. Calendar data includes participant RSVP status, conferencing links, and timezone information, giving the agent enough context to schedule meetings without follow-up questions.
def list_events(days=7):
"""List upcoming calendar events."""
result = subprocess.run(
["nylas", "calendar", "events", "list", "--days", str(days), "--json"],
capture_output=True,
text=True
)
return result.stdout if result.returncode == 0 else f"Error: {result.stderr}"
def create_event(title, start, end, participants=None):
"""Create a calendar event."""
cmd = ["nylas", "calendar", "events", "create", "--title", title, "--start", start, "--end", end]
if participants:
for p in participants:
cmd.extend(["--participant", p])
result = subprocess.run(cmd, capture_output=True, text=True)
return result.stdout if result.returncode == 0 else f"Error: {result.stderr}"
def find_meeting_time(participants, duration="30m"):
"""Find when participants are free for a meeting."""
result = subprocess.run(
["nylas", "calendar", "find-time", "--participants", ",".join(participants),
"--duration", duration, "--json"],
capture_output=True,
text=True
)
return result.stdout if result.returncode == 0 else f"Error: {result.stderr}"When list_events() calls nylas calendar events list --json, the output includes participant RSVP status, conferencing provider and join URL, and IANA timezone identifiers. Each event object contains start/end times as Unix timestamps, which the LLM can compare to find conflicts or open slots.
[
{
"id": "evt_9x8y7z6w5v4u3t2s",
"title": "API Design Review",
"when": {
"start_time": 1774535400,
"end_time": 1774537200,
"start_timezone": "America/New_York",
"object": "timespan"
},
"participants": [
{"email": "alex@example.com", "status": "yes"},
{"email": "sarah@example.com", "status": "yes"},
{"email": "jordan@example.com", "status": "noreply"}
],
"status": "confirmed",
"conferencing": {
"provider": "Google Meet",
"details": {"url": "https://meet.google.com/abc-defg-hij"}
}
}
]6. CLI commands you can wrap
The Nylas CLI provides 7 commands that map directly to agent tools — 4 for email operations and 3 for calendar operations. Each command accepts a --json flag for structured output that LLMs can parse reliably. The tables below list every command, its flags, and the agent use case it covers.
| Command | Use case |
|---|---|
nylas email list --json | List messages (add --unread, --limit) |
nylas email search "query" --json | Search by keyword |
nylas email read msg_id --json | Read full message |
nylas email send --to X --subject Y --body Z --yes | Send email |
Calendar
| Command | Use case |
|---|---|
nylas calendar events list --json | List events (add --days, --timezone) |
nylas calendar events create --title X --start Y --end Z | Create event |
nylas calendar availability find --participants X,Y --duration 30 --json | Find free slots |
7. Context engineering tips
Context engineering is the practice of managing what goes into and out of an LLM's context window to control cost and quality. Each tool call's JSON output counts toward the context window — a single nylas email list --limit 50 call can produce 8,000-12,000 tokens, roughly 10% of GPT-4o's 128K context. Keeping tool outputs compact improves response quality and cuts API costs.
- Use
--limit 5or--limit 10instead of fetching everything — 10 messages produce roughly 2,000 tokens - Summarize large outputs in a separate LLM call before appending to the main context
- Only expose the tools the agent needs for the task. Email-only agents don't need calendar tools — fewer tool definitions means fewer tokens per request.
Using Cursor or Claude instead?
The Model Context Protocol (MCP) is an open standard from Anthropic that lets AI assistants call external tools without custom agent code. If you want email and calendar tools inside Claude Code, Cursor, or VS Code, the Nylas MCP server provides the same capabilities as the subprocess tools above — but installs with a single command instead of writing Python wrappers. MCP supports 4 assistants: Claude Code, Cursor, Windsurf, and VS Code.
The install command registers the Nylas MCP server with your chosen assistant's configuration file. After running it, the assistant gains access to email list, send, search, and calendar tools without any Python code.
nylas mcp install --assistant claude-code
# or: cursor, windsurf, vscodeSee Give AI Agents Email Access via MCP for the full setup walkthrough.
FAQ
These are the most common questions developers ask when wiring CLI-based email and calendar tools into LLM agents. The answers cover provider compatibility, authentication in headless environments, and multi-grant setups — the 3 areas where agent builds most often stall.
Does this work with Anthropic, Gemini, or other LLM providers?
Yes. The tool pattern (define tools, handle tool calls, append results to context) is the same across all 3 major providers. Swap the client.chat.completions.create call for your provider's equivalent. The CLI subprocess wrappers don't change — they return JSON regardless of which LLM consumes it.
What if the CLI is not in PATH?
Use the full path to the binary (e.g. /opt/homebrew/bin/nylas on macOS) or pass shell=True with the full command string. For Homebrew installs, which nylas shows the path.
How do I use a specific mailbox when I have multiple grants?
Set NYLAS_GRANT_ID in the environment before running your agent, or pass the grant ID as the first argument to each command (e.g. nylas email list grant_xyz --json). Use nylas auth list to see your grants.
Why use --yes when sending email?
Without --yes, nylas email send prompts for confirmation interactively. In an agent loop, stdin is not available, so the command would hang. Always use --yes for non-interactive use.
Can I run this in a server or CI environment?
Yes. Authenticate with nylas auth config and set NYLAS_API_KEY in your environment. The CLI reads credentials from config and env vars, so no interactive login is needed after initial setup.
Next steps
- Give your AI coding agent an email address -- setup for Claude Code, Cursor, Codex CLI, and OpenClaw
- Send email from the terminal -- full CLI reference for email commands
- Manage calendar from the terminal -- events, availability, timezone handling
- Give AI agents email access via MCP -- plug into Claude, Cursor, or VS Code
- Build an AI email triage agent -- classify, draft, and archive with Python + Nylas CLI
- Record meetings from the CLI -- add meeting recording and transcription to agent workflows
- Receive inbound email -- give your agent a dedicated email address for incoming messages
- Command reference -- every flag and subcommand
- Email APIs for AI agents compared -- Gmail API vs Graph vs SendGrid vs IMAP vs Nylas CLI