# Policies, Rules, and Lists

Source: https://developer.nylas.com/docs/v3/agent-accounts/policies-rules-lists/

Agent Accounts ship with three admin-scoped resources that control how mail is handled on the account level:

- **Policies** bundle limits and spam settings. One policy can govern many Agent Accounts.
- **Rules** match inbound mail and outbound sends using sender fields, recipient fields, and `outbound.type`, then run actions like `block`, `mark_as_spam`, `assign_to_folder`, `archive`, or `trash`.
- **Lists** are typed collections of domains, TLDs, or addresses that rules reference through the `in_list` operator.

**Workspaces carry policies and rules.** You don't attach a policy or rule to an individual grant. Instead, you set `policy_id` and `rule_ids` on a [workspace](/docs/reference/api/workspaces/), and they apply to every Agent Account in that workspace. Each application has a default workspace that holds any Agent Account you haven't placed in a custom one, so attaching a policy there configures all of your unassigned accounts at once.

All three resources are application-scoped: they have no grant ID in the path, and your API key identifies the application.

You can inspect existing policies and rules from the [Nylas CLI](/docs/v3/getting-started/cli/) with `nylas agent policy list` and `nylas agent rule list`. Creation and updates currently go through the API.

## When to use them

Policies, Rules, and Lists are **optional**. Without a workspace policy, an Agent Account runs at the maximum limits your billing plan allows for send rate, storage, and retention — you only need these resources when you want stricter behavior or mail filtering. Reach for them when you need to:

- **Customize limits per agent or customer.** Different send quotas, storage caps, or retention windows for different Agent Accounts — for example, stricter limits on prototype accounts, higher quotas on a production sales agent.
- **Tune spam handling.** Enable DNSBL checks and header anomaly detection, or turn the sensitivity dial up or down for a particular class of agent.
- **Reject known-bad senders at SMTP.** A `block` rule rejects the message before it's ever delivered to the mailbox, so your application never sees it.
- **Route inbound mail automatically.** Move newsletters to a folder, auto-star mail from key customers, archive or trash noise without writing any application code.
- **Stop sends to the wrong recipients.** An outbound `block` rule rejects a send before it reaches the email provider — useful for data-loss prevention, catching test domains that slipped into production, or keeping an agent from emailing competitors.
- **Organize the sent side automatically.** Archive or auto-star outbound mail based on recipient domain so the sent copy lands where you expect.
- **Maintain dynamic allow/block lists.** A List fronted by a Rule lets non-engineers update who's allowed or blocked without touching rule definitions or redeploying anything.

If none of these apply, skip this page — your Agent Accounts run at your plan's maximums and deliver every inbound message to the inbox.

## How the pieces fit together

The pieces form a chain. **Lists** hold values. **Rules** reference lists (via the `in_list` operator) and describe conditions and actions. **Policies** bundle limits and spam settings. A **workspace** carries one `policy_id` plus an array of `rule_ids`, and every Agent Account in the workspace inherits both.

| Resource | What it owns | How it's referenced |
| --- | --- | --- |
| List | A typed collection of domains, TLDs, or email addresses | By ID, from a rule condition's `value` (when `operator` is `in_list`) |
| Rule | A `trigger` (`inbound` or `outbound`), match conditions, and actions (`block`, `mark_as_spam`, `assign_to_folder`, and more) | By ID, in a workspace's `rule_ids` array. Nylas filters by `trigger` at evaluation time, so one array carries both directions. |
| Policy | Limits and spam detection settings | By ID, in a workspace's `policy_id` |
| Workspace | The `policy_id` and `rule_ids` that govern its Agent Accounts | By ID, in a grant's `workspace_id` |
| Agent Account | The grant itself | Carries `workspace_id`; set it on `POST /v3/connect/custom` or change it with `PATCH /v3/grants/{grant_id}` |

