Source: https://cli.nylas.com/guides/agent-account-contacts

# Give Your AI Agent a Contacts Directory

An AI agent that sends email needs to know who it's writing to. The agent account that carries the agent's email and calendar also carries contacts — a directory the agent owns and queries from the CLI. This guide adds contacts, resolves a name to an address before a send, groups recipients for batch outreach, dedupes the list, and turns the directory into the source of truth for who the agent is allowed to contact. One grant, no separate CRM integration.

Written by [Nick Barraclough](https://cli.nylas.com/authors/nick-barraclough) Product Manager

Updated June 7, 2026

> **TL;DR:** An agent account's grant carries contacts alongside email and calendar. Add people with `nylas contacts create`, resolve a name to an address with `contacts search`, group recipients with `contacts groups`, and use the directory as the allow-list of who the agent may email.

## Why does an AI agent need its own contacts directory?

An agent that sends email has to answer a question on every message: what address does this name map to? A contacts directory on the agent account answers it. The grant that holds the agent's inbox and calendar also holds a contacts store the agent reads and writes from the CLI — no separate CRM API, no second set of credentials, no provider-specific contact schema to parse.

Owning the directory matters for the same reason owning the inbox does. The agent's contacts are scoped to the agent's grant, so deleting the grant removes them, and the directory becomes a clean source of truth for who the agent is supposed to contact. That last point turns contacts into a guardrail, not just a convenience — the directory defines the allow-list the agent works within.

Agent contacts directory feeds three jobs: resolve a name to an address, group recipients, and check an allow-listContacts directoryon the agent account grantResolvename → addressGrouprecipient listsGuardallow-list source

## How do I add contacts to the agent's directory?

Populate the directory with `nylas contacts create`. The command writes a structured contact to the agent's grant with separate fields for first name, last name, email, company, job title, and phone — the shape that downstream lookups and personalization depend on. Splitting the name into `--first-name` and `--last-name` is what lets a later greeting say "Hi Jane" without string-splitting a full name.

```bash
nylas contacts create \
  --first-name "Jane" \
  --last-name "Smith" \
  --email "jane@acme.com" \
  --company "Acme" \
  --job-title "VP Operations"
```

For bulk loading, drive the same command from a shell loop over a CSV — the agent's directory fills in seconds rather than through a paginated web UI one record at a time. The fields follow the [vCard standard (RFC 6350)](https://datatracker.ietf.org/doc/html/rfc6350), so an export from another address book maps cleanly onto the create flags.

## How does the agent resolve a name to an email address?

Before a send, the agent turns a name into an address with `nylas contacts search`. The command filters on real fields — `--query`, `--company`, `--email`, and `--has-email` — and returns up to 50 matches by default. Resolving the address from the directory keeps a language model from guessing an email that bounces.

```bash
nylas contacts search --query "Jane Smith" --has-email --json \
  | jq -re '.[0].emails[0].email // empty'
```

The [`-r`](https://jqlang.github.io/jq/manual/) flag emits the raw address (not a quoted JSON string) and `-e` makes jq exit non-zero when there's no match, so the agent fails loudly on an unknown name instead of piping an empty string into a send. This is the deterministic guard between the model's intent ("email Jane") and the actual address — the lookup is code, not a guess. Pass `--company "Acme"` when two people share a name and you need to disambiguate.

## How does the agent group recipients?

For batch outreach, the agent organizes contacts with `nylas contacts groups`. Create a group, then query its members with the `--group` filter on search. A group is a stable handle — "Q3 webinar invitees" — that the agent resolves to a current recipient list at send time, instead of hard-coding addresses in a script.

```bash
nylas contacts groups list --json | jq '.[] | {id, name}'

nylas contacts search --group grp_abc123 --has-email --json \
  | jq -r '.[].emails[0].email'
```

Piping the group's addresses into a send loop is how the agent runs a campaign without a marketing tool. Each address comes from the directory, so the recipient list reflects the current group membership — add a contact to the group today and tomorrow's run picks it up. For the personalization layer on top of this, see [personalizing outbound email from the CLI](https://cli.nylas.com/guides/personalize-outbound-email-cli).

## How does the agent keep the directory clean?

A directory the agent writes to drifts toward duplicates, so dedup is a maintenance job worth scripting. Pull a page with `nylas contacts list --json` and group by email to find repeats. The `--limit` flag raises the page size so a single scan covers up to 500 contacts — directories larger than that need pagination across multiple calls. The filter drops contacts with no email before grouping, since a null key isn't a real duplicate:

```bash
nylas contacts list --json --limit 500 \
  | jq '[ .[] | select((.emails | length) > 0) ]
       | [group_by(.emails[0].email)[] | select(length > 1)
       | {email: .[0].emails[0].email, count: length}]'
```

Catching duplicates before a batch send is what stops the agent from emailing the same person twice in one run. Run the scan on a schedule and the directory stays trustworthy as the source of recipient lists. The same `--json` output drives a CSV export with jq's `@csv` filter when you need the directory outside the agent — see [managing contacts from the terminal](https://cli.nylas.com/guides/manage-contacts-from-terminal) for the export and update patterns.

## How does the directory power an allow-list guardrail?

The directory's second job is containment. The contacts store is the allow-list the agent works within, and an outbound rule on the agent's workspace enforces a hard boundary around it. No rule primitive checks contact membership directly — rules match on envelope fields like the recipient domain — so the rule below enforces a company-domain boundary, and your send loop reconciles individual recipients against the directory. The rule is evaluated before the message reaches the send pipeline, so a prompt injection can't talk the agent past it — the constraint lives outside the agent's decision loop.

```bash
# Block any outbound mail to a recipient outside the company domain
nylas agent rule create \
  --name "Restrict outbound to company domain" \
  --trigger outbound \
  --condition recipient.domain,is_not,acme.com \
  --action block
```

That single-domain rule fits an internal agent. For an agent that contacts customers across many domains, derive the allowed set from the directory — extract recipient domains from `contacts list --json` and reconcile sends against it in your loop, then layer the workspace rules for the hard stops. The full set of triggers, conditions, and actions is in [Agent Rules and Policies](https://cli.nylas.com/guides/agent-rules-and-policies), and the containment pattern is in [Stop Your AI Agent From Going Rogue](https://cli.nylas.com/guides/stop-ai-agent-going-rogue).

## Contacts directory vs a CRM or database

An agent-account contacts directory isn't a CRM replacement — it's the working set the agent needs at send time. The difference is scope: the directory answers "who is this and may I email them," while a CRM tracks deals, history, and reporting. For most agents the directory is enough, and it ships on the grant the agent already has.

| Concern | External CRM / database | Agent account contacts |
| --- | --- | --- |
| Setup | Separate API keys and schema | **On the agent's existing grant** |
| Lookup | SQL or a vendor SDK | **One `contacts search` call** |
| Scope | Org-wide system of record | **Scoped to one agent grant** |
| Teardown | Delete rows, manage retention | **Delete the grant** |

When the agent does need deal history, export the directory and sync it outward — the `--json` output feeds any CRM import. Both the directory and the connected providers speak the same [Nylas v3 Contacts API](https://developer.nylas.com/docs/v3/contacts/), so a script written against one works against the other.

## Next steps

- [Getting Started with Agent Accounts](https://cli.nylas.com/guides/getting-started-agent-accounts) — the architecture that puts email, calendar, and contacts on one grant
- [Give Your AI Agent a Managed Calendar](https://cli.nylas.com/guides/agent-account-calendar) — the calendar capability on the same grant: availability, find-time, and booking
- [Manage Contacts from the Terminal](https://cli.nylas.com/guides/manage-contacts-from-terminal) — CSV export, bulk update, and provider-specific contact fields in depth
- [Build a Lead-Capture & Qualification Agent](https://cli.nylas.com/guides/lead-capture-agent-account) — an agent that saves qualified inbound leads straight into this directory
- [Build an Email Digest Agent](https://cli.nylas.com/guides/newsletter-digest-agent-account) — resolve a subscriber group from this directory and send a scheduled digest
- [Personalize Outbound Email from the CLI](https://cli.nylas.com/guides/personalize-outbound-email-cli) — merge contact fields into templated sends
- [Agent Rules and Policies](https://cli.nylas.com/guides/agent-rules-and-policies) — the outbound rules that enforce the directory as an allow-list
- [Full command reference](https://cli.nylas.com/docs/commands) — every `nylas contacts` and `nylas agent` flag and subcommand
- [Nylas v3 Contacts API documentation](https://developer.nylas.com/docs/v3/contacts/) — the API surface behind these commands
