> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oximail.ch/llms.txt
> Use this file to discover all available pages before exploring further.

# Filtering: Rules & Sieve

> The two server-side filtering layers: structured Rules managed over JMAP, and Sieve scripts (RFC 5228) with ManageSieve access — both applied at final delivery, including local-to-local mail.

OxiMail filters mail server-side through two complementary layers:

* **Rules** — structured filter objects (conditions → actions) managed over JMAP. This is what a webmail settings screen edits: no script syntax, introspectable, orderable.
* **Sieve scripts** — the standard filtering language ([RFC 5228](https://www.rfc-editor.org/rfc/rfc5228)), for power users and for clients that speak ManageSieve. A script can express things the structured form cannot.

Both run **at final delivery**, where RFC 5228 §1.1 puts them. That includes mail a colleague sends you from the same server: the local-delivery shortcut applies the recipient's Rules and vacation response exactly as an externally received message would (see [the SMTP layer](../architecture/oximail-smtp) and ADR-117).

## Rules

A Rule pairs conditions (sender, recipient, subject, headers…) with actions (file into a folder, mark, forward, …). Two properties matter operationally:

* **Total, deterministic order.** Rules evaluate in a defined order with no ties, so the same message always takes the same path. Order is part of the data, not an accident of storage.
* **Apply to existing mail.** `Rule/apply` runs a rule over a folder's existing messages, **newest first**, in bounded batches: the response carries `totalInFolder`, `truncated`, and `nextPosition` so a client continues explicitly instead of believing a silent cap (each evaluated message is a blob fetch + decrypt — an unbounded apply would be a self-inflicted denial of service). `dryRun: true` evaluates and counts without mutating anything, and unknown arguments are rejected as `invalidArguments` rather than ignored — on a mass-mutating method, a silently dropped `dryRun` is not acceptable.

## Sieve scripts

### Over JMAP (RFC 9661)

Sieve scripts are managed with the `SieveScript` JMAP methods per [RFC 9661](https://www.rfc-editor.org/rfc/rfc9661). Script content travels as a **blob**: upload the script, reference it by `blobId` on create/update, and read it back the same way (§2.1/2.2 — the round-trip returns the exact stored bytes). Validation is a separate step from activation, and destroying the currently **active** script is refused with `scriptIsActive` — deactivate first, then destroy.

### Supported extensions

The interpreter supports, and the ManageSieve capability line advertises, exactly this set (the advertisement is built from the compiler's own list, so the two cannot drift):

```text theme={null}
body  envelope  fileinto  imap4flags  reject  relational  vacation  variables
```

A `require` for anything else fails compilation loudly. Note that a Sieve `header` test with multiple header names (`["From", "Sender"]`) matches any of them — the list is never truncated to its first element.

### The global script

`[sieve] global_script` names an optional server-wide script that runs **before** each user's own script. If the global script ends with a terminal action (`redirect`, `discard`, `reject` with `stop`), the per-user script is skipped. Use it for organization-wide policy that individual users must not override.

```toml theme={null}
[sieve]
global_script = "/etc/oximail/global.sieve"
```

Compiled scripts are hot-reloaded via an atomic pointer swap — editing a script never requires a restart and never blocks in-flight deliveries.

## ManageSieve (RFC 5804)

Clients that manage Sieve scripts natively (Thunderbird's Sieve add-on, `sieve-connect`) speak [RFC 5804](https://www.rfc-editor.org/rfc/rfc5804). The listener binds `127.0.0.1:4190` by default — **localhost only**, because ManageSieve predates modern TLS conventions. To expose it, terminate TLS in front (stunnel, nginx `stream`) and enable the PROXY protocol so the server still sees the real client address:

```toml theme={null}
[legacy]
managesieve_proxy_protocol = true   # listener expects a HAProxy PROXY v1 header
```

Without the PROXY header, every connection would look like loopback (typically trusted) and failed logins could not feed fail2ban. Authentication supports SASL `PLAIN` and `LOGIN` (including the multi-step variant), and failed attempts count toward the same [fail2ban machinery](./operations) as every other protocol. `GETSCRIPT` returns the archived original bytes of the script, not a re-serialization.

## Vacation auto-reply

The vacation response is exposed twice — as the JMAP `VacationResponse` object and as the Sieve `vacation` extension — over one underlying mechanism with reply deduplication (one auto-reply per correspondent per period). Two behaviours worth knowing:

* Vacation replies are **suppressed for a disabled account** — mail still delivers, but a disabled identity does not speak.
* A vacation reply is generated at final delivery, so it also answers local senders.

## Where to look when a filter misbehaves

* `oximail sieve …` and `oximail rule …` CLI verbs inspect and manage both layers per account — see the [CLI reference](./cli).
* Delivery decisions are logged with the account and rule/script context — see [Operations](./operations) for the journald workflow.
* A rule that appears to skip mail sent from a colleague on the same server would have been the pre-#693 gap; since then local delivery runs recipient policy. If you see it, it is a bug, not a configuration subtlety.