When a message arrives for an Agent Account, Nylas resolves the grant's workspace, applies the workspace policy's limits and spam settings, and evaluates the workspace's **inbound** rules in priority order (lowest number first). When the account issues a send, Nylas evaluates the workspace's **outbound** rules before handing the message to the provider. If the workspace has no policy attached, the account runs at your billing plan's maximum limits. Inbound and outbound rules are isolated: inbound rules never run during sends, and outbound rules never run on receipt, so the stored sent copy isn't re-evaluated against inbound rules. Limits on the policy govern what the account can send, receive, and store. Attachment limits (size, count, and allowed types) apply to inbound mail only — over-limit attachments are dropped from the stored message — and are never applied to the stored sent copy, so sent copies always keep all of their attachments.

## Policies

A policy is the configuration you reuse across many Agent Accounts. It contains:

- **Limits** — attachment size and count, allowed MIME types, total message size, per-account storage, daily send quotas, and inbox/spam retention.
- **Spam detection** — DNSBL checking, header anomaly detection, and a `spam_sensitivity` dial (0.1–5.0; higher is more aggressive).

Every limit is optional. If you omit one, it defaults to your plan's maximum. If you request a value above the plan maximum, the API returns an error.

### Configurable limits

Each limit lives in the policy's `limits` object and is enforced per Agent Account. Inbound caps apply to received mail, the send cap to outbound, and storage and retention to what's kept. Where a plan permits no limit, a field accepts `-1` for unlimited.

| Group               | Limit                   | Field                                | What it controls                                                                |
| ------------------- | ----------------------- | ------------------------------------ | ------------------------------------------------------------------------------- |
| Inbound             | Daily messages received | `limit_count_daily_message_received` | Messages the account can receive per day.                                       |
| Inbound             | Max attachment size     | `limit_attachment_size_limit`        | Size of a single attachment, in bytes.                                          |
| Inbound             | Max attachment count    | `limit_attachment_count_limit`       | Attachments allowed on one message.                                             |
| Inbound             | Max message size        | `limit_size_total_mime`              | Total MIME size of a message, including attachments, in bytes.                  |
| Inbound             | Attachment types        | `limit_attachment_allowed_types`     | Allowed attachment MIME types (array; empty means every type your plan permits). |
| Outbound            | Daily emails sent       | `limit_count_daily_email_sent`       | Messages the account can send per day.                                          |
| Storage & retention | Storage cap             | `limit_storage_total`                | Total stored bytes per account.                                                 |
| Storage & retention | Inbox retention         | `limit_inbox_retention_period`       | Days a message stays in the inbox before deletion.                              |
| Storage & retention | Spam retention          | `limit_spam_retention_period`        | Days a message stays in spam before deletion. Must be shorter than inbox retention. |

### Create a policy

```bash
curl --request POST \
  --url "https://api.us.nylas.com/v3/policies" \
  --header "Authorization: Bearer <NYLAS_API_KEY>" \
  --header "Content-Type: application/json" \
  --data '{
    "name": "Standard Agent Account Policy",
    "limits": {
      "limit_attachment_size_limit": 26214400,
      "limit_attachment_count_limit": 20,
      "limit_inbox_retention_period": 365,
      "limit_spam_retention_period": 30
    },
    "spam_detection": {
      "use_list_dnsbl": true,
      "use_header_anomaly_detection": true,
      "spam_sensitivity": 1.5
    }
  }'
```

```json [createPolicy-Response (JSON)]
{
  "request_id": "5fa64c92-e840-4357-86b9-2aa364d35b88",
  "data": {
    "id": "b1c2d3e4-5678-4abc-9def-0123456789ab",
    "name": "Standard Agent Account Policy",
    "limits": {
      "limit_attachment_size_limit": 26214400,
      "limit_attachment_count_limit": 20,
      "limit_inbox_retention_period": 365,
      "limit_spam_retention_period": 30
    },
    "spam_detection": {
      "use_list_dnsbl": true,
      "use_header_anomaly_detection": true,
      "spam_sensitivity": 1.5
    },
    "rules": [],
    "created_at": 1742932766,
    "updated_at": 1742932766
  }
}
```

### Attach a policy to a workspace

Attaching a policy to a [workspace](/docs/reference/api/workspaces/) is the only way to apply it. Set `policy_id` (and optionally `rule_ids`) when you create or update a workspace, and every Agent Account in that workspace picks up the policy's limits and spam settings. With `auto_group: true`, new Agent Accounts whose email domain matches the workspace's `domain` join it automatically.

