Source: https://cli.nylas.com/guides/openai-assistants-email-tools

# OpenAI Assistants Email Tools (CLI)

OpenAI's function calling lets a model ask your code to run a named function with structured arguments. That's exactly the seam you need to give an assistant email: define a few tool schemas, and dispatch each call to the Nylas CLI instead of writing a provider SDK. The model decides what to do; the CLI does it across six providers and returns JSON. This guide wires function calling to the CLI and keeps sends behind a human.

Written by [Pouya Sanooei](https://cli.nylas.com/authors/pouya-sanooei) Software Engineer

Updated June 8, 2026

> **TL;DR:** Define OpenAI function-calling tools for the actions you want — list, search, draft — and dispatch each tool call to the Nylas CLI as a subprocess that returns JSON. The model picks the tool and arguments; your dispatcher runs `nylas email list --json` and feeds the result back. One dispatcher covers six providers with no SDK. Keep sends behind a human by exposing a draft tool, not a send tool.

Command references used in this guide: [`nylas email list`](https://cli.nylas.com/docs/commands/email-list), [`nylas email search`](https://cli.nylas.com/docs/commands/email-search), and [`nylas email drafts create`](https://cli.nylas.com/docs/commands/email-drafts-create).

## How do you give an OpenAI assistant email?

You give an OpenAI assistant email by declaring function-calling tools and routing each call to the Nylas CLI. A tool is a JSON schema describing a name, a description, and parameters; when the model returns a `tool_calls` entry, your code runs the matching command and returns the output as a tool message. Because `nylas email list --json` emits structured data, the model gets clean JSON to reason over.

Authenticate the CLI once with `nylas auth login`; the stored grant is reused on every subprocess call, so your dispatcher never handles credentials. OpenAI's function-calling contract — tool schemas in, `tool_calls` out, tool results back — is documented in the [OpenAI function calling guide](https://platform.openai.com/docs/guides/function-calling).

## How do you define the tool schemas?

Define one schema per action so the model has clear, narrow capabilities. A `list_unread` tool takes an integer limit; a `search_email` tool takes a query string. Keep descriptions specific — “list unread emails as JSON” — because the model selects tools from those descriptions. Two read tools plus a draft tool cover most inbox assistants.

The schemas are pure data; they don't run anything until your dispatcher maps a tool name to a CLI command. That separation keeps the model's view simple and the execution auditable: every action is one command with explicit arguments you can log. Define no more than a handful of tools — a smaller surface is easier for the model to use correctly.

```python
tools = [
  {
    "type": "function",
    "function": {
      "name": "list_unread",
      "description": "List unread emails as JSON across the connected mailbox.",
      "parameters": {
        "type": "object",
        "properties": {"limit": {"type": "integer", "default": 10}},
      },
    },
  },
  {
    "type": "function",
    "function": {
      "name": "search_email",
      "description": "Search the mailbox with a provider-agnostic query, return JSON.",
      "parameters": {
        "type": "object",
        "properties": {"query": {"type": "string"}},
        "required": ["query"],
      },
    },
  },
]
```

## How do you dispatch tool calls to the CLI?

Dispatch by mapping each tool name to a CLI command, running it as a subprocess, and returning stdout as the tool result. The loop is: send the message, read any `tool_calls`, run them, append the results, and call the model again until it answers. A triage request resolves in two or three round trips, with the model reading JSON between each.

Keep the dispatcher a thin switch — name to command — so it stays easy to audit. The model never sees a provider name; it calls `search_email`, and your code runs `nylas email search`. That indirection is what lets one assistant work against Gmail, Outlook, and four more backends without changing a line of agent code.

```python
import subprocess, json
from openai import OpenAI
client = OpenAI()

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.chat.completions.create(model="gpt-4o", messages=msgs, tools=tools)
# Read resp tool_calls, run_tool() each, append results, call again until done.
```

## How do you keep sends safe?

Keep outbound actions behind a human. Expose a draft tool that runs `nylas email drafts create` — it composes a message without sending and returns a draft ID — instead of a send tool. A person reviews the draft and sends it, so a misclassification can't put mail in a customer's inbox. This is the single most important guardrail for an email assistant.

The model reads untrusted content, and a prompt injection in a message body can try to add an unwanted `tool_call`. 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](https://cli.nylas.com/guides/stop-ai-agent-going-rogue).

## Next steps

- [Anthropic tool use email](https://cli.nylas.com/guides/anthropic-tool-use-email) — the same pattern with Claude's Messages API
- [Connect Claude to email](https://cli.nylas.com/guides/connect-claude-to-email) — MCP-based wiring instead of function calling
- [Human-in-the-loop email agent](https://cli.nylas.com/guides/build-human-in-loop-email-agent) — draft-and-approve guardrails
- [Build an AI email triage agent](https://cli.nylas.com/guides/build-ai-email-triage-agent) — classification and routing end to end
- [Semantic Kernel email agent](https://cli.nylas.com/guides/semantic-kernel-email-agent) — the CLI as a native plugin
- [Full command reference](https://cli.nylas.com/docs/commands) — every flag and subcommand documented
