# Buckets and rules

A bucket is the central configuration object:

| Part | What it is |
|---|---|
| `name` | Unique within the tenant. Lowercase, digits, dashes. |
| `priority` | Lower runs first. Default 100. |
| `rule` | A JSON predicate. The message lands here if it matches and no lower-priority bucket matched first. |
| `schema` | Optional. Fields and extractors. See [Schemas and extractors](/docs/schemas-and-extractors). |
| `policy` | Optional. What auto-applies and what becomes a task. See [Policy, tasks and captures](/docs/policy-tasks-and-captures). |
| `destinations` | Where applied captures go. |

A message lands in exactly one bucket or in `unrouted`.

## Rules

A rule is a JSON object with exactly one key. Combine with `any`, `all`, and `not`.

```json
{ "any": [
  { "address": "orders@" },
  { "all": [ { "sender_domain": ".shop.example" }, { "subject_contains": ["order", "receipt"] } ] },
  { "not": { "has_attachment": false } }
] }
```

The full predicate list is in [Predicates](/docs/predicates). Regexes are JavaScript syntax, case-insensitive by default, and are checked when you save: a bad regex is rejected with the path of the predicate that failed.

`dry_run_rule` returns, per message, whether it matched and the path of the deciding predicate (for example `rule.any[1].all[0].sender_domain`), which is also stored on the message as its classification reason.

## Writing rules as an agent

1. Ingest a few real messages. Read them with `get_message`; the sanitized `text`, the `from`, and the `to` are what rules see.
2. Start with the cheapest signals: the receiving address, then the sender domain, then subject words. Body text and attachment text last.
3. Test with `dry_run_rule` against the message ids, including messages that should not match.
4. Save with `create_bucket` or `update_bucket`, then `reprocess_message` on the ones that were unrouted.