```bash
curl --request POST \
  --url "https://api.us.nylas.com/v3/workspaces" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>' \
  --header 'Content-Type: application/json' \
  --data '{
    "name": "The Nylas Workspace",
    "domain": "nylas.com",
    "auto_group": true,
    "policy_id": "<POLICY_ID>",
    "rule_ids": ["<RULE_ID>"]
  }'


```

To change a workspace's policy later, send `PATCH /v3/workspaces/{workspace_id}` with the new `policy_id`. On the application's default workspace, `policy_id` and `rule_ids` are the only fields you can update; Nylas manages the rest.

### The default workspace

Every application has one default workspace that Nylas creates and manages. An Agent Account created without an explicit `workspace_id`, and not auto-grouped by domain, lands in the default workspace. Attach your policy and rules there to cover every unassigned account in one place. A workspace with no `policy_id` runs its accounts at your billing plan's maximum limits.

### Move an Agent Account to a different workspace

Set `workspace_id` when you create the grant on `POST /v3/connect/custom`, or move an existing account out of the default workspace by updating the grant. The target workspace must belong to the same application; Nylas validates ownership and rejects the update otherwise.

```bash
curl --request PATCH \
  --url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>" \
  --header "Authorization: Bearer <NYLAS_API_KEY>" \
  --header "Content-Type: application/json" \
  --data '{
    "workspace_id": "<WORKSPACE_ID>"
  }'
```

For the complete policy schema, see the [Policies API reference](/docs/reference/api/policies/).

## Rules

A rule decides what to do with a message — either on the way in, or on the way out. It has:

- **A `trigger`** of `inbound` (runs on received mail) or `outbound` (runs on sends before they reach the email provider). Defaults to `inbound`.
- **Match conditions** on fields valid for that trigger, combined with operators `is`, `is_not`, `contains`, or `in_list`.
- **An operator** (`all` or `any`) for combining multiple conditions with AND or OR. If you omit it, the rule defaults to `all`.
- **Actions** that run when the match hits: `block`, `mark_as_spam`, `assign_to_folder`, `mark_as_read`, `mark_as_starred`, `archive`, or `trash`.

Rules run in `priority` order (lower numbers first; range 0–1000, default 10). The `block` action is terminal and can't be combined with other actions — for inbound rules it rejects the message at the SMTP level, for outbound rules it rejects the send with HTTP 403 before it reaches the provider.

Rule evaluation **fails closed**: if a `block` rule can't be evaluated because of a transient infrastructure error (for example, a list lookup failure during `in_list` matching), Nylas blocks the message rather than letting it through. These fail-closed blocks are surfaced as retryable errors — an API send returns `503` instead of `403`, and inbound SMTP responds with a `451` tempfail so the sending server retries instead of bouncing. The corresponding [rule-evaluation audit record](#audit-which-rules-ran) carries `blocked_by_evaluation_error: true` so you can distinguish an infrastructure failure from a genuine rule match.

### Condition fields

Which fields you can match depends on the trigger. Inbound rules accept only `from.*`. Outbound rules can match `from.*`, `recipient.*`, and `outbound.type`.

| Field             | Trigger  | What it matches                                                      | Valid operators                   |
| ----------------- | -------- | -------------------------------------------------------------------- | --------------------------------- |
| `from.address`    | inbound, outbound | The full sender email address                                 | `is`, `is_not`, `contains`, `in_list` |
| `from.domain`     | inbound, outbound | The sender's domain                                           | `is`, `is_not`, `contains`, `in_list` |
| `from.tld`        | inbound, outbound | The sender's top-level domain                                 | `is`, `is_not`, `contains`, `in_list` |
| `recipient.address` | outbound | Any recipient address — To, CC, BCC, and SMTP envelope recipients | `is`, `is_not`, `contains`, `in_list` |
| `recipient.domain`  | outbound | The domain of any recipient                                        | `is`, `is_not`, `contains`, `in_list` |
| `recipient.tld`     | outbound | The top-level domain of any recipient                              | `is`, `is_not`, `contains`, `in_list` |
| `outbound.type`     | outbound | `compose` for brand-new messages, `reply` for replies to existing threads | `is`, `is_not` only     |

