Guide

Build a Vellum Email Agent

Build a Vellum workflow that drafts email through a code/API node, calls the Nylas CLI with subprocess, and tracks each action in versioned workflow runs.

Written by Nick Barraclough Product Manager

VerifiedCLI 3.1.20 · Gmail · last tested June 14, 2026

Command references used in this guide: nylas auth config, nylas email list, and nylas email drafts create.

How does a Vellum Workflow give an agent email?

A Vellum Workflow gives an agent email by placing the mailbox action in a graph node, not inside the prompt. That split matters because a 5-node workflow can let one prompt decide intent while a separate Code Execution Node or API Node performs exactly one side effect.

In Vellum's docs, Workflows are graphs made from nodes such as prompt nodes, API nodes, Code Execution Nodes, Conditional Nodes, and Final Output Nodes. Email belongs in the code/API layer because the workflow needs a controlled process boundary, a clear stdout/stderr record, and a typed output for the next node.

The pattern is simple: a prompt node prepares recipient, subject, and body; an approval or guardrail node checks the payload; then a code/API node runs the CLI to create a draft. That keeps one email draft tied to one node execution and one workflow run.

How do you set up CLI credentials in Vellum?

Vellum Workflow settings should hold the email runtime credentials as environment variables, so every run uses the same approved account boundary. Use 2 values at minimum: a Nylas API key secret and a configured sender identity already connected to the CLI runtime.

Put NYLAS_API_KEY in Vellum's environment settings for the workflow or deployment, not in a prompt node. If your workflow runs in a custom container, configure the CLI before the draft node executes. Keep the sender address as a normal workflow input only when the account is fixed by policy.

The nylas auth config --api-key command writes API credentials into the CLI config for the runtime. Run it from a setup Code Execution Node or container entrypoint because it is a one-time credential step per runtime image. The example uses 1 secret from Vellum Workflow settings and avoids passing keys through prompt text.

import os
import subprocess

def configure_cli() -> None:
    subprocess.run(
        ["nylas", "auth", "config", "--api-key", os.environ["NYLAS_API_KEY"]],
        capture_output=True,
        text=True,
        check=True,
    )

How does the Vellum email node call the CLI?

The Vellum email node should expose a tiny function: receive approved fields, call the CLI once, and return a compact result. That 1-call shape makes the node easy to mock in Vellum tests and easy to inspect in the workflow run log after deployment.

Use nylas email drafts create after the workflow has produced an approved recipient, subject, and body. The command below prepares 1 draft and uses only --to, --subject, and --body, which keeps the subprocess interface small. A person opens the draft in their mail client and sends it, so a bad workflow decision never reaches the recipient unattended.

import subprocess

def draft_reply(to: str, subject: str, body: str) -> dict:
    result = subprocess.run(
        [
            "nylas",
            "email",
            "drafts",
            "create",
            "--to",
            to,
            "--subject",
            subject,
            "--body",
            body,
        ],
        capture_output=True,
        text=True,
        check=True,
    )
    return {
        "status": "drafted",
        "stdout": result.stdout,
    }

If your Vellum deployment cannot run local binaries, swap the Code Execution Node for an API Node that calls an internal service. The service should run the same subprocess and return the same JSON shape, so the rest of the workflow stays unchanged across 2 deployment models.

How does Vellum observability track email actions?

Vellum observability tracks the email action at the workflow-run level: the draft node has inputs, outputs, timestamps, and any error text captured with the node execution. That means 1 production draft can be traced to a workflow deployment, release tag, and prompt version.

Return a small structured object from the code/API node instead of raw prose. Include an application-level action_id, the recipient domain, and the CLI stdout. Avoid storing full message bodies in logs unless your data policy permits it; a 20-word summary is enough for most audit views.

For pre-draft validation, add a read-only check node that runs nylas email list. The command below fetches 10 recent messages as JSON so Vellum can record the mailbox context used by the decision node. It uses --limit and --json, which are enough for a compact run record.

import subprocess

def recent_context() -> str:
    result = subprocess.run(
        ["nylas", "email", "list", "--limit", "10", "--json"],
        capture_output=True,
        text=True,
        check=True,
    )
    return result.stdout

How do you trigger the workflow from the Vellum SDK?

Trigger the deployed Vellum Workflow from your application, not from the email node itself. The app sends 3 or 4 inputs to the workflow deployment, and Vellum records the execution ID so support can find the exact run that prepared the draft.

The Vellum Client SDK and API accept workflow inputs plus a deployment name or ID. Keep the email fields as plain strings, and pass a separate tracking key from your app. Use a release tag when you want a production workflow to stay pinned while a new graph version is tested.

The SDK trigger below sends 4 inputs into a deployed workflow named email_action. The workflow, not the caller, decides whether to continue to the draft node. That gives Vellum one run log for the prompt, approval branch, code/API node, and final output.

import os
from vellum.client import Vellum

client = Vellum(api_key=os.environ["VELLUM_API_KEY"])

execution = client.execute_workflow(
    workflow_deployment_name="email_action",
    release_tag="production",
    external_id="ticket-1842",
    inputs=[
        {"name": "to", "type": "STRING", "value": "customer@example.com"},
        {"name": "subject", "type": "STRING", "value": "Your account update"},
        {"name": "body", "type": "STRING", "value": "Thanks for your note. Here is the update."},
        {"name": "requested_by", "type": "STRING", "value": "support-agent"},
    ],
)

print(execution.execution_id)

What guardrails should the Vellum workflow have?

A Vellum email workflow needs guardrails because email combines private data, model-readable user content, and external communication. That lethal trifecta can turn 1 hostile message into an outbound action unless the graph separates reading, deciding, approval, and drafting.

Treat every email body as untrusted input. A prompt node can summarize or classify it, but it should not convert quoted instructions into workflow control decisions. Prompt injection is OWASP LLM01 in the 2025 OWASP LLM Top 10, and mailbox content is a common delivery path.

Put a Guardrail Node, Conditional Node, or human review step before the draft node. Require exact recipients, block domains outside policy, and cap each workflow run to 1 draft action. The draft itself is the review queue: a person inspects the proposed body in their mail client and decides whether to send it.

Next steps