Source: https://cli.nylas.com/guides/provision-agent-accounts-at-scale

# Provision Agent Accounts at Scale

One Nylas application can hold hundreds of agent accounts, which makes per-tenant agent inboxes practical for a B2B product. The hard part isn't the API call — it's doing it repeatably: bulk-create from a source-of-truth list, skip accounts that already exist, apply the same guardrails to each, keep an inventory, and tear down on churn. This guide scripts the whole loop from the CLI, so a tenant signing up or leaving maps to one idempotent provisioning run.

Written by [Caleb Geene](https://cli.nylas.com/authors/caleb-geene) Director, Site Reliability Engineering

Updated June 7, 2026

> **TL;DR:** One application holds hundreds of agent accounts. Loop `nylas agent account create` over a tenant list, guard each create with `account get` for idempotency, inventory with `account list --json`, and tear down on churn with `account delete --yes`.

## What does provisioning agent accounts at scale mean?

Provisioning at scale means treating agent accounts as fleet infrastructure: a script — not a person — creates, configures, and retires them in bulk against a source-of-truth list. A single Nylas application can hold hundreds of agent accounts, so a B2B product can give every customer its own agent inbox. The work is repeatable CLI calls wrapped in the safety rails any provisioning system needs: idempotency, inventory, and teardown.

The pattern that makes this practical is one account per tenant. Each account is a separate grant with its own workspace, so tenant A's agent can't see tenant B's mail, and retiring a customer is a single deletion. The diagram below shows the fan-out: one application key at the top, one isolated account per tenant beneath it.

One application provisions many agent accounts, each with its own grant and workspaceOne applicationone API keytenant-agrant + workspacetenant-bgrant + workspacetenant-cgrant + workspaceone account per tenant, each created in under 2 seconds

## How do I bulk-create accounts from a tenant list?

Bulk creation is a loop over your tenant list calling [`nylas agent account create`](https://cli.nylas.com/docs/commands/agent-account-create). Each call returns in under 2 seconds, so provisioning 100 tenants finishes in a few minutes even run serially. Use a stable naming convention — the tenant slug in the [local part](https://datatracker.ietf.org/doc/html/rfc5322#section-3.4.1) of the address — so an address always maps back to a tenant:

```bash
#!/usr/bin/env bash
set -euo pipefail

# tenants.txt: one tenant slug per line
while IFS= read -r tenant || [ -n "$tenant" ]; do
  [ -z "$tenant" ] && continue
  nylas agent account create "support-${tenant}@yourapp.nylas.email"
done < tenants.txt
```

Keeping the tenant slug in the address is what makes the fleet self-describing: a glance at `support-acme@yourapp.nylas.email` tells you which customer it serves. Run the loop serially for predictable rate behavior, or background each call when you need to provision a large batch faster.

## How do I make provisioning idempotent?

A provisioning script reruns — on a new deploy, after a partial failure, on every tenant sync. It has to be safe to run twice, which means checking whether an account exists before creating it. Probe with `nylas agent account get` and skip the create when it succeeds:

```bash
while IFS= read -r tenant || [ -n "$tenant" ]; do
  [ -z "$tenant" ] && continue
  addr="support-${tenant}@yourapp.nylas.email"
  if nylas agent account get "$addr" --quiet >/dev/null 2>&1; then
    echo "skip: $addr already exists"
  else
    nylas agent account create "$addr"
    echo "created: $addr"
  fi
done < tenants.txt
```

The `get` probe returns non-zero when the account doesn't exist, so the `if` branch only creates what's missing. This converts the script from "create everything" — which errors on the second run — into "reconcile to the desired state," the property every provisioning system needs.

## How do I apply shared guardrails to every tenant?

Every tenant's agent should run under the same policy and rules: the same send cap, the same blocked domains, the same spam handling. Create the policy and rules once with `nylas agent policy create` and `nylas agent rule create`, then apply them as part of each tenant's provisioning step so the guardrails are identical across the fleet:

```bash
# Define the shared policy once (daily send cap, attachment limits)
nylas agent policy create --data '{
  "name": "Tenant agent baseline",
  "limits": {
    "limit_count_daily_message_per_grant": 500,
    "limit_attachment_count_limit": 10
  }
}'
```

Applying a policy and rules to each account is the per-account half of the workflow — the full attachment mechanism, including every policy setting and rule trigger, is in [Agent Rules and Policies](https://cli.nylas.com/guides/agent-rules-and-policies). The point at scale is consistency: the same baseline on every tenant means one place to change a limit, and a fleet that behaves predictably. Because rules live on the workspace, a tenant's agent can't prompt its way past them.

## How do I inventory the fleet?

You can't manage what you can't list. The `nylas agent account list --json` command is the fleet inventory — every account in the application as structured data. Pipe it through [jq](https://jqlang.github.io/jq/manual/) to count accounts, find broken ones, or reconcile against your tenant list:

```bash
# Total accounts
nylas agent account list --json | jq length

# Any account not healthy
nylas agent account list --json \
  | jq -r '.[] | select(.grant_status != "valid") | .email'

# Provisioned addresses vs the tenant list (find drift)
nylas agent account list --json | jq -r '.[].email' | sort > provisioned.txt
```

Diffing `provisioned.txt` against your expected list surfaces both orphans (accounts with no tenant) and gaps (tenants with no account). A nightly run of this check is how a fleet of 200 accounts stays consistent without anyone watching it. The per-account operations behind each entry are in the [agent account lifecycle guide](https://cli.nylas.com/guides/agent-account-lifecycle).

## How do I tear down a tenant's account?

Churn is part of the lifecycle: when a tenant leaves, its agent account should go with them. Deletion takes the account ID, which `get --quiet` resolves from the address, and `--yes` skips the prompt for unattended teardown:

```bash
offboard_tenant() {
  local tenant="${1:?usage: offboard_tenant <tenant>}"
  local addr="support-${tenant}@yourapp.nylas.email"
  if ! nylas agent account get "$addr" --quiet >/dev/null 2>&1; then
    echo "not found: $addr"; return 0
  fi
  local id; id=$(nylas agent account get "$addr" --quiet)
  nylas agent account delete "$id" --yes
  echo "deleted: $addr"
}

offboard_tenant acme
```

Wiring teardown into your tenant-offboarding flow guarantees a departed customer leaves no working mailbox behind — the grant is gone, so the address stops sending and receiving immediately. That clean revocation is the scale benefit of one account per tenant: removing a customer is one deletion, not an audit of shared access.

## One app, many accounts: what to know

A single application is the right container for a fleet because all accounts share one API key, one region, and one managed connector — the connector is auto-created on the first account and reused for the rest. The trade-off to design around is isolation versus sharing: one account per tenant maximizes isolation, while a smaller pool of shared accounts trades that for fewer identities to manage.

| Model | Isolation | Best for |
| --- | --- | --- |
| One account per tenant | **Strong — per-grant separation** | B2B products, per-customer inboxes |
| One account per agent role | Medium — separation by function | A few internal agents (support, billing) |
| Shared account | Low — one identity, many callers | Prototypes and test automation |

For per-customer inboxes specifically — addressing, routing replies, and keeping tenants separate — the [agent account architecture](https://cli.nylas.com/guides/getting-started-agent-accounts) explains how the grant and workspace give each tenant its own isolated identity. Provisioning at scale is just that model, multiplied and scripted.

## Next steps

- [Manage the Agent Account Lifecycle](https://cli.nylas.com/guides/agent-account-lifecycle) — the per-account create, inspect, rotate, pause, and delete commands this loops over
- [Getting Started with Agent Accounts](https://cli.nylas.com/guides/getting-started-agent-accounts) — the grant-and-workspace model behind per-tenant isolation
- [Agent Rules and Policies](https://cli.nylas.com/guides/agent-rules-and-policies) — the shared guardrails you apply across the fleet
- [Deploy Agent Accounts in Docker & CI](https://cli.nylas.com/guides/deploy-agent-accounts-docker) — run these provisioning commands headlessly from a container or pipeline
- [Best Email Infrastructure for AI Agents](https://cli.nylas.com/guides/best-email-infrastructure-ai-agents) — where agent accounts fit among other agent email backends
- [Full command reference](https://cli.nylas.com/docs/commands) — every `nylas agent account` flag and subcommand
- [Nylas v3 API documentation](https://developer.nylas.com/) — the grants and policies API behind these commands