`recipient.*` fields match against **any** recipient, which includes BCC and SMTP envelope recipients — useful when you want DLP coverage that catches hidden recipients. `is_not` is only true when **no** recipient matches the value. String matching is case-insensitive.

`outbound.type` is classified as `reply` when the send request includes `reply_to_message_id`, or when the raw MIME contains `In-Reply-To` or `References` headers. Everything else is `compose`. Values are normalized to lowercase on write.

### Block a known spam domain

```bash
curl --request POST \
  --url "https://api.us.nylas.com/v3/rules" \
  --header "Authorization: Bearer <NYLAS_API_KEY>" \
  --header "Content-Type: application/json" \
  --data '{
    "name": "Block spam-domain.com",
    "priority": 1,
    "trigger": "inbound",
    "match": {
      "conditions": [
        { "field": "from.domain", "operator": "is", "value": "spam-domain.com" }
      ]
    },
    "actions": [
      { "type": "block" }
    ]
  }'
```

```json [createBlockRule-Response (JSON)]
{
  "request_id": "5fa64c92-e840-4357-86b9-2aa364d35b88",
  "data": {
    "id": "c1d2e3f4-5678-4abc-9def-0123456789ab",
    "name": "Block spam-domain.com",
    "priority": 1,
    "enabled": true,
    "trigger": "inbound",
    "match": {
      "operator": "all",
      "conditions": [
        { "field": "from.domain", "operator": "is", "value": "spam-domain.com" }
      ]
    },
    "actions": [{ "type": "block" }],
    "created_at": 1742932766,
    "updated_at": 1742932766
  }
}
```

### Activate the rule on a workspace

A rule does nothing until a workspace references it. Add the rule's ID to the workspace's `rule_ids` array, and it runs for every Agent Account in that workspace. One array carries inbound and outbound rules together; Nylas filters by `trigger` at evaluation time.

```bash
curl --request PATCH \
  --url "https://api.us.nylas.com/v3/workspaces/<WORKSPACE_ID>" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>' \
  --header 'Content-Type: application/json' \
  --data '{
    "policy_id": "<POLICY_ID>",
    "rule_ids": ["<RULE_ID>"]
  }'


```

### Route newsletters to a folder

Combine multiple conditions with `operator: "any"` (OR) and pair actions — an `assign_to_folder` action with a `mark_as_read` action.

```bash
curl --request POST \
  --url "https://api.us.nylas.com/v3/rules" \
  --header "Authorization: Bearer <NYLAS_API_KEY>" \
  --header "Content-Type: application/json" \
  --data '{
    "name": "Newsletters → Reading folder",
    "trigger": "inbound",
    "match": {
      "operator": "any",
      "conditions": [
        { "field": "from.address", "operator": "contains", "value": "newsletter@" },
        { "field": "from.domain", "operator": "contains", "value": "substack.com" }
      ]
    },
    "actions": [
      { "type": "assign_to_folder", "value": "<READING_FOLDER_ID>" },
      { "type": "mark_as_read" }
    ]
  }'
```

### Outbound rules

Outbound rules run when the Agent Account issues a send. They evaluate before the message is handed off to the provider, so a `block` action short-circuits delivery entirely — the API returns `403` and no sent copy is created. Non-blocking actions (`mark_as_spam`, `archive`, `mark_as_read`, `mark_as_starred`, `assign_to_folder`, `trash`) apply to the stored sent copy once the send succeeds, not to what the recipient receives.

> **Info:** 
> **Outbound rule scope.** Send-time evaluation loads the outbound rules referenced by the sending Agent Account's workspace, from the same `rule_ids` array that carries inbound rules. Nylas filters the array by `trigger`, so a rule with `trigger: outbound` never runs on received mail.

#### Block outbound mail to a domain

Useful for data-loss prevention, test domains, or hard-coded competitor blocks.

```bash
curl --request POST \
  --url "https://api.us.nylas.com/v3/rules" \
  --header "Authorization: Bearer <NYLAS_API_KEY>" \
  --header "Content-Type: application/json" \
  --data '{
    "name": "Block outbound to example.net",
    "trigger": "outbound",
    "match": {
      "conditions": [
        { "field": "recipient.domain", "operator": "is", "value": "example.net" }
      ]
    },
    "actions": [
      { "type": "block" }
    ]
  }'
```

