---
title: "Rules and Sieve: what runs when, vacation replies and the Run function action"
url: https://www.hmailserver.co.uk/topic/28268/rules-and-sieve-what-runs-when-vacation-replies-and-the-run-function-action
author: "Progressiverobot"
published: 2026-09-27T19:50:13.731Z
updated: 2026-09-27T19:50:13.731Z
category: "Rules, Sieve & routing"
tags: ["sieve","smtp","administration","security","linux"]
site: hMailServer Community Forum
---

# Rules and Sieve: what runs when, vacation replies and the Run function action

*Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) holds a rule and a Sieve script to their owner: a domain's or an account's rule may no longer set `From`, `Sender`, `Resent-From`, `Resent-Sender`, `DKIM-Signature` or the loop count, and its Reply goes only from an address its owner may send as; a Sieve script may no longer add or delete those fields, `Auto-Submitted` or the loop count, and its `notify` goes from the account's own address unless its `:from` reaches the account. One stored before the upgrade still runs without that action or edit, and the application log names it. A loop count that is not digits alone, or is above the limit, reads as the limit, and `RuleLoopLimit` 0 now stops out-of-office replies too. See [the 6.3.7 announcement](/topic/28277).*



hMailServer filters mail with two systems that both run during delivery and do not know about each other. **Rules** are the server's own engine: criteria and actions, edited in the Control Panel, and the only one of the two that can run server-wide. **Sieve** (RFC 5228) is the standard mail filtering language, owned by the mailbox user and edited from a mail client over ManageSieve or on the Control Panel. This topic covers the order they run in, rules and the Run function action, Sieve and vacation, the out-of-office policy, and what both see on sealed mail. It describes 6.3.4 (27 September 2026, schema 6104). The one thing to know first: **they run at different points, they see the message at different stages, and when they disagree about where a message goes, Sieve wins.**

### The order they run in

The filtering pipeline for one inbound message:

1. **During the SMTP conversation**, while the sender is still connected. The pre-transmission spam tests (SPF, DNSBL, HELO, greylisting) run at `MAIL FROM` and `RCPT TO`; the script event `OnSMTPData` runs at the `DATA` command, before any of the body is sent; once the body is in come the post-transmission tests (SURBL, DKIM, ARC, SpamAssassin). A message over the delete threshold is refused, with `550` when the verdict came before the body and with `554` after it; with quarantine on, one judged after the body is held in quarantine instead and the sender answered `250`. Otherwise the script event `OnAcceptMessage` runs and the message is queued.
2. **On the delivery thread**, once per message and on every delivery attempt: `OnDeliveryStart`, blocked attachments and the virus scan, the **global rules**, `OnDeliverMessage`, then DKIM signing and mirroring (first attempt only).
3. The message is **split** into local and external recipients. External recipients go to relays, routes and MX delivery.
4. **For each local recipient**, on that recipient's own copy: per-account spam overrides, the account auto-reply, the **account rules**, account forwarding, trace headers, the **Sieve script**, the domain-wide auto-reply (only if the account said nothing), Sieve `reject` then `discard`, Sieve flags, the folder decision, and the save to the mailbox.

| Because | It follows that |
|---|---|
| Spam scoring happens during the SMTP conversation | A rule cannot make a message spam; it can only read the flag, or a header the filter wrote |
| Virus scanning happens before global rules | A rule never sees an infected message |
| Global rules run on the shared message, before the split | **Delete e-mail** in a global rule destroys the message for every recipient; in an account rule, only that recipient's copy |
| Global rules re-run on every delivery attempt | Forward, Create copy and Reply can fire more than once for a deferred message. Write `Delivery attempts equals 1` to prevent it |
| Sieve runs after account rules | A Sieve `fileinto` overrides the folder a rule chose. A header a rule set, or a copy it queued, has already happened and Sieve cannot undo it |

### What a rule is

