---
title: "List rules"
description: "Every rule on the connection, in the order they are evaluated."
url: "https://openemail.uk/docs/api/rules/list"
area: "API"
category: "Mailbox"
---

# List rules

Every rule on the connection, in the order they are evaluated.

`GET /rules`

## GET /rules

Every rule on the connection, in the order they are evaluated.

## How a rule runs

**shell**

```
export OE=https://api.openemail.uk
export AUTH="Authorization: Bearer $OPENEMAIL_API_KEY"
```

A rule is a list of conditions over an arriving message and a list of actions to take on anything that matches them. Rules belong to the CONNECTION rather than to whoever wrote one. A workspace key sees the same rules a colleague does, and deleting the author’s account does not take them with it.

A connection holds 100 rules, and each of them holds at most 20 conditions and 10 actions. Those are runaway-script guards rather than plan limits: the 101st rule is `rule_limit_reached`, a 422, and the 21st condition is refused by the schema before anything is written.

`match: "all"` ANDs the conditions together, `match: "any"` ORs them, and `negate` on a single condition is NOT. There is no nested boolean tree: `(A and B) or C` is two rules. That is a decision rather than a shortcut. A recursive schema cannot be described in the OpenAPI document with `$refStrategy: "none"`, cannot be handed to an MCP client as JSON Schema, and is the exact shape that has blown TypeScript’s instantiation ceiling in this repo before. Two rules is also how a person reads it back six months later.

Every enabled rule on the connection is evaluated against every arriving message, in `position` order, lowest first. Evaluation stops after a matching rule that carries `stopProcessing: true`, and nothing below it is considered at all. Where two matching rules both name a folder, the LATER one wins and the message ends up in its folder, which is the reading a numbered list gives you and the one thing about ordering worth stating rather than leaving to be discovered.

> Rules fail OPEN. A condition an older client wrote, a value that will not compile to a pattern, a database that will not answer: each of those is skipped and the message is delivered as it would have been, with the failure logged against the run. A rule engine that throws is a message that never arrives; a rule engine that is skipped is one message in the wrong folder.

Nothing is retroactive. A rule decides what happens to mail arriving after it exists, and there is deliberately no endpoint that applies one to a mailbox you already have. See `POST /rules/{id}/test`, which answers the question people are actually asking when they reach for that.

## Example

Needs `rules:read`. `limit` goes to 100, `cursor` is opaque (pass back the `nextCursor` you were given), and `enabled` narrows to one side of the switch.

**curl**

```
curl "$OE/rules?limit=25&enabled=true" -H "$AUTH"
```

**Response**

```
{
  "object": "list",
  "data": [
    {
      "object": "rule",
      "id": "rul_7f3a1c94e05d3862c1f0a44b",
      "name": "Receipts to their own label",
      "description": null,
      "enabled": true,
      "position": 0,
      "match": "all",
      "conditions": [
        { "field": "from_domain", "op": "matches", "value": "*.stripe.com", "negate": false },
        { "field": "subject", "op": "contains", "value": "receipt", "negate": false }
      ],
      "actions": [
        { "type": "label", "value": "USER_RECEIPTS" },
        { "type": "archive" }
      ],
      "stopProcessing": true,
      "lastMatchedAt": "2026-08-29T11:04:12.000Z",
      "matchCount": 148,
      "createdAt": "2026-08-01T09:00:00.000Z",
      "updatedAt": "2026-08-20T16:31:00.000Z"
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
```

> `enabled` takes the strings `true` and `false` rather than a coerced boolean, and that is not fussiness: `Boolean("false")` is `true`, so a coerced query would have answered "show me my disabled rules" with the enabled ones and looked like it worked.

> The order of the list is the evaluation order, so reading it top to bottom is reading what happens to a message. `position` is not unique and is not an id (it is renumbered by `POST /rules/reorder`), so pin a rule by its `rul_` id and never by where it currently sits.

> `matchCount` and `lastMatchedAt` are counted in the database as mail arrives rather than read and rewritten, so two messages landing at once cannot lose a count between them. A rule that has never fired reads `0` and `null`, which is the answer worth acting on when somebody says a rule is not working.
