Guide

Semantic Kernel Email Agent (CLI Plugin)

Microsoft Semantic Kernel turns plain functions into plugins the model can call, with automatic function-calling that picks the right one. Giving such an agent email usually means Microsoft Graph and an Azure app. The lighter path wraps the Nylas CLI in a native plugin function: each call is one subprocess returning JSON, and the same plugin reaches Gmail, Outlook, and four more providers. This guide builds the plugin and keeps sends behind a human.

Written by Prem Keshari Senior SRE

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 a Semantic Kernel agent email?

You give a Semantic Kernel agent email by writing a native plugin whose functions call the Nylas CLI. A native function is a Python or C# method decorated with @kernel_function and a description; the kernel exposes it for automatic function calling. Inside, you run a CLI command and return its stdout — and because nylas email list --json emits structured data, the agent receives clean JSON to reason over.

Authenticate the CLI once with nylas auth login; the stored grant is reused on every subprocess call, so the plugin never handles credentials. This subprocess boundary keeps provider details out of your kernel code, and it sidesteps the Azure app registration Graph would require. Semantic Kernel's plugin and function-calling model is documented in the Semantic Kernel docs.

How do you build the plugin?

Build the plugin as a class with one decorated method per action. The @kernel_function decorator's description tells the model when to call it, and type-annotated parameters become the function's schema. Keep each method small — a reader that runs nylas email list --json, a search that runs nylas email search — so the kernel has clear, narrow capabilities to choose from.

Register the plugin with kernel.add_plugin and the agent can call it during automatic function calling. Two read methods plus a draft method are enough for most inbox agents. The thin wrapper returns JSON the kernel passes straight to the model, with no provider object to serialize.

import subprocess
from semantic_kernel.functions import kernel_function

class EmailPlugin:
    @kernel_function(description="List unread emails as JSON.")
    def list_unread(self, limit: int = 10) -> str:
        out = subprocess.run(
            ["nylas", "email", "list", "--unread", "--json", "--limit", str(limit)],
            capture_output=True, text=True, check=True,
        )
        return out.stdout

    @kernel_function(description="Search the mailbox, return JSON.")
    def search_email(self, query: str) -> str:
        out = subprocess.run(
            ["nylas", "email", "search", query, "--json", "--limit", "20"],
            capture_output=True, text=True, check=True,
        )
        return out.stdout

How do you run it with automatic function calling?

Add the plugin to a kernel that has a chat model, enable automatic function-calling behavior, and invoke a prompt. The kernel decides which function to call, runs the subprocess, reads the JSON, and loops until it answers. A triage prompt like “list my unread mail and flag anything urgent” resolves in a few function calls, all reading structured output.

Automatic function calling is what makes the plugin feel like an agent: you don't hand-route calls, the kernel does. Your job is to give it well-described, single-purpose functions. The CLI keeps each one provider-neutral, so the same agent works whether the connected account is Gmail or Outlook.

from semantic_kernel import Kernel
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
from semantic_kernel.connectors.ai import FunctionChoiceBehavior

kernel = Kernel()
kernel.add_service(OpenAIChatCompletion(ai_model_id="gpt-4o"))
kernel.add_plugin(EmailPlugin(), plugin_name="email")

settings = kernel.get_prompt_execution_settings_from_service_id("default")
settings.function_choice_behavior = FunctionChoiceBehavior.Auto()
result = await kernel.invoke_prompt(
    "List my unread mail and flag anything urgent.", arguments=settings)

How do you keep sends safe?

Keep outbound actions behind a human. Expose a draft method that runs nylas email drafts create — composing a message without sending it — instead of a send method. A person reviews and sends, so a misclassification can't reach a customer. The need is concrete: Microsoft's own May 2026 Semantic Kernel security finding showed how a prompt injection can steer an agent's tool calls.

The agent reads untrusted content, so guardrails that sit outside its reasoning — a review checkpoint, or connector-level rules — are what actually hold. Injected text can argue with a system prompt, but it can't talk past a deterministic rule enforced before send. For that enforcement at the connector, see stopping a rogue agent at the connector layer.

Next steps