**Rules** in the Control Panel sidebar is the server-wide set ("Global rules"); each account has its own on the **Rules** tab of its account dialog. A rule is a name, an enabled flag, a match mode - *Match ALL criteria (AND)* or *Match ANY criterion (OR)* - an ordered list of criteria and an ordered list of actions.

| Criterion | What is compared | Note |
|---|---|---|
| **From**, **To**, **CC** | The `From:`, `To:` and `Cc:` **headers** | Not the envelope. Mail that reached you by Bcc, an alias or a list has no `To:` naming you |
| **Body** | The plain-text body concatenated with the HTML body | A phrase inside an attachment is not matched |
| **Message size** | The file's size **in bytes** | 25 MB is `26214400` |
| **Recipient list** | Every SMTP envelope recipient, joined with `;` | The field that sees a Bcc, an alias expansion or a list member |
| **Delivery attempts** | The retry count plus one | The first attempt is `1` |
| **Custom header** | The named header's value | Name it without the colon |

**Match types.** `equals`, `does not equal`, `contains` and `does not contain` are case-insensitive. `is less than` and `is greater than` parse both sides as numbers, and a non-numeric string parses as `0`. `matches wildcard` and `matches regex` require the **whole value** to match; a regex is also **case-sensitive**, so to find a word anywhere write `.*invoice.*`, and `(?i).*invoice.*` to ignore case. **`does not equal` and `does not contain` are true when the field is empty**: `Subject does not contain [ok] -> Delete e-mail` deletes every message that has no Subject at all. Add a positive criterion under match mode ALL when the action is Delete.

| Action | What it does |
|---|---|
| **Delete e-mail** | Global: the message is dropped and `Message will be deleted. Action triggered by a global rule (<name>)` is logged. Account: only this recipient's copy |
| **Forward e-mail** | Queues a second message to the named address, loop-count guarded |
| **Reply** | Queues a new message to the envelope sender, stamped `Auto-Submitted`; never answers auto-submitted mail |
| **Move to IMAP folder** | Happens at the end of local delivery. `INBOX.Archive`; `#Public.Name` for a public folder, subject to the insert permission; `%YEAR%`, `%MONTH%` and `%DAY%` come from the server's local date. A missing folder is created |
| **Run script function** | Calls a function in the event script - next section |
| **Stop rule processing** | Stops the rule set after this rule's remaining actions have run |
| **Set header value** | Rewrites the message file; `%MACRO_ORIGINAL_HEADER%` is the header's current value |
| **Create copy** | Queues a duplicate carrying `X-CopyRule: <rule name>` |
| **Send using route**, **Bind to address** | Force a named route, or the local address the connection leaves from. Absent from an account's rules |

### The Run function action, since 6.3.4

The action the Control Panel calls **Run script function** calls the named function in the event script as `<function>(HMAILSERVER_MESSAGE)`, then reloads the message from disk, so a script that edits and saves is seen by the rest of the rule set. It needs scripting enabled.

**Since 6.3.4 the name must be a plain identifier**: ASCII letters, digits, `_` and `$`, not starting with a digit. On Windows, in either script language (VBScript or JScript), any other name is refused as `HM5018` each time the rule fires. Before 6.3.4 the name was written into the call unescaped, in either language, so whoever may edit a rule, a domain's own administrator included, could run code in the server's process: that is CVE-2026-100741, in every release from 6.0.0 to 6.3.3, and [event scripts and the 6.3.4 security fix](/topic/28261) has the flaw and what to do until a server is upgraded.

**On Linux** the action works since 6.3.4, where event scripts are JavaScript. The Linux engine looks a handler up by name instead of building a call, so such a name is never evaluated and finds no function: nothing is run, and the ERROR log has a Script Error line, not `HM5018`.

**Since 6.3.7** such a name is refused when the rule is saved, over COM and REST, for a rule of every owner; and the action on a domain's or an account's rule is the server administrator's to set, since it reaches a function of the server's event script: a credential confined to named domains - such as a domain administrator, or an API key issued for its domains - is refused it.

