Test a rule
Which of your recent messages this rule would have caught. It changes nothing.
Runs the real call against your workspace, with your own key.
POST /rules/{id}/test
Which of your recent messages this rule would have caught. It changes nothing.
Example
Needs rules:read. A dry run reads a mailbox and writes nothing, so it is not a rules:write operation. days defaults to 30 and goes to 365, limit defaults to 50 and goes to 200, and threadIds tests named threads instead of a window.
curl -X POST "$OE/rules/rul_7f3a1c94e05d3862c1f0a44b/test" -H "$AUTH" \ -H "Content-Type: application/json" \ -d '{ "days": 30, "limit": 50 }'{ "object": "rule_test", "ruleId": "rul_7f3a1c94e05d3862c1f0a44b", "scanned": 50, "matched": 3, "wouldApply": ["label:USER_RECEIPTS", "archive"], "messages": [ { "threadId": "thr_5d31c2a8…", "from": "[email protected]", "subject": "Your receipt", "receivedAt": "2026-08-28T10:00:00.000Z" } ], "warnings": [{ "code": "forward_unverified", "value": "[email protected]" }]}The same engine that runs on the delivery path answers this. It is one module, deliberately pure (no database, no network), so the settings screen, the dry run and the SMTP handler cannot disagree about whether a rule matches. A preview written twice is two answers to one question, and the one a person is shown is the one that is wrong.
Bounded by construction: at most 200 messages over at most a year. This runs inside a Worker with a wall-clock budget, and an unbounded scan is a request that dies halfway with nothing to show for it.
A rule that is disabled can still be tested. That is the point: write it, test it, then enable it.
warnings is where a rule that parses but will not behave shows up. A forward address nobody has confirmed is the one you will meet. A warning never stops the test: the point of a dry run is to report everything it found in one pass rather than to fail at the first thing.
There is no “run this over my mailbox”
There is deliberately no POST /rules/{id}/run, and there is not going to be one. Applying a rule retroactively to a whole mailbox is unbounded, irreversible and has no undo (a trash action over ten years of mail is one call with no second chance), and the write path it would have to use merges into the stored thread, which would bump every message it touched to the top of the inbox as a side effect of tidying up.
This endpoint is the honest half of that request: it answers "what would this have done", which is the question, and then you file the messages you actually meant with PATCH /threads/{id}.