#### Archive sent copies to a specific vendor

Keeps automated correspondence out of the main sent folder by archiving and marking the sent copy read.

```bash
curl --request POST \
  --url "https://api.us.nylas.com/v3/rules" \
  --header "Authorization: Bearer <NYLAS_API_KEY>" \
  --header "Content-Type: application/json" \
  --data '{
    "name": "Archive sent copies to vendor domain",
    "trigger": "outbound",
    "match": {
      "conditions": [
        { "field": "recipient.domain", "operator": "is", "value": "vendor.example" }
      ]
    },
    "actions": [
      { "type": "archive" },
      { "type": "mark_as_read" }
    ]
  }'
```

#### Match only replies with `outbound.type`

Narrow an outbound rule to replies, or to brand-new messages, without looking at recipients at all. The `outbound.type` field takes one of two values: `reply` when the send is part of an existing thread, or `compose` when it's a brand-new message.

```bash
curl --request POST \
  --url "https://api.us.nylas.com/v3/rules" \
  --header "Authorization: Bearer <NYLAS_API_KEY>" \
  --header "Content-Type: application/json" \
  --data '{
    "name": "Star outbound replies",
    "trigger": "outbound",
    "match": {
      "conditions": [
        { "field": "outbound.type", "operator": "is", "value": "reply" }
      ]
    },
    "actions": [
      { "type": "mark_as_starred" }
    ]
  }'
```

A send is classified as a `reply` when the API request includes `reply_to_message_id` or the raw MIME carries `In-Reply-To` or `References`. Everything else is `compose`. `outbound.type` supports only `is` and `is_not` — `contains` and `in_list` are rejected.

> **Info:** 
> **Outbound `block` returns `403` to the caller.** Your application should treat that response the same as any other delivery failure — the send never reached the provider, no retries will deliver it, and no sent copy is stored. Check `GET /v3/grants/{grant_id}/rule-evaluations` to see which outbound rule matched.

### Rule limits

Rules have fixed caps. Requests that exceed any of these are rejected with a validation error:

- **50** conditions per rule.
- **20** actions per rule.
- **10** lists referenced per `in_list` condition.
- **500** characters per condition `value` string.

For the full rule schema, see the [Rules API reference](/docs/reference/api/rules/).

## Lists

A list is a typed collection of values that rules match against via the `in_list` operator. Use lists when a rule's allow/block values change over time — update the list and every rule that references it picks up the new values immediately.

Each list has a fixed `type`, set at creation and immutable:

| Type | Values it holds | Rule field it matches |
| --- | --- | --- |
| `domain` | Domain names (`example.com`) | `from.domain`, `recipient.domain` |
| `tld` | Top-level domains (`com`, `xyz`) | `from.tld`, `recipient.tld` |
| `address` | Full email addresses (`user@example.com`) | `from.address`, `recipient.address` |

Values are lowercased and trimmed on write, and validated against the list's `type`. For example, a `domain` list rejects full email addresses. Duplicate additions are silently ignored.

### Create a list

```bash
curl --request POST \
  --url "https://api.us.nylas.com/v3/lists" \
  --header "Authorization: Bearer <NYLAS_API_KEY>" \
  --header "Content-Type: application/json" \
  --data '{
    "name": "Blocked domains",
    "type": "domain"
  }'
```

```json [createList-Response (JSON)]
{
  "request_id": "5fa64c92-e840-4357-86b9-2aa364d35b88",
  "data": {
    "id": "d1e2f3a4-5678-4abc-9def-0123456789ab",
    "name": "Blocked domains",
    "type": "domain",
    "items_count": 0,
    "created_at": 1742932766,
    "updated_at": 1742932766
  }
}
```

### Add items to a list

Add up to 1000 items per request.

```bash
curl --request POST \
  --url "https://api.us.nylas.com/v3/lists/<LIST_ID>/items" \
  --header "Authorization: Bearer <NYLAS_API_KEY>" \
  --header "Content-Type: application/json" \
  --data '{
    "items": ["spam-domain.com", "another-bad-domain.net"]
  }'
```