### Evaluation: a matching rule does not stop the set

For each **active** rule, in order, the criteria are evaluated under the match mode and, if they matched, every action runs. Only a **Stop rule processing** action ends the set, so make that the last action of every rule that should be terminal.

- **A rule with no criteria runs no actions**, in either match mode. A catch-all rule needs a criterion that is always true.
- **If the message file cannot be loaded, the whole set is skipped** and the message is delivered unfiltered, with `HM6040` in the log, rather than run against an empty message.
- When several choose a folder, a Sieve `fileinto` beats an account rule's folder, which beats a global rule's.

**The regular-expression breaker.** A regex runs against a value the sender wrote, so it has a budget of **250 ms** per evaluation. A pattern that will not compile is reported as `HM6042`; one that takes 250 ms or more as `HM6043`; either is suspended for **300 seconds**, keyed on the pattern text, so a corrected pattern takes effect on the next message. A suspended pattern **does not match**. The same breaker covers Sieve's `:regex`, and suspension is shared.

**Loop protection.** Every generated message carries `X-hMailServer-LoopCount`. Forward, Reply and Create copy check it against **RuleLoopLimit** (COM `Settings.RuleLoopLimit`; **Mail flow & delivery → Delivery of e-mail → Rules**, since 6.3.7 a card on the **Rules** page; default **5**); at or above it nothing is generated and `HM5064`, `HM5065`, `HM5201` or `HM4404` is logged. An `Auto-Submitted` header stops Reply and bounce reports only; Forward deliberately ignores it.

### What else 6.3.4 changed for rules

- **Three collections instead of two.** A **domain's rules** (`Domain.Rules`, `/api/v1/domains/{name}/rules`) run after the server's. Account rules are on `/api/v1/accounts/{address}/rules`.
- **Five more actions**: **Add a disclaimer**, **Reject with a text**, **Require TLS**, **Quarantine** and **Warn the sender**.
- **A [legal hold](/topic/28242)** keeps, among the account's deleted items, what an account rule's Delete or Reject, or a Sieve `discard`, `reject` or `redirect` that keeps no local copy, dropped. It does **not** reach the server's or a domain's rules: a Delete there drops the message for everybody, held accounts included.

A domain's rules do not always act on the whole message. [Mail flow rules and what a domain does to a message](/topic/28251) says when they act on all of it and when on the domain's own recipients only, what each new action does, and two 6.3.4 fixes to what a rule queues.

### Sieve: where a script lives and how it becomes active

Each account has **one active script**, plus any number of named scripts. They are files, not database rows, under `[Directories] DataFolder`:

```text
<DataFolder>\
  Sieve\
    _global\                    administrator-placed scripts, for include :global
    example.com\
      alice\
        active.sieve            THE ACTIVE SCRIPT - this is what delivery runs
        vacation.responses      who has already had a vacation reply, and until when
        duplicate.seen          identifiers the duplicate test has seen
        scripts\
          holiday.sieve         named scripts, from ManageSieve PUTSCRIPT
```

Four routes write that same `active.sieve`, and all four check the syntax first:

| Route | Where |
|---|---|
| Control Panel (Windows) | Account dialog, **Sieve** tab |
| COM (Windows) | `Account.SieveScript`; no `Save()` is needed |
| REST | `GET` and `PUT /api/v1/me/filters`, as the account itself; 256 KB cap; an empty script removes the filter |
| ManageSieve | `PUTSCRIPT` then `SETACTIVE` |

Deactivating a script also clears that account's vacation-response records.

