Skip to main content
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), 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 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. 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):
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.
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. 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:
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 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.
  • Delivery decisions are logged with the account and rule/script context — see 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.