An AI agent with its own mailbox reacts to whatever lands in it. That's the point, until a spam blast, a mailer-daemon loop, or an auto-reply triggers the agent into answering noise. The same goes the other way: an agent composing mail on its own can address the wrong person, leak to a test domain that slipped into production, or email a competitor because nobody told it not to. A human would catch these. An agent needs guardrails encoded somewhere it can't skip.
Agent Accounts ship three admin resources for exactly this: Policies bundle limits and spam settings, Rules match mail on the way in or out and run actions like block or assign_to_folder, and Lists are reusable collections of domains or addresses that rules reference. This post covers all three from two angles: the HTTP API for your backend, and the Nylas CLI for inspecting and managing policies and rules from the terminal. Lists, workspaces, and the rule-evaluations audit log are API-only for now. I work on the CLI, so the terminal commands below are the ones I reach for.
How Policies, Rules, and Lists fit together
The three resources form a chain, and a workspace ties it to your accounts. A List holds values like domains or addresses. A Rule references lists through the in_list operator and describes conditions and actions. A Policy bundles limits and spam settings. A workspace carries one policy_id plus an array of rule_ids, and every Agent Account in that workspace inherits both.
What matters here: you don't attach a policy or rule to an individual grant. You set policy_id and rule_ids on a workspace, and they apply to every account in it. Each application has a default workspace that holds any account you haven't placed elsewhere, so configuring that one workspace covers all your unassigned accounts at once. All three resources are application-scoped — they carry no grant ID in the path, and your API key identifies the application.
| Resource | What it owns | How it's referenced |
|---|---|---|
| List | A typed collection of domains, TLDs, or email addresses | By ID, from a rule condition when operator is in_list
|
| Rule | A trigger (inbound or outbound), match conditions, and actions |
By ID, in a workspace's rule_ids array |
| Policy | Limits and spam detection settings | By ID, in a workspace's policy_id
|
| Workspace | The policy_id and rule_ids that govern its accounts |
By ID, in a grant's workspace_id
|
They're optional. Without a workspace policy, an account runs at your billing plan's maximum limits; without workspace rules, inbound mail is delivered to the inbox unfiltered. Reach for these only when you want stricter behavior or filtering.
Inspect what's already configured from the CLI
Before changing anything, see what exists. The Nylas CLI lists your policies and rules so you can audit the current setup without opening the Dashboard. This is usually my first step when I pick up an application I didn't configure myself.
# List policies and rules on the application
nylas agent policy list
nylas agent rule list
# Fetch a single policy or rule by ID
nylas agent policy get <policy-id>
nylas agent rule get <rule-id>
The same listing is available over the API at GET /v3/policies and GET /v3/rules. Both surfaces return the application's resources, since policies and rules aren't grant-scoped. Once you know what's there, you can create new ones or update the workspace assignments.
Set limits and spam detection with a Policy
A policy is the configuration you reuse across many accounts. It holds a limits object — attachment size and count, allowed MIME types, total message size, per-account storage, daily send quota, and inbox and spam retention — and a spam_detection object with DNSBL checking, header anomaly detection, and a spam_sensitivity dial from 0.1 to 5.0 where higher is more aggressive. Every limit is optional and defaults to your plan's maximum; requesting a value above the plan maximum returns an error.
These are the limit fields you set most often, each enforced per Agent Account:
| Field | What it controls |
|---|---|
limit_count_daily_message_received |
Messages the account can receive per day |
limit_attachment_size_limit |
Size of a single inbound attachment, in bytes |
limit_attachment_count_limit |
Attachments allowed on one inbound message |
limit_count_daily_email_sent |
Messages the account can send per day |
limit_storage_total |
Total stored bytes per account |
limit_inbox_retention_period |
Days a message stays in the inbox before deletion |
limit_spam_retention_period |
Days a message stays in spam before deletion |
Create a policy over the API with POST /v3/policies. The body below caps attachments at 25 MB, keeps inbox mail for a year and spam for 30 days, and turns spam detection up slightly to a sensitivity of 1.5:
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_inbox_retention_period": 365,
"limit_spam_retention_period": 30
},
"spam_detection": {
"use_list_dnsbl": true,
"use_header_anomaly_detection": true,
"spam_sensitivity": 1.5
}
}'
The CLI creates the same policy from the terminal. nylas agent policy create takes a --name and the configuration as inline JSON with --data, or from a file with --data-file — handy for keeping policy definitions in version control:
nylas agent policy create \
--name "Standard Agent Account Policy" \
--data-file ./standard-policy.json
A policy does nothing until a workspace references it. Attach it by setting policy_id on a workspace with PATCH /v3/workspaces/{workspace_id}, and every account in that workspace picks up its limits and spam settings. Set the spam retention shorter than the inbox retention, since spam should clear out ahead of real mail.
Block inbound spam before the agent sees it
A rule decides what to do with a message, either on the way in or on the way out. An inbound rule matches on sender fields and runs actions when it hits. The most useful one for an agent is block: it rejects the message at the SMTP layer, so the message never lands in the mailbox and your application never sees it. Inbound rules can match from.address, from.domain, and from.tld with the operators is, is_not, contains, and in_list.
This rule blocks every message from a known bad domain. Rules run in priority order from 0 to 1000, lowest first, defaulting to 10, and a block action is terminal — it can't be combined with other actions:
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" }]
}'
From the CLI, nylas agent rule create builds the same rule. You can pass the whole definition with --data-file, or assemble it inline with --trigger, --condition, and --action flags:
nylas agent rule create \
--name "Block spam-domain.com" \
--data-file ./block-rule.json
Block isn't the only action. To route instead of reject, pair assign_to_folder with mark_as_read to move newsletters into a reading folder and mark them read in one rule. The full action set is block, mark_as_spam, assign_to_folder, mark_as_read, mark_as_starred, archive, and trash. A rule only runs once a workspace lists its ID in rule_ids; one array carries both inbound and outbound rules, and Nylas filters by trigger at evaluation time.
Stop sends to the wrong recipients with outbound rules
Outbound rules are where an agent gets data-loss prevention. They run when the account issues a send, before the message reaches the email provider, so a block action short-circuits delivery entirely — the API returns 403 and no sent copy is stored. Outbound rules can match from.*, plus recipient.address, recipient.domain, recipient.tld, and outbound.type.
The recipient.* fields match against any recipient, including BCC and SMTP envelope recipients, which is what makes them useful for DLP — a hidden BCC to a blocked domain still trips the rule. This one blocks any send to a specific domain, the kind of hard stop you'd use for a competitor or a test domain:
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" }]
}'
The outbound.type field narrows a rule to replies or brand-new messages without looking at recipients at all. It takes two values: reply when the send includes reply_to_message_id or the raw MIME carries In-Reply-To or References, and compose for everything else. It supports only is and is_not. A rule with outbound.type is reply lets you, say, auto-star every reply the agent sends. Non-blocking outbound actions like archive and mark_as_starred apply to the stored sent copy after the send succeeds — they don't change what the recipient receives.
One behavior worth designing for: rule evaluation fails closed. If a block rule can't be evaluated because of a transient error, like a list lookup failing during in_list matching, Nylas blocks rather than letting the message through. Inbound, that surfaces as a 451 tempfail so the sending server retries; on an API send it returns 503 instead of 403, so your application can retry too.
Maintain dynamic allow and block lists
When the set of blocked domains changes over time, you don't want to edit rules every time. A List is a typed collection of values a rule matches against through in_list, so you update the list and every rule referencing it picks up the change immediately. Each list has a fixed type set at creation: domain for domain names, tld for top-level domains, or address for full email addresses.
Lists have no CLI command, so you manage them over the API. Create a list, then add up to 1000 items per request. Values are lowercased and trimmed on write and validated against the list's type, so a domain list rejects full email addresses:
# Create the list
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" }'
# Add items to it
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"] }'
To use the list, set a rule condition's operator to in_list and pass one or more list IDs as the value. A single in_list condition can reference up to 10 lists. Rules cap at 50 conditions and 20 actions each, with up to 500 characters per condition value, so a list is the right tool once a block set grows past a handful of inline values. Lists let a non-engineer update who's allowed or blocked without touching rule definitions or redeploying anything.
Audit which rules ran
When a message gets blocked, routed, or marked and you need to know why, the rule-evaluations endpoint is the fastest answer. Every time the engine evaluates an inbound message, an SMTP envelope, or an outbound send for an account, Nylas records an audit entry. List them, most recent first, with GET /v3/grants/{grant_id}/rule-evaluations:
curl --request GET \
--url "https://api.us.nylas.com/v3/grants/<GRANT_ID>/rule-evaluations?limit=50" \
--header "Authorization: Bearer <NYLAS_API_KEY>"
Each record names the evaluation stage — smtp_rcpt when a message was rejected before acceptance, inbox_processing after acceptance, or outbound_send at send time — the normalized sender and recipient data considered, the IDs of any matched rules, and the actions applied. When a block came from a fail-closed evaluation error rather than a genuine match, the record carries blocked_by_evaluation_error: true, so you can tell an infrastructure hiccup apart from a real rule hit. Cross-reference matched_rule_ids with nylas agent rule get to see the conditions behind each match.
Things to keep in mind
A few practices keep this system predictable as it grows. None are complicated, but each one prevents a class of surprise.
-
Order rules by specificity. Lower
priorityruns first and the firstblockis terminal, so put narrow rules (is,in_listagainst a small list) ahead of broad ones (contains). - Pick the right trigger. Inbound rules only see received mail; outbound rules only see sends. They're isolated on purpose, so don't try to filter sends with an inbound rule.
-
Handle
403from sends. An outboundblockreturns403with no sent copy stored. Treat it like any delivery failure — no retry path will deliver it. - Prefer lists over inline values when the set will grow. One list feeds many rules and updates without touching rule definitions.
- Start spam sensitivity at 1.0 and tune from there: up if spam slips through, down if real mail gets marked.
- Use separate workspaces per agent archetype. A sales-outreach agent and a support-triage agent have different send limits and spam tolerances — give each its own workspace and policy rather than one catch-all.
Wrapping up
Policies, Rules, and Lists give an agent the judgment it doesn't have on its own: what to drop on the way in, what to refuse on the way out, and how much it's allowed to send and store. The model is small once it clicks — lists feed rules, a policy bundles limits, and a workspace carries both to every account inside it. Build the pieces over the API or the CLI, attach them to a workspace, and the audit trail tells you exactly what fired.
Where to go next:
- Policies, Rules, and Lists — the full guide with every field and operator
- Policies API reference and Rules API reference
- Workspaces — how policies and rules attach to accounts
-
Nylas CLI agent commands —
nylas agent policyandnylas agent rulereference
Top comments (0)