### Reference a list from a rule

Use `"operator": "in_list"` and pass one or more List IDs as `value`.

```bash
curl --request POST \
  --url "https://api.us.nylas.com/v3/rules" \
  --header "Authorization: Bearer <NYLAS_API_KEY>" \
  --header "Content-Type: application/json" \
  --data '{
    "name": "Block anything on our blocklist",
    "trigger": "inbound",
    "match": {
      "conditions": [
        {
          "field": "from.domain",
          "operator": "in_list",
          "value": ["<LIST_ID>"]
        }
      ]
    },
    "actions": [
      { "type": "block" }
    ]
  }'
```

Deleting a list cascades to its items, and rules that reference the list through `in_list` stop matching its values after deletion. For the full list schema, see the [Lists API reference](/docs/reference/api/lists/).

## Audit which rules ran

Every time the rule engine evaluates an inbound message, SMTP envelope, or outbound send for an Agent Account, Nylas records an audit entry. Use [`GET /v3/grants/{grant_id}/rule-evaluations`](/docs/reference/api/rules/list-rule-evaluations/) to list those entries, most recent first — it's the fastest way to answer "why did this message get blocked / routed / marked?" for a specific grant.

```bash
curl --request GET \
  --url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/rule-evaluations?limit=50" \
  --header "Authorization: Bearer <NYLAS_API_KEY>"
```

Each record includes the evaluation stage (`smtp_rcpt` if the message was rejected before acceptance, `inbox_processing` if it was evaluated post-acceptance, or `outbound_send` for send-time evaluation), the normalized sender or recipient data that was considered (`from_address`, `from_domain`, `from_tld`, `recipient_addresses`, `recipient_domains`, `recipient_tlds`, and `outbound_type`), the IDs of any matched rules, and the actions that were applied (`blocked`, `marked_as_spam`, `archived`, `folder_ids`, and so on). When a block was applied fail-closed because rule evaluation errored rather than matched, the record also carries `blocked_by_evaluation_error: true`. For `outbound_send`, `message_id` is populated when a sent copy was stored; it is `null` when the send was blocked before storage or when no sent copy was persisted. Cross-reference `matched_rule_ids` with the Rules API to see the conditions each matching rule was built from.

## Keep in mind

- **Order rules carefully.** Lower `priority` runs first, and the first matching `block` action is terminal. Put specific rules (`is`, `in_list` against a small list) before broad ones (`contains`).
- **Pick the right `trigger`.** `inbound` rules only see received mail; `outbound` rules only see sends. Don't try to use inbound rules to filter what the agent sends, or vice versa — they're isolated on purpose.
- **Handle `403` from sends.** An outbound `block` rule returns `403` and no sent copy is stored. Your application should treat it like any delivery failure — there's no retry path that will deliver it.
- **Outbound actions modify the sent copy.** `mark_as_spam`, `archive`, `mark_as_read`, `assign_to_folder`, and friends affect only what's stored in the agent's sent folder. They don't change what the recipient receives.
- **Prefer lists over inline values** when the set is likely to grow. One list can feed many rules and be updated without touching rule definitions.
- **Start with `spam_sensitivity: 1.0`** and tune from there. Go up if spam is slipping through; go down if legitimate mail is getting marked.
- **Set both retention values.** `limit_spam_retention_period` must be shorter than `limit_inbox_retention_period`, so spam clears out ahead of the inbox.
- **Use separate workspaces per agent archetype.** A sales-outreach agent and a support-triage agent have different send limits and spam tolerances — give each group its own workspace with its own policy rather than one catch-all.

## What's next

- [Policies API reference](/docs/reference/api/policies/) for the full endpoint documentation
- [Rules API reference](/docs/reference/api/rules/) for every condition operator, action, and field
- [Lists API reference](/docs/reference/api/lists/) for list and list-item management
- [Workspaces API reference](/docs/reference/api/workspaces/) for attaching `policy_id` and `rule_ids` and managing the default workspace
- [Provisioning Agent Accounts](/docs/v3/agent-accounts/provisioning/) to create Agent Accounts against a registered domain