Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) holds a domain's or an account's rule to its owner: it may no longer set From, Sender, Resent-From, Resent-Sender, DKIM-Signature or the loop count, and its Reply goes only from an address the owner may send as; such a rule is refused when it is saved, one stored before the upgrade runs without that action, and the application log names it. Its Warn sender notice, and the notice of a Reject met after acceptance, come from the postmaster of the rule owner's domain. A credential confined to named domains may no longer give a domain's or an account's rule a route, a bound address or a script function, and a Run function that is not a plain function name is refused when the rule is saved. A rule's forward of mail from outside whose From names a hosted domain's organisation is given an envelope sender in that organisation only when the message passed DMARC for it on arrival; otherwise it keeps the envelope sender it came with or, where that is itself at this server's host name or at a hosted domain in the organisation its From names, is not sent to an outside address at all, and the application log names each recipient. A message over 80 MB goes through the rules by its header alone: the conditions that need the body do not match, a script function is not run, and the disclaimer and the signature are not appended. See the 6.3.7 announcement.
This topic covers the rules the server runs on every message, the conditions and actions 6.3.4 added to them to make an outbound content policy, and the three things a domain can be told to do to the mail passing through it: mark what comes from outside, say when a sender has never written before, and put a legal footer on what leaves. The one thing to know first is that these run at three different moments, and the moment decides what each can see. The server's and the domains' rules run in the deliverer on every delivery attempt, with reject also asked at the end of DATA, and an account's own rules once for their recipient; the external tag and the first-contact note are applied to the recipient's own copy at local delivery; and the disclaimer goes on at submission, before this server's DKIM signature is made.
Three collections of rules
Collection
Where it is edited
Runs for
The server's
Application.Rules, /api/v1/rules, the Control Panel's Rules page, the Deck's
every message, first
A domain's (since 6.3.4, schema 6063)
Domain.Rules, /api/v1/domains/{name}/rules, the same pages with the domain chosen at the top
every message with a recipient in the domain or a sender it hosts, after the server's
An account's
Account.Rules, /api/v1/accounts/{address}/rules
that account's mail, at local delivery
The server's and the domains' rules run in SMTPDeliverer on every delivery attempt, after virus scanning and before DKIM signing and the split between local and external delivery. An account's own rules run after the split, at local delivery, on that recipient's own copy: once for their recipient, when its copy is stored. All three have the same conditions and actions, except that the wiki's Rules and Sieve page lists two actions, send using a route and bind to an address, as absent from an account's rules. Since 6.3.7 a domain's or an account's rule may not set From, Sender, Resent-From, Resent-Sender or DKIM-Signature, which are for the server's rules alone, nor the loop count, which no rule sets, and its Reply goes only from an address the owner may send as: one the account may send as for an account's rule, an address of the domain or of one of its domain aliases for a domain's; a credential confined to named domains may not give such a rule a route, a bound address or a script function. A rule stored before the upgrade runs without the refused action, and the application log names it; to set From on a domain's mail, make the rule a server rule with a condition on the domain. A domain's rules are how a domain administrator has rules of their own: a domain-restricted key reaches its domain's rules and no other's. The Control Panel and the COM objects are Windows-only; the Deck and REST serve both platforms.
A repeated pass does not repeat the side effects. A pass that repeats an attempt because this server's own outbound shaping held the message, without counting a try, runs the rules again but does not forward, copy, reply or call a script function again. CHANGELOG records the fix: before it, a held message sent one copy per pass.
What a rule queues goes at once. Since 6.3.4 a rule's forward, copy, reply or notice to the sender, and an account's own forwarding, no longer wait up to a minute in the queue: each is queued inside the delivery that caused it, and the delivery manager is now told of it, where before it sat until something else woke the manager.
A domain's rules act only on what is that domain's. Domain rules are new in 6.3.4 and shipped with this limit.
The sending domain's rules act on the whole message. The sending domain is that of the account that submitted the message (SMTP AUTH, the webmail, a scheduled send), and the envelope sender's domain when that is vouched for: its account submitted the message, DMARC passed for the domain on mail from outside, or the message reaches no other domain's mailboxes. An envelope sender nobody vouched for does not choose a domain's rules for mail to another domain's mailboxes.
A recipient domain's rules act on the whole message only when every recipient is its own. Otherwise they act on the domain's own recipients: a delete or a reject takes those recipients off the message, a quarantine keeps a copy for them under the rule's name, and a copy goes to them alone. Such a reject is met at delivery, not in the SMTP session, whose one answer is for every recipient; a sender this server hosts is mailed a notice naming the recipients refused. A header, a disclaimer, a script function, a route, a bound address and required TLS are not applied, since they would change the message for everybody.
As domain rules were first written in the source tree, without this limit, one tenant's delete, reject, header or route reached another tenant's recipients; no release had that.
Conditions and actions
The long-standing conditions (From, To, CC, Subject, body, size, the recipient list, delivery attempts, any named header), the match types, the long-standing actions and how a rule set is evaluated are the rule engine's, and are set out in Rules and Sieve: what runs when. Since 6.3.4 there is one more condition, the attachment name (attachment_name every attachment's file name, with ; between them, so contains .exe or a wildcard *.zip reads as "any attachment is".
Run a script function takes a plain function name since 6.3.4, without a switch: ASCII letters, digits, _ and $, not starting with a digit. On Windows any other name is refused as HM5018 each time the rule fires; on Linux it finds no function, nothing is run, and the error log has a Script Error line. Since 6.3.7 such a rule is also refused when it is saved, over COM and REST, for a rule of every owner. Give every such rule a plain function name before you upgrade, and look for HM5018 after; why it changed is in event scripts and the 6.3.4 security fix and the 6.3.4 security announcement.
Since 6.3.4 there are five more actions:
Action
REST word
What it does
Add a disclaimer
add_disclaimer
Appends the text to the message's plain and HTML parts when the rule matches, with X-hMailServer-Disclaimer naming the rule. The domain disclaimer stays the unconditional footer it was
Reject with a text
reject
The SMTP session answers 550 5.7.1 with the text before the message is accepted. A reject with no text of its own names the rule
Require TLS
require_tls
Every external delivery of the message needs STARTTLS. A destination that offers none is deferred with a reason naming the rule; the message is not sent in the clear
Quarantine
quarantine
Held in the quarantine under Rule: <name> for an administrator to release or delete. A quarantine that could not store it delivers the message rather than lose it, and says so in the error log
Warn the sender
warn_sender
The message goes where the rule's other actions send it, and a sender this server hosts is mailed a notice
Where reject is asked. The server's rules, and those of every domain whose rules act on the whole message, are asked at the end of DATA, and the message is parsed for the match only when one of them has a reject action at all. The webmail's send route asks the same rules before it queues, and answers 403, code policy_rejected, with the text. A message that reached delivery another way is dropped, as a delete is, with the rule named in the log.
The outbound content policy
Since 6.3.4. What Exchange calls data loss prevention is here four more conditions and one more action in the same rules, with no second engine. Each condition answers as a list with ; between the entries, so contains x reads as "any of them is x" and equals "" as "none".
Condition
REST word
Answers
Sensitive content
sensitive_content
The detectors that found something in the readable text: card_number, iban, account_number
Attachment type
attachment_type
Every attachment's type from its first bytes, not its name: executable, elf, pdf, zip, office_xml, jar, office, rar, 7z, gzip, png, jpeg, gif, rtf, script, text, empty, binary
External recipients
external_recipient
Every envelope recipient this server does not host; empty when every recipient is local
Readable text
content
The text itself, for a pattern of the rule's own
The readable text is the plain part, the HTML part with its tags removed, and every attachment whose bytes are text, a .csv sent as application/octet-stream included. It is bounded at a megabyte.
The detectors.
card_number: a run of 13 to 19 digits, with at most one space or hyphen between any two, that passes the Luhn check and is not one digit repeated. A run longer than 19 digits is not searched for a card inside it.
iban: two letters of a country in the IBAN registry and two digits, then exactly that country's number of characters, passing the ISO 13616 check.
account_number: the regular expression in the dlp_account_number_pattern setting, such as \b\d{2}-\d{2}-\d{2} \d{8}\b for a sort code and account. Empty, the default, is a detector that never fires. A pattern that does not compile is reported as HM6821, and a search given up as too complex, or one that takes over the 250 ms budget, as HM6822; either way the pattern is rested for five minutes, finding nothing meanwhile. That is fail-open, on purpose and said out loud.
Marked internal needs no new condition: header Sensitivity contains Company-Confidential, combined with external_recipient contains @.
A sealed message, encrypted end to end and judged from its body rather than its label, has no text the server can read, so sensitive_content and content do not match it, whichever way they are phrased, and the application log says why once per rule. attachment_type and external_recipient still answer.
A worked policy. From the document:
POST /api/v1/rules
{
"name": "No card numbers leave",
"all_criteria": true,
"criteria": [
{ "field": "external_recipient", "match": "contains", "value": "@" },
{ "field": "sensitive_content", "match": "contains", "value": "card_number" }
],
"actions": [
{ "type": "reject", "value": "Card numbers may not be sent by mail. Use the payment portal." }
]
}
A rule with external_recipient contains @ fires only on a message with a recipient this server does not host; a rule without that condition applies to inbound mail too. A policy is a rule of the server or of the sender's domain. It cannot be an account's rule, because those are the recipient's own filters and never see the account's own outgoing mail.
The notice warn_sender sends comes from postmaster@<their domain> (to 6.3.6; since 6.3.7 from the postmaster of the rule owner's domain, and for a server rule of the default domain when one is set and hosted here, else of the hosted domain the message was addressed to, else of the sender's own, signed as that domain) with the subject Policy notice: <rule name>, the headers X-hMailServer-Policy: <rule name> and X-hMailServer-Policy-Outcome: delivered, and Auto-Submitted: auto-generated. It is sent only when this server hosts the sender's domain, because a notice to an outside sender is backscatter, and it goes once per message, on the first delivery attempt. A rule with only that action delivers the message.
A released message is not held again by the rule that quarantined it; the rule's other actions still run.
What the policy does not do. It is pattern matching, and patterns have false positives: the document says one 16-digit reference number in ten passes Luhn, and recommends warn_sender or quarantine as the first choice for a new pattern rather than reject. It reads text, not documents: a card number inside a PDF, a Word file or a spreadsheet is not found, an image of a card is an image, and an encrypted zip is zip. There are no fingerprints, classifiers or dictionaries, no per-user exceptions, and no policy tip in the sender's mail client before they press Send.
What a domain does to a message
Since 6.3.4 (schema 6044). Three per-domain features, off on every existing and every new domain, costing nothing while off. They are set in the Control Panel's domain dialog, tab Tagging and disclaimer (Windows only); in the Control Deck's domain editor, What this domain does to a message; or with PUT /api/v1/domains/{name} and the keys external_tag_subject, external_tag_header, external_tag_text, first_contact_tip, disclaimer_enabled, disclaimer_plain_text and disclaimer_html.
Who counts as outside is one rule, MessageOrigin::IsExternalSender. A sender is outside when none of these is true:
The session authenticated. This is read from the topmost Received: header this server wrote; X-AuthUser is deliberately not consulted.
A trusted incoming relay handed the message over.
Every address the message names, the envelope sender and the From: address, is at a domain this server hosts. Either being outside is enough.
The sharp edge: an inbound gateway. If you run a filtering gateway in front of this server and list it as an incoming relay, nothing arriving through it is ever tagged. The choice is between the tag and the relay declaration; the document says there is no third answer today.
It is not anti-spoofing. A message that forges a hosted address in both its envelope and its From is judged inside and is not tagged. The control for that is DMARC with a policy on your own domain.
Switch
What the reader gets
Cost
external_tag_subject
The tag text at the front of the Subject: external_tag_text, at most 100 characters, [EXTERNAL] when empty. A subject that already begins with it gets no second copy
Breaks any DKIM signature the sender made over the Subject for anyone who verifies downstream, as the spam subject prefix does. The verdict this server reached is unaffected
external_tag_header
X-hMailServer-External: YES, which the webmail shows as This message came from outside your organisation.
None to the signature. Prefer this wherever the reader's client can show it
first_contact_tip
X-hMailServer-First-Contact: YES when an outside sender writes to an account that has had mail from other people but never from them. The webmail shows You do not usually get mail from this sender.
One indexed SELECT per delivered copy, then an UPDATE, or a further SELECT and an INSERT the first time
The tag is applied to the recipient account's own copy, above the account's rules and its Sieve script, so both can match on the header, and below the per-account spam settings. A domain with tagging on does not share one delivered file between several recipients (DeliveryHardLinks). Where a domain has turned the feature on, a forged copy of the header on a message judged inside is removed.
The first-contact memory is the table hm_knownsenders, one row per account and sender. When an account that signed in sends a message, each recipient is recorded as somebody that account knows, so their reply is not announced; an unauthenticated session records nothing. The wiki adds that mail sent from the webmail or by send-later does not record its recipients, so a reply to a message first sent that way can be announced once. Two limits are by design. After a cold start, a new installation or the day the switch is turned on, most senders look new; the document says to expect a week. After a restore, the memory is as of the backup.
The domain disclaimer
disclaimer_enabled appends the domain's legal footer to mail leaving the organisation. Filling in only disclaimer_plain_text uses it for both forms. It runs where the domain signature runs, before DKIMSigner::Sign, so this server's own signature covers the footer. The footer is appended to the text/plain part and the text/html part in place; a message with neither is left alone.
It is never added to:
a signed or encrypted message (multipart/signed, multipart/encrypted, application/pkcs7-mime, application/x-pkcs7-mime, application/pgp-encrypted or application/pgp-signature), which is sent exactly as written, with a line in the application log;
an automatic reply, a vacation message or a delivery report;
a bounce;
a list posting (List-Id, List-Unsubscribe, or a Precedence: of bulk, list or junk);
a message with no recipient outside this server;
a message this server already footed, which carries X-hMailServer-Disclaimer;
a message relayed in from outside that merely claims a hosted sender domain.
A reply quoting a message that already carried the footer does not collect a second copy. A footer shorter than eight characters is never treated as already present.
Webmail and send-later get the footers too, since 6.3.4. The domain's signature and disclaimer go on mail sent from the webmail (POST /api/v1/me/messages, which is also its reply, forward and phishing report) and from its send-later, through the same function as SMTP (docs/DomainTransforms.md; CHANGELOG). In 6.3.3 and earlier only the SMTP path had either footer, which is what the wiki's Stopping Spam §10.7a still describes.
Two sharp edges on the webmail paths. Send-later sends with the account as the envelope sender, so a draft scheduled with a chosen From at another hosted domain gets the account's domain's footers. And with the domain's signature method set to overwrite the account's, the webmail has already written the account's signature into the body, which the server does not take out.
Signed and encrypted messages get no signature either, since 6.3.4. Before, a signature was appended inside a multipart/signed, breaking the S/MIME signature. This is a change on the SMTP path too: an installation that relied on a signature inside signed mail will now see none there.
Where to read more
Sieve, the account's own filters and the rule engine itself are in Rules and Sieve; the blocked-attachment list and the sandbox are in the topic on viruses and dangerous attachments. The two browser surfaces have topics of their own: The Control Deck and The webmail portal at /portal.
https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/MailFlowRules.md
https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/OutboundContentPolicy.md
https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/DomainTransforms.md
Wiki: https://gitlab.com/hmailserver/hmailserver/-/wikis/Rules-and-Sieve and, for the tag and the note, https://gitlab.com/hmailserver/hmailserver/-/wikis/Stopping-Spam
Public documentation: https://www.progressiverobot.com/hmailserver-documentation/#121-hmailserver-rules and https://www.progressiverobot.com/hmailserver-documentation/#27-rules-in-practice-a-cookbook