All help articles

Mail filters (Sieve)

Filters decide what happens to mail as it arrives: file it in a folder, flag it, forward it, refuse it or throw it away. Mailocity runs filters written in Sieve (RFC 5228), the standard filter language that most mail servers and many mail apps understand. Every user can have their own filters, and administrators can add rules for a whole tenant or domain.

The webmail rule builder ("when the sender is X, move it to folder Y") and the matching screens in the admin panels arrive in M2. Until then, filters are managed with mail apps that speak ManageSieve or JMAP Sieve, or through the API.

How filters run

When a message arrives for one of your users (from another server, or from a colleague on the same server), up to three scripts run in this order:

  1. Tenant rules, set by the tenant's administrators, for every account in the tenant.
  2. Domain rules, for mail addressed to that domain. The platform sets the rules of shared domains.
  3. The user's active script. A user can keep up to 20 scripts and turn one of them on.

A script that decides where the message goes (it files it, discards it, redirects it, rejects it or keeps it explicitly) ends the chain: later scripts do not run. A script that only sets flags, or does nothing, passes the message and its flags to the next one. Actions with :copy add a copy without ending the chain. If nothing decides, the message goes where it would have gone without filters: the inbox, the plus-address folder, or Junk when it failed the sender's DMARC policy.

Filters never lose mail by accident: a script that fails to run, a folder that does not exist, or a redirect that is not allowed all leave the message in the default folder.

What scripts can do

Supported extensions: fileinto, envelope, imap4flags, copy, reject and ereject, body, variables, relational, subaddress, environment, encoded-character, date, mailbox, vacation and vacation-seconds (see Vacation replies), and the comparators i;octet, i;ascii-casemap, i;ascii-numeric and i;unicode-casemap.

An example user script:

require ["fileinto", "imap4flags", "copy", "subaddress", "envelope"];

# Newsletters go to their own folder, already read.
if header :contains "list-id" "newsletter" {
    fileinto :flags "\\Seen" "Newsletters";
    stop;
}

# Mail to alice+invoices@ goes to Invoices.
if envelope :detail "to" "invoices" {
    fileinto "Invoices";
}

# Anything from the boss is flagged and also sent to my assistant.
if address :is "from" "boss@example.com" {
    addflag "\\Flagged";
    redirect :copy "assistant@example.com";
}

And a tenant rule that flags urgent mail for everyone without filing it, so each user's own filters still run:

require "imap4flags";
if header :contains "subject" "[URGENT]" { addflag "\\Flagged"; }

Notes on matching:

Dates and folders

date tests a date in a header field, and currentdate the time the message arrives (RFC 5260). Dates are compared in UTC unless the test gives :zone "+0200", or :originalzone to keep the header's own zone. For Received fields the date after the last ; is used. The parts are year, month, day, date (2026-10-06), julian, hour, minute, second, time (14:05:00), iso8601, std11, zone (+0200) and weekday (0 is Sunday). With relational, :value "ge" and :value "le" give ranges:

require ["date", "relational", "fileinto"];
# During October, file mail about the conference in its own folder.
if allof(currentdate :value "ge" "date" "2026-10-01",
         currentdate :value "le" "date" "2026-10-31",
         header :contains "subject" "conference") {
    fileinto "Conference";
}

mailbox (RFC 5490) adds fileinto :create, which creates the folder when it does not exist, and the mailboxexists test:

require ["mailbox", "fileinto", "variables"];
# One folder per project, created on first use.
if header :matches "subject" "[project *]*" {
    fileinto :create "Projects/${1}";
}

A folder is created only when its name is allowed (not INBOX, no *, % or line breaks, at most 255 bytes) and the account has fewer than 200 folders; otherwise the message goes to the default folder. Without :create, a missing folder still means the default folder.

Forwarding (redirect)

Reject

reject and ereject refuse the message with the script's reason. When everyone the message was addressed to on this server rejects it, the sending server is told during the SMTP conversation and returns it to the sender. When only some of them reject it, the others still get it and the rejecting users' copies are dropped without telling the sender.

Managing filters

Users, in webmail: open Filters (the sliders button at the top).

Scripts made in a mail app over ManageSieve show up in the editor, and the builder's rules show up in mail apps as the webmail-rules script. Editing webmail-rules by hand is possible, but the builder rewrites it the next time its rules are saved.

Users, from a mail app that speaks ManageSieve (RFC 5804), such as Thunderbird with the Sieve add-on, Roundcube's managesieve plugin or sieve-connect:

SettingValue
Serverthe mail server's host name, the same one used for IMAP
Port4190
SecuritySTARTTLS
User name and passwordthe full email address and its password

Apps that look up the _sieve._tcp SRV record find the server and port on their own when the domain publishes it (it is in the domain's DNS records list). The app can upload, rename, delete and check scripts and choose the active one; these are the same scripts JMAP Sieve and the API manage. A script that does not compile is refused with the line and column of the problem. The active script cannot be deleted until another one is turned on or filters are turned off. ManageSieve is not tied to a plan feature, just as JMAP Sieve is not. Outside development mode the server asks for STARTTLS before it accepts a password.

Users, from a mail app that supports JMAP Sieve (RFC 9661): upload the script as a blob, then create it with SieveScript/set and turn it on with onSuccessActivateScript. SieveScript/validate checks a script without saving it, and SieveScript/get and SieveScript/query list them. The active script cannot be destroyed until another one is turned on or onSuccessDeactivateScript turns filters off.

Administrators, through the API:

RequestWhat it does
POST /v1/sieve/validateChecks a script and says what is wrong, with the line and column.
GET / PUT /v1/tenants/{id}/sieveThe tenant rules. PUT with {"script": ""} removes them.
GET / PUT /v1/tenants/{id}/domains/{domain}/sieveA domain's rules.
GET / PUT /v1/shared-domains/{domain}/sieveA shared domain's rules.
GET / POST /v1/tenants/{id}/accounts/{accountId}/sieve-scriptsA user's scripts; create one, optionally "active": true.
GET / PATCH / DELETE .../sieve-scripts/{scriptId}Read, rename, replace, turn on or off ("active"), or delete one.

A script that does not compile is refused with a 422 naming the problem, so a typo never reaches delivery.

Limits

Not yet