Guide

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 Software Engineer

VerifiedCLI 3.1.16 · Gmail, Outlook · last tested June 8, 2026

Command references used in this guide: nylas email list, nylas email search, and nylas 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.

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.

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.

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.

Next steps