Most "AI email" demos point a model at a human's inbox and let it flail through whatever's there. That's fine for a read-only assistant. But the moment your agent owns a mailbox — a support@yourcompany.com that takes inbound mail, triages it, and replies — you inherit a problem every human with an inbox already knows: the thing fills up with noise. Newsletters, vendor pings, no-reply notifications, status pages. If your agent has to wake up, fetch, classify, and route every message just to find the three that matter, you've written a pile of handler code to do what a mail rule does in one config object.
So before you write that code, ask whether you need it at all. Keeping an agent inbox tidy — by sender — is exactly what Rules are for. You declare "mail from this domain goes in this folder, marked read" once, attach it to the workspace, and Nylas does the filing server-side before your application ever sees the message. No webhook handler, no classifier, no folder-move call. That's the angle of this post: no-code inbox organization for Agent Accounts.
One thing I want to be honest about up front, because it's the most common wrong assumption people make here: inbound Rules match the sender, not the subject. I'll come back to that hard line repeatedly. Filing by who sent it is a Rule. Filing by what the subject says is app-side work. Both are easy; they're just different tools, and conflating them is how people end up frustrated that "my subject rule isn't firing." There is no subject rule.
I work on the Nylas CLI, so the nylas ... commands below are the exact ones I reach for. Every operation gets the two-angle tour: the raw curl against the API, and the CLI equivalent.
What you can do with Rules (and what you can't)
An Agent Account is just a grant with a grant_id. Everything you already know about the Messages, Folders, and Threads endpoints applies unchanged — there's nothing new to learn on the data plane. What Agent Accounts add on top is a small admin layer: Policies (limits and spam settings), Rules (match-and-act on mail), and Lists (reusable value sets that Rules reference).
For tidiness, Rules are the star. An inbound Rule has a trigger of inbound, one or more match conditions, and a set of actions. The conditions can match only these sender fields:
-
from.address— the full sender address -
from.domain— the sender's domain -
from.tld— the sender's top-level domain
Operators are is, is_not, contains, and in_list. That's the whole condition vocabulary for inbound. No subject, no body, no header matching. If your filing decision depends on anything other than who sent the mail, a Rule can't make it — that's app-side, and I'll show that path too.
The actions you can attach to a matching inbound Rule are:
-
assign_to_folder— move the message into a folder you name by ID -
mark_as_read— clear the unread flag -
mark_as_starred— star it -
archive— move it out of the inbox into the archive folder -
trash— soft-delete to Trash -
block— reject at SMTP (terminal; can't be combined with other actions)
For organization, you'll mostly reach for the first four. block is access-control territory — it rejects the message before it's ever delivered, which is a different job from tidying. I'm staying on the tidiness side here.
Why this beats a webhook handler
You could absolutely do all of this in application code: subscribe to message.created, fetch the message, look at the sender, call PUT /messages/{id} to move it. It works. But for sender-based filing it's strictly more moving parts than you need:
-
It runs server-side, before your app sees the message. The mail is already in the right folder by the time anything fires. Your agent's inbox query (
in=inbox) returns the stuff that actually needs attention. - Non-engineers can maintain the input. Pair a Rule with a List, and someone can add a vendor domain to the "noise" list without redeploying anything.
- It's auditable. Nylas records every rule evaluation; you can ask "why did this land in that folder?" with one API call.
The honest tradeoff: Rules only see the sender. If you want "anything with [invoice] in the subject goes to the Finance folder," no Rule will do it. So the real architecture for most agents is both — Rules handle the bulk sender-based sorting cheaply, and a thin handler covers the few content-based cases. Let's build both.
Before you begin
You'll need an Agent Account (a grant on a registered domain), your NYLAS_API_KEY, and the CLI initialized against the account. The examples use https://api.us.nylas.com and a bearer token. If you haven't provisioned an account yet, start with the Agent Accounts quickstart; the Policies, Rules, and Lists page is the reference I'm drawing on throughout.
Throughout, I'll use support@yourcompany.com as the agent's address and pretend it's drowning in three categories of mail: newsletters, vendor notifications, and the occasional VIP customer.
Step 1: Create the folders you'll file into
assign_to_folder needs a folder ID, so the folders have to exist first. Agent Accounts auto-provision six system folders (inbox, sent, drafts, trash, junk, archive); you create custom ones alongside them.
Create a "Newsletters" folder over the API:
curl --request POST \
--url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/folders" \
--header "Authorization: Bearer <NYLAS_API_KEY>" \
--header "Content-Type: application/json" \
--data '{ "name": "Newsletters" }'
The response carries the folder's id — hang onto it. The CLI does the same thing and prints the ID:
nylas email folders create "Newsletters"
Create a second folder the same way for vendor noise:
curl --request POST \
--url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/folders" \
--header "Authorization: Bearer <NYLAS_API_KEY>" \
--header "Content-Type: application/json" \
--data '{ "name": "Vendor notifications" }'
nylas email folders create "Vendor notifications"
If you ever need to look up an existing folder's ID, nylas email folders list prints them, and GET /v3/grants/<NYLAS_GRANT_ID>/folders returns the full set over the API.
Step 2: File newsletters into a folder, marked read
Now the Rule. I want anything from a newsletter sender to land in the Newsletters folder and skip the unread count, because I'm never going to make the agent act on a newsletter. This is a textbook sender-based filing Rule: it matches from.domain and pairs assign_to_folder with mark_as_read.
Here's the API call. Note operator: "any" so either condition matches (OR), and the value on assign_to_folder is the folder ID from Step 1:
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 → Newsletters folder",
"trigger": "inbound",
"match": {
"operator": "any",
"conditions": [
{ "field": "from.domain", "operator": "contains", "value": "substack.com" },
{ "field": "from.address", "operator": "contains", "value": "newsletter@" }
]
},
"actions": [
{ "type": "assign_to_folder", "value": "<NEWSLETTERS_FOLDER_ID>" },
{ "type": "mark_as_read" }
]
}'
The CLI expresses conditions as field,operator,value and actions as type or type=value, repeatable. The equivalent:
nylas agent rule create \
--name "Newsletters → Newsletters folder" \
--trigger inbound \
--match-operator any \
--condition "from.domain,contains,substack.com" \
--condition "from.address,contains,newsletter@" \
--action "assign_to_folder=<NEWSLETTERS_FOLDER_ID>" \
--action mark_as_read
One detail worth calling out, and it's the whole point of this post: I'm matching from.domain and from.address because those are the only things an inbound Rule can see. I am explicitly not writing "if the subject contains the word newsletter" — there is no such condition. If a newsletter comes from a sender I haven't listed, this Rule won't catch it, and that's a sender-coverage problem I solve by adding senders, not by reaching for the subject.
Step 3: Auto-star VIP mail, and archive vendor noise
Two more Rules, same shape. First, auto-star anything from a key customer so it floats to the top of a "starred" query — mark_as_starred is the action, and I'm matching the exact sender domain with is:
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 VIP customer",
"priority": 1,
"trigger": "inbound",
"match": {
"conditions": [
{ "field": "from.domain", "operator": "is", "value": "bigcustomer.example" }
]
},
"actions": [
{ "type": "mark_as_starred" }
]
}'
nylas agent rule create \
--name "Star VIP customer" \
--priority 1 \
--trigger inbound \
--condition "from.domain,is,bigcustomer.example" \
--action mark_as_starred
I gave that one priority: 1 (lower numbers run first) so VIP handling beats any broader rule. Rules run lowest-priority-first, and a block is terminal — but since none of these tidiness rules block, ordering here is mostly about which folder wins if two assign_to_folder actions could both apply.
Second, push vendor status-page mail straight to a folder out of the inbox. You could use archive to drop it into the system archive folder, but I'd rather keep vendor mail in its own named folder, so I'll assign_to_folder to the "Vendor notifications" folder and mark it read:
curl --request POST \
--url "https://api.us.nylas.com/v3/rules" \
--header "Authorization: Bearer <NYLAS_API_KEY>" \
--header "Content-Type: application/json" \
--data '{
"name": "Vendor noise → Vendor folder",
"trigger": "inbound",
"match": {
"conditions": [
{ "field": "from.address", "operator": "is", "value": "no-reply@status.example" }
]
},
"actions": [
{ "type": "assign_to_folder", "value": "<VENDOR_FOLDER_ID>" },
{ "type": "mark_as_read" }
]
}'
nylas agent rule create \
--name "Vendor noise → Vendor folder" \
--trigger inbound \
--condition "from.address,is,no-reply@status.example" \
--action "assign_to_folder=<VENDOR_FOLDER_ID>" \
--action mark_as_read
If you'd rather use the system archive folder than a custom one, swap the actions for a single { "type": "archive" } — archive is a first-class inbound action, so you don't need to fetch the archive folder's ID.
Step 4: Activate the Rules on the workspace
Here's the gotcha that bites everyone once: a Rule does nothing until a workspace references it. Creating a Rule over POST /v3/rules just defines it. It sits inert until its ID lands in a workspace's rule_ids array. Workspaces carry rules (and policies); grants inherit them by belonging to a workspace.
So after creating a Rule, patch the workspace to include its ID. One array carries both inbound and outbound rules — Nylas filters by trigger at evaluation time, so you list them all together:
curl --request PATCH \
--url "https://api.us.nylas.com/v3/workspaces/<WORKSPACE_ID>" \
--header "Authorization: Bearer <NYLAS_API_KEY>" \
--header "Content-Type: application/json" \
--data '{
"rule_ids": ["<NEWSLETTER_RULE_ID>", "<VIP_RULE_ID>", "<VENDOR_RULE_ID>"]
}'
nylas workspace update <WORKSPACE_ID> --rules-ids <NEWSLETTER_RULE_ID>,<VIP_RULE_ID>,<VENDOR_RULE_ID>
A convenience worth knowing: the CLI's nylas agent rule create attaches the new rule to the default workspace for you, resolved from the current grant — so if all your Agent Accounts live in the default workspace, the create command alone is enough and you can skip the explicit workspace update. I still show the patch because the moment you run more than one workspace (say a separate one per agent archetype), you'll want to control exactly which workspace each rule lands in. Find your workspace IDs with nylas workspace list or GET /v3/workspaces.
The other half: filing by subject (app-side)
Everything above sorts by sender. The instant your filing rule depends on the subject or the body — "invoices to Finance," "anything mentioning 'urgent' stays starred" — you're out of Rule territory and into your own code. There's no shame in it; it's just a different mechanism, and the Agent-Account-as-grant abstraction means it's the same Messages API you'd use anywhere.
The pattern is: react to the inbound message.created webhook, fetch the message, classify it however you like (regex, an LLM, whatever), then move it. Don't rely on the webhook payload for the body — fetch the full message by id when you need it, and branch on message.created.truncated for large messages. Once you've decided where it goes, set the folder:
curl --request PUT \
--url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages/<MESSAGE_ID>" \
--header "Authorization: Bearer <NYLAS_API_KEY>" \
--header "Content-Type: application/json" \
--data '{
"folders": ["<FINANCE_FOLDER_ID>"]
}'
The CLI wraps the same move in one command:
nylas email move <MESSAGE_ID> --folder <FINANCE_FOLDER_ID>
nylas email move also takes --archive to clear all folders/labels instead of targeting one — handy when "file it out of sight" is the whole goal and you don't care which folder.
The mental split is clean once you internalize it: sender → Rule (server-side, zero handler code); subject or content → app-side fetch-classify-move. Most agents I've seen want both, and they compose nicely — the Rules thin the inbox down to the messages that genuinely need a content decision, and your handler only runs the expensive classification on what's left.
Gotchas worth knowing
A few things I'd want a teammate to know before they ship this:
-
Marking read is
mark_as_readin a Rule, but a separatePUTin app code. If you move a message by hand and also want it read, that'sPUT /messages/{id}with{"unread": false}— a GET never clears the flag as a side effect. Keep fetch and mark-read as distinct operations. -
assign_to_folderneeds a folder that exists. Create the folder first (Step 1) and use its ID, not its name. Pointing a Rule at a folder ID you later delete will leave it filing into nothing. -
Order matters when actions could collide. Two Rules that both
assign_to_folderwill fight over the message; lowerpriorityruns first. Put specific rules (is,in_listagainst a tight list) ahead of broad ones (contains). -
Prefer Lists when the sender set grows. Instead of editing a Rule every time you add a noisy vendor, create a domain List, reference it with
from.domain in_list <list-id>, and let a non-engineer append to the List. The Rule never changes. - Rules are sender-only. Forever. I'll say it one last time so nobody files a bug: there is no subject condition for inbound Rules. Subject-based filing is app-side. If you remember one thing from this post, make it that.
What's next
- Policies, Rules, and Lists — the full condition/action/operator reference, plus Lists and the rule-evaluations audit trail
- Supported endpoints for Agent Accounts — the Messages, Folders, and Threads surface your app-side handler builds on
-
Nylas CLI command reference — every flag for
nylas agent rule,nylas email folders,nylas email move, andnylas workspace
Build the sender-based filing as Rules, keep a thin handler for the subject cases, and your agent wakes up to an inbox that's already sorted — which means it spends its compute deciding what to do, not where things go.
Top comments (0)