{"slug": "auto-route-invoices-with-inbound-email-rules", "title": "Auto-Route Invoices With Inbound Email Rules", "summary": "Nylas introduced inbound email rules for Agent Accounts, enabling automatic invoice routing by matching sender fields and assigning messages to folders before application code runs. The rules support up to 50 conditions and 20 actions, and can reference domain lists for dynamic vendor management without redeployment.", "body_md": "A single inbound email rule can carry up to 50 match conditions and 20 actions — and it runs inside the mail infrastructure itself, before your webhook handler, your queue, or any line of your application code gets involved.\n\nThat changes how you build something like invoice routing. The usual approach is: receive `message.created`\n\n, fetch the message, check the sender, call the folders endpoint to move it. Four network hops and a handler you have to deploy and monitor. With [Agent Accounts](https://developer.nylas.com/docs/v3/agent-accounts/) — programmatic mailboxes currently in beta — there's a declarative alternative: Rules that match sender fields and run `assign_to_folder`\n\nso invoices are already sitting in the finance folder when your application first sees them.\n\nThe [Policies, Rules, and Lists docs](https://developer.nylas.com/docs/v3/agent-accounts/policies-rules-lists/) define a chain:\n\n`block`\n\n, `mark_as_spam`\n\n, `assign_to_folder`\n\n, `mark_as_read`\n\n, `mark_as_starred`\n\n, `archive`\n\n, or `trash`\n\n.None of them attach to an individual mailbox. Instead, a **workspace** carries one `policy_id`\n\nplus an array of `rule_ids`\n\n, and every agent mailbox in that workspace inherits the lot. Each application gets a default workspace that holds any account you haven't placed elsewhere — attach your rules there once and every unassigned mailbox picks them up. All of this is optional, too: with no workspace policy attached, an account simply runs at your billing plan's maximum limits and delivers every inbound message straight to the inbox.\n\nSay invoices arrive from a handful of vendors. One rule, OR'd conditions, two actions:\n\n```\ncurl --request POST \\\n  --url \"https://api.us.nylas.com/v3/rules\" \\\n  --header \"Authorization: Bearer <NYLAS_API_KEY>\" \\\n  --header \"Content-Type: application/json\" \\\n  --data '{\n    \"name\": \"Invoices → Finance folder\",\n    \"trigger\": \"inbound\",\n    \"match\": {\n      \"operator\": \"any\",\n      \"conditions\": [\n        { \"field\": \"from.domain\", \"operator\": \"is\", \"value\": \"billing.vendor-a.com\" },\n        { \"field\": \"from.address\", \"operator\": \"contains\", \"value\": \"invoice@\" }\n      ]\n    },\n    \"actions\": [\n      { \"type\": \"assign_to_folder\", \"value\": \"<FINANCE_FOLDER_ID>\" },\n      { \"type\": \"mark_as_read\" }\n    ]\n  }'\n```\n\nA rule does nothing until a workspace references it — add its ID to the workspace's `rule_ids`\n\narray and it's live for every account there. Inbound rules can match `from.address`\n\n, `from.domain`\n\n, and `from.tld`\n\nwith the operators `is`\n\n, `is_not`\n\n, `contains`\n\n, and `in_list`\n\n. Matching is case-insensitive.\n\nHardcoding domains in rule conditions works until accounting onboards a new vendor and files a ticket. A `domain`\n\n-typed List fixes that. Create it, load it, and point the rule at it:\n\n```\n# 1. Create the list (type is immutable after creation)\ncurl --request POST \\\n  --url \"https://api.us.nylas.com/v3/lists\" \\\n  --header \"Authorization: Bearer <NYLAS_API_KEY>\" \\\n  --header \"Content-Type: application/json\" \\\n  --data '{ \"name\": \"Invoice vendors\", \"type\": \"domain\" }'\n\n# 2. Add vendor domains — up to 1,000 items per request\ncurl --request POST \\\n  --url \"https://api.us.nylas.com/v3/lists/<LIST_ID>/items\" \\\n  --header \"Authorization: Bearer <NYLAS_API_KEY>\" \\\n  --header \"Content-Type: application/json\" \\\n  --data '{ \"items\": [\"billing.vendor-a.com\", \"invoices.vendor-b.io\"] }'\n```\n\nThen the rule condition becomes:\n\n```\n{ \"field\": \"from.domain\", \"operator\": \"in_list\", \"value\": [\"<LIST_ID>\"] }\n```\n\nNow whoever maintains the vendor roster updates the List without touching the rule or redeploying anything — every rule that references the list picks up new values immediately. Values get lowercased, trimmed, and validated against the list type on write (a `domain`\n\nlist rejects full email addresses), and duplicates are silently ignored. Lists come in three types — `domain`\n\n, `tld`\n\n, and `address`\n\n— and the type decides which rule fields they can match. One caution: deleting a list cascades to its items, and any rule matching it through `in_list`\n\nsilently stops matching those values.\n\nRules have fixed limits, and requests that exceed them are rejected with a validation error:\n\n| Cap | Value |\n|---|---|\n| Conditions per rule | 50 |\n| Actions per rule | 20 |\nLists per `in_list` condition |\n10 |\nCharacters per condition `value`\n|\n500 |\n\nFifty conditions sounds like a lot until you start inlining vendor addresses one condition at a time — which is exactly why the List pattern above exists. Ten lists per `in_list`\n\ncondition, each holding thousands of entries, scales much further than inline values ever will.\n\nInbound rules match exactly three fields: `from.address`\n\n, `from.domain`\n\n, and `from.tld`\n\n. There's no subject matching, no body matching, no \"has attachment\" condition. So a rule can route mail *from billing.vendor-a.com* to the finance folder, but it can't catch \"an invoice from an unknown sender.\" That split is actually a decent architecture: deterministic sender-based routing lives in the rule layer, and content-based classification — \"is this PDF actually an invoice?\" — stays in your application or LLM, working over a pre-sorted folder instead of a raw inbox.\n\nTwo adjacent facts from the policy layer are relevant here too. Attachment limits (size, count, and allowed MIME types) on the workspace policy apply to inbound mail only — over-limit attachments are dropped from the stored message — so a policy with a 26214400-byte (25 MB) attachment cap protects your invoice parser from absurd payloads before any code runs. And the same `rule_ids`\n\narray also carries `outbound`\n\nrules, evaluated when the account sends; Nylas filters by trigger at evaluation time, so the two directions never cross-fire.\n\nRules evaluate in `priority`\n\norder — lower runs first, the range is 0–1000, and the default is 10. The `block`\n\naction is terminal: for inbound mail it rejects at the SMTP stage, so a spam-block rule at priority 1 means junk never even reaches your invoice rule, let alone your LLM or your handler.\n\nThe behavior worth knowing before an incident: rule evaluation **fails closed**. If a `block`\n\nrule can't be evaluated because of a transient infrastructure error — say a list lookup fails mid-`in_list`\n\nmatch — the message gets blocked rather than waved through. Inbound SMTP responds with a `451`\n\ntempfail so the sending server retries instead of bouncing. The audit record carries `blocked_by_evaluation_error: true`\n\nso you can tell an infrastructure hiccup apart from a genuine match.\n\nEvery evaluation writes an audit entry. `GET /v3/grants/{grant_id}/rule-evaluations`\n\nlists them newest-first, with the evaluation stage, the normalized sender data that was considered, the matched rule IDs, and the applied actions. When finance asks why a vendor's invoice vanished, that's a one-call answer instead of a log-diving session.\n\nCreate one `domain`\n\nList with your top three vendors, one `assign_to_folder`\n\nrule referencing it, attach both to your default workspace, and send yourself a test invoice. Then check the rule-evaluations endpoint to watch the match happen. Total setup is three API calls. What's the most annoying piece of inbox sorting you're currently doing in application code?", "url": "https://wpnews.pro/news/auto-route-invoices-with-inbound-email-rules", "canonical_source": "https://dev.to/qasim157/auto-route-invoices-with-inbound-email-rules-ab3", "published_at": "2026-06-13 11:21:46+00:00", "updated_at": "2026-06-13 11:47:46.736878+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["Nylas", "Agent Accounts"], "alternates": {"html": "https://wpnews.pro/news/auto-route-invoices-with-inbound-email-rules", "markdown": "https://wpnews.pro/news/auto-route-invoices-with-inbound-email-rules.md", "text": "https://wpnews.pro/news/auto-route-invoices-with-inbound-email-rules.txt", "jsonld": "https://wpnews.pro/news/auto-route-invoices-with-inbound-email-rules.jsonld"}}