**ManageSieve is off by default**: `ManageSieveServerPort=0` means the listener is never created. Set the port (the wiki's example is `4190`) and `ManageSieveServerBindAddress` on the **ManageSieve (RFC 5804)** card of the Control Panel's **API & monitoring** page on Windows (since 6.3.7 a **ManageSieve** tab of the **Protocols** page; Ctrl+K takes the old title to it), or on either platform with `PUT /api/v1/settings/ini/{name}` or `hmctl ini set`. Since 6.3.4 both wait for a reinitialise, not a restart: **Apply now** in the Control Panel or the Deck, or `POST /api/v1/server/reinitialize`. A script is at most **1 MB**. With no certificate, STARTTLS is not advertised at all; with no certificate and no range policy, keep the listener on `127.0.0.1`.

### What this server's Sieve accepts and refuses

`require` accepts `fileinto`, `envelope`, `copy`, `relational`, `subaddress`, `vacation`, `vacation-seconds`, `imap4flags`, `body`, `mailbox`, `regex`, `ihave`, `environment`, `date`, `index`, `spamtest`, `spamtestplus`, `duplicate`, `editheader`, `variables`, `reject`, `ereject`, `include` and `enotify`, and the comparators `comparator-i;ascii-casemap`, `comparator-i;octet` and `comparator-i;ascii-numeric`. What matters in practice:

- **`fileinto :copy` is refused at upload.** This server stores exactly one local copy. Use `:copy` on `redirect`.
- **`redirect` without `:copy` cancels the implicit keep**, and the message leaves the mailbox entirely.
- **`imap4flags` stores only the five system flags**, `\Seen \Answered \Flagged \Deleted \Draft`. A keyword is accepted, logged, and stored nowhere.
- **`spamtest`** reads the server's internal flag, never a header a sender could forge: `"0"` when the message was not classified, `"10"` when it was. Any threshold from 1 to 10 behaves identically.
- **`duplicate`** keys on `Message-ID` over 7 days by default, and its store fails **open**, because a wrong "duplicate" destroys mail.
- **`reject` and `ereject`** send a non-delivery report with status `5.7.1` to whoever the envelope names. Against forged senders use `discard`.
- **`editheader`** refuses `Received` and `Return-Path`, and since 6.3.7 `From`, `Sender`, `Resent-From`, `Resent-Sender`, `DKIM-Signature`, `Auto-Submitted` and `X-hMailServer-LoopCount`, at upload and at delivery, where a script stored before the upgrade still runs without the edit; **`enotify`** accepts `mailto:` only, and since 6.3.7 a `notify` goes from the account's own address unless its `:from` is the account's own address or an active alias pointing at it; **`include`** nests at most three levels.

The everyday script:

```sieve
require ["fileinto", "mailbox", "spamtest", "relational", "comparator-i;ascii-numeric"];

if spamtest :value "ge" :comparator "i;ascii-numeric" "5" {
    fileinto :create "INBOX.Junk";
    stop;
}

if header :contains "List-Id" "announce.example.com" {
    fileinto :create "INBOX.Lists.announce";
    stop;
}
```

### Vacation, and the out-of-office policy

```sieve
require ["vacation"];

vacation
  :days 7
  :subject "Out of the office until 22 September"
  :addresses ["alice@example.com", "a.smith@example.com"]
  "I am away until 22 September. For anything urgent please write to
support@example.com.";
```

`:addresses` is not decoration: a reply is sent only when one of the user's own addresses appears in the envelope or the headers, and that list is what "the user's own addresses" means. `:days` defaults to **7**; `vacation-seconds` gives `:seconds`, and `:seconds 0` means "reply every time". The window is per sender, in `vacation.responses`, and that store **fails closed**: if it cannot be read, no reply is sent.

The account's **Auto-reply** tab and the domain's **Out of office** tab are the server's own reply, separate from Sieve. **At most one reply answers any message.** The account's own, when it is on and inside its dates, comes first; a domain's reply answers only for accounts that have none. If both an account-level auto-reply and a Sieve `vacation` are configured, the account-level one wins.

| | Account | Domain |
|---|---|---|
| General text | `VacationSubject`, `VacationMessage` | `VacationSubject`, `VacationMessage` |
| Text for colleagues | `VacationInternalSubject`, `VacationInternalMessage` | the same two |
| Colleagues only | `VacationMessageInternalOnly` | leave the general text empty |
| Strangers get the domain's text | - | `VacationExternalOverride` |

A sender is *internal* when the envelope sender resolves to an account hosted here or to an active alias. That is whatever the submitting client said, so an internal text must never carry anything that cannot be shown to a stranger. Each sender is answered once per absence; a window - `VacationMessageDays` on the account, `VacationDays` on the domain, 0 to 3650 - answers the same sender again after that many days. The memory is the table `hm_autoreplies` (schema 6060), so since 6.3.4 a restart no longer repeats every reply.

**Never answered**: a null return path; `Auto-Submitted` other than `no`; `Precedence: bulk`, `list` or `junk`; anything with a `List-*` header; a sender that set `X-Auto-Response-Suppress`; `postmaster` and `mailer-daemon`; ourselves; and marked spam when `VacationMessageAbortSpamFlagged` says so; and since 6.3.7 a message whose `X-hMailServer-LoopCount` has reached **RuleLoopLimit**, so `RuleLoopLimit` 0 stops out-of-office replies as it stops every other generated message. Every reply goes out with a null return path and `Auto-Submitted: auto-replied`.

### What rules and Sieve see on sealed mail

A message encrypted end to end (S/MIME or PGP/MIME) is *opaque*: the server holds no key to it. How the server decides that, and what the rest of the server does with such a message, is in [end-to-end encryption and sealed mail](/topic/28260). For rules and Sieve:

- **A rule's body criterion on an opaque message does not match, whichever way it is phrased**, and one line per rule per message in the log says so. Under AND, a rule with a body criterion does not fire; under OR, the other criteria are still evaluated. Before 6.3.4, *does not contain* matched every encrypted message. A `multipart/signed` message is read as it always was.
- **To catch encrypted mail**, test the `Content-Type` header for `multipart/encrypted` or `application/pkcs7-mime` with a custom-header criterion.
- **Sieve's evaluation is unchanged; a log line is new.** An encrypted message has no text part, so `body :contains "x"` is false and `not body :contains "x"` is true, and the delivery writes a log line saying so, once per message and recipient. `body :raw` is the undecoded body exactly as it arrived, so `if body :raw :contains "BEGIN PGP MESSAGE"` is the honest way a script can recognise a sealed message.

### What it does not do

- **Sieve cannot run server-wide**, force a route or bind an outbound address.
- **A rule's Delete e-mail is silent.** The sender was already answered `250`. To tell the sender, use Sieve `reject`, or since 6.3.4 the **Reject with a text** action.
- **A [journal rule](/topic/28271) does not see a rule's forward, copy or reply, or a Sieve redirect**: the server queues those itself.

### Where to read more

- Wiki: [Rules and Sieve](https://gitlab.com/hmailserver/hmailserver/-/wikis/Rules-and-Sieve), [The COM API and Scripting](https://gitlab.com/hmailserver/hmailserver/-/wikis/The-COM-API-and-Scripting)
- Documents at the 6.3.4 tag: [OutOfOffice.md](https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/OutOfOffice.md), [SealedMail.md](https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/SealedMail.md), [MailFlowRules.md](https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/MailFlowRules.md), [EventScripts.md](https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/EventScripts.md)
- The public documentation: [chapter 12](https://www.progressiverobot.com/hmailserver-documentation/#12-sorting-mail-automatically-rules-and-sieve) and [the rules cookbook](https://www.progressiverobot.com/hmailserver-documentation/#27-rules-in-practice-a-cookbook)
- On this forum: the [6.3.4 release announcement](/topic/28218), [the Linux guide](/topic/28189), [mail flow rules and domain rules](/topic/28251), [event scripts](/topic/28261), and [when a saved setting takes effect](/topic/28222) for the ManageSieve keys
