Skip to content
hMailServer 6.3.8 is the current release, signed 2 November 2026. Windows and Linux, and an in-place upgrade from 5.3 or later, but for the original hMailServer's 5.7.Download 6.3.8 Documentation FAQ
  • 0 Votes
    1 Posts
    128 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) regroups the Control Panel's sidebar: the Journaling and dual delivery page is Journaling & dual delivery, in the new Compliance group rather than under Monitoring & troubleshooting as the table below has it, and Ctrl+K takes the old title to it. See the 6.3.7 announcement. A journal rule sends a copy of a domain's mail, or of one account's, to an address. Exchange calls it journaling and sends an envelope report with the message attached; Google calls it dual delivery and sends the message itself. In hMailServer they are one mechanism with two shapes of copy, new in 6.3.4 (27 September 2026, schema 6104; the rules themselves arrived with schema 6075). This topic covers what a rule says, the two shapes, when a rule is evaluated, how it avoids looping, where it is edited, and what it deliberately does not do. The one thing to know first: a journal rule covers mail that comes in through the SMTP session, a fetch account or the send API, and nothing this server queues itself. A compliance archive built on a journal rule therefore has known gaps, and they are listed exactly below. Nothing is journalled until a rule exists. With an empty table the whole of this costs one comparison per accepted message. What a rule says Field Meaning scope domain or account: whose mail the rule is about target The domain name, or the account address. It must be a domain this server hosts, or an account in one direction inbound (a message with a local recipient the rule covers), outbound (a message from an authenticated sender it covers), or both journal_address Where the copy goes. One address, local or anywhere else, checked as an address before the row is written. It may be a mailbox in the very domain the rule journals; it may not be the account an account rule names mode report or copy enabled A rule that is off copies nothing and keeps everything it said note Why the rule exists. At most 255 characters The rules are stored in hm_journalrules. The row holds the domain id and, for an account rule, the account id, so nothing breaks when a domain or an account is renamed under it. A domain's rules go with the domain when it is deleted, and a rule naming an account goes with the account. A rule has no conditions. It covers all of a domain's or an account's mail in the direction it names. There is no "only messages over 10 MB" and no "only from these senders"; a mail flow rule is the place for that. The two shapes report is the Exchange journal shape: a multipart/mixed message whose first part is the envelope report and whose second part is the original message attached as message/rfc822, written through byte for byte. The report is four fixed fields, one per line, in one order: Sender: [email protected] Subject: Quarterly figures Message-Id: <[email protected]> Recipient: [email protected] Recipient: [email protected] The envelope is the point. A message's To and Cc headers are what the sender typed; the Recipient: lines are where the message actually went, blind copies and expanded lists included. A message with no envelope sender - a bounce - writes Sender: <>. copy is the message itself, as it was accepted, with one header line (X-hMailServer-Journal) in front of it and nothing else changed: not the body, not its transfer encoding, not its signature. The file is written byte for byte rather than through the MIME writer. What a migration or a second mail system wants is the mail, not a report about it; that is what copy is for. Both shapes are queued through the ordinary delivery queue, so a journal address somewhere else is delivered, retried and bounced as any other mail is, and a local one lands in a mailbox. The envelope sender of a journal message is the journalled domain's postmaster, not the person who wrote the message. A journal address that stops answering is the administrator's problem, and bouncing it to the sender would tell somebody outside this server that their mail is being copied. When a rule is evaluated Once per accepted message, in the SMTP session, after the row is saved and the message file is settled - the same place, and the same recipient list, the on-disk archive decision is made from. Not at delivery: the deliverer runs again for every retry and for every route a message splits into, and a compliance archive that gained a second copy because a remote host was busy would be worse than useless. One report or copy per matching rule per message, not per recipient. A domain rule that matches a message addressed to three accounts of the domain sends one report, with all three on it. There are three points of entry where rules are evaluated: The SMTP session. The outbound half judges an authenticated sender; this is what a desktop or phone client uses. A fetch account. Mail a mailbox collects from another provider by POP3 or IMAP is journalled once per message, at the same point in that path. A download has no authenticated sender, so it is the inbound half of a rule that speaks for it, and it has no SMTP session, so its lines go to the application log. The send API, POST /api/v1/send, which is new in 6.3.4 and is journalled in it (CHANGELOG lists the fix in its 6.3.4 section). The key's sender counts as the sender an outbound rule judges when that sender is an account here. What a journal rule does not see A message the server queues itself is queued past all three points and is not journalled in either direction, even when it is addressed to a mailbox a rule covers: Not journalled Note A message the webmail queues directly POST /api/v1/me/messages A send-later draft A message written by an event script A mail flow rule's forward, copy or reply An automatic reply A Sieve redirect An account's forwarding A delivery report Two consequences follow, and both matter to anyone relying on this for compliance: The outbound half covers SMTP submission and the send API, and not the browser. Mail a person sends from the webmail is not journalled by an outbound rule. The inbound half covers mail from outside this server, and not what the server sends on to a mailbox of its own. Two further limits of the same kind: A message that was refused is not journalled. A message rejected at RCPT TO or by a rule never existed as far as this is concerned. A message held for review is journalled, because it was accepted. Not looping There is one guard, and it takes nobody's word for anything. Every journal message this server writes carries X-hMailServer-Journal, and its value is the rule's id, a full stop, and a mark: 128 random bits the server process chose when it first needed one. The evaluation reads that header before it does anything else and stops only when the mark is this process's own. A journal message that comes back into this server - relayed by the far end, forwarded by the journal mailbox's owner - is never journalled again, and the guard stops the copy, not the delivery: the mailbox still gets its mail. A message carrying the header with any other value is journalled exactly as it would have been without it, and the attempt is noted in the log. The header's name is in the published document, so a guard that honoured the header as written would let anybody under an outbound compliance rule keep a message out of the archive by typing one line into their mail client. An archive that the people it is about can opt out of is not an archive. Three properties of the mark to understand: It is per process, and nothing is stored for it. A restart forgets the marks it wrote, so one of this server's own reports that returns after a restart is journalled once more, and the report that makes carries a mark the running process does know, so it stops there. A loop cannot run away; the price of keeping no secret on disk is one duplicate. It is not a signature over the message. It travels in the messages it marks, so whoever can read this server's journal mail - the journal address itself, and anything relaying to it - could quote a mark back in a message of their own and keep that one message out of the archive. If that relay is a worry, journal to a local mailbox, or over an authenticated route. There is no second guard, and no rule is skipped for where its journal address points. Journalling a domain into a mailbox of that domain works. example.com to [email protected] sends one report per message into that mailbox and nothing cascades, because a journal message is written straight into the delivery queue and never passes an evaluation point. It used to be refused at run time, silently: the rule matched, the copy was dropped with a line in the log, and every surface went on showing the rule as enabled. That guard went. The one arrangement refused is an account journalled into itself, and it is refused when the rule is written rather than found out later: it would put every message in the mailbox twice and archive nothing anywhere. Where it is edited Journal rules are the server administrator's. A rule says that somebody's mail is copied to an address outside their sight, so a credential confined to named domains is refused even though a rule names one. Surface How Platform REST GET/POST /api/v1/journal-rules, PUT/DELETE /api/v1/journal-rules/{id} Windows and Linux The Control Deck The Journaling view: every rule, a switch on each row, and a form to add one Windows and Linux The Control Panel The Journaling and dual delivery page, under Monitoring & troubleshooting (since 6.3.7 Journaling & dual delivery, in the Compliance group; Ctrl+K takes the old title to it), over three Utilities methods (GetJournalRules, SaveJournalRule, DeleteJournalRule) Windows only hmctl hmctl journalrule list\|create\|update\|delete A client over the REST API PowerShell module Get-HmJournalRule, New-HmJournalRule, Set-HmJournalRule, Remove-HmJournalRule Over the REST API Over REST, reading needs view_configuration and writing needs manage_server, so the auditor role reads the rules and writes none. Every surface goes through one function on the server, Journaling::SaveRule, so the three small vocabularies (domain/account, inbound/outbound/both, report/copy) mean the same thing whichever you use, and a refusal reads the same sentence. Writing, changing or removing a rule is on the audit trail. Setting up a compliance copy for one domain: Decide the journal address. A mailbox in the same domain, such as [email protected], is allowed; an address elsewhere is delivered through the ordinary queue. Create one rule with scope domain, target the domain name, direction both, mode report and the journal_address. Over SMTP - from a mail client signed in to an account, or from outside, and not from the webmail - send one message to two recipients of the domain, one of them as a blind copy, and open what arrives at the journal address: one report for the message, with a Recipient: line for each envelope recipient. Then send one message from the webmail and confirm that it is not journalled, so that whoever owns the archive knows the gap is there. A rule is refused before it is written for an unhosted target, a word outside the three vocabularies, no journal address, one that is not an address, one carrying a line break, and an account journalled into itself. Journaling beside three other features Sealed mail. The journal keeps the bytes verbatim, so a message that was encrypted end to end still decrypts from the copy - but nobody reviewing the journal can read its content. Restricted delivery. A journal rule's copy of a restricted account's mail goes where the rule says, because the copy is the administrator's and not the account's forward. Rules and Sieve. A rule's forward, copy or reply and a Sieve redirect are among the messages the server queues itself, so they are not journalled. What it does not do It is not the archive. ArchiveDir and ArchiveDomains keep files on this server; a journal rule sends mail elsewhere. Neither replaces the other, and turning one on does not turn the other off. It is not eDiscovery. A case searches the mailboxes, their deleted items and the archive on this server, and a journal copy has left it. A journal address that is a mailbox here is searched like any other custodian. It is not an account's forwarding. A forward is the account holder's, and can deliver instead of the mailbox. A journal copy is the administrator's, and never changes what the mailbox gets. It is not a mail flow rule's forward or copy action. Those have conditions, run per delivery attempt, and can be edited by the account. It is not retention. Once a journal message leaves the queue it belongs to whatever receives it. This server keeps no second copy, expires nothing and withdraws nothing; deleting a rule stops the next copy and does not reach the ones already sent. It is not encryption or redaction. A journal address outside your control sees everything the recipient sees. Where to read more The document at the 6.3.4 tag: Journaling.md, with Discovery.md, SendApi.md, SealedMail.md and RestrictedDelivery.md Wiki: Routes and Relays (section 14.2d) and Compliance and eDiscovery On this forum: the 6.3.4 release announcement; Rules and Sieve for the forwards and redirects a journal rule does not see; restricted delivery; the send API; compliance, eDiscovery and legal holds; and the archive
  • 0 Votes
    1 Posts
    132 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) signs an authenticated submission as another hosted domain only when the account may send as the address its From names - its own, an alias pointing at it, a mailbox that granted it the post right, an address it proved, or a masked address of its own; otherwise the mail is sent unsigned as that domain, so give such an account the alias or the grant before upgrading. Mail from outside whose From names a hosted domain is no longer signed as it, nor is an alias's or a distribution list's copy of it to an outside address; where such a message did not pass DMARC for the organisation its From names when it arrived, a copy whose envelope sender is at this server's host name, or at a hosted domain or a subdomain of one, in that organisation is not sent where an alias or a distribution list expands to an outside address, a managed list does not distribute a posting whose From names the list domain's organisation, and a distribution list's bounce address is not given to it; a managed list's copies of a posting from outside are not signed as the list's domain when an address of the list domain's organisation is in its From fields. Creating a domain alias is now the server administrator's: a credential confined to named domains is refused it, over REST and over COM. See the 6.3.7 announcement. There are three ways of making one address reach somewhere other than a mailbox of the same name - an alias, a distribution list and a route - and one way of giving several people the same folder. This topic covers all of them as they are in 6.3.4 (27 September 2026, schema 6104): aliases, domain aliases and plus addressing; distribution lists, who may post to them and how moderation works; the membership query, which lets a list define its own members; the list manager, which turns a distribution list into a mailing list people join and leave themselves; and public folders with their permissions. The membership query and the list manager are new in 6.3.4; the distribution list itself is in every release. The one thing to know first: the address is resolved in one function (RecipientParser::CheckDeliveryPossibility), in a fixed order, and an account always wins. The order an address is resolved in At RCPT TO the server works through this, and stops at the first answer: Domain aliases rewrite the domain. Plus addressing trims the local part. Account? If found, deliver to the mailbox. Alias? If found, follow it and start over, up to 25 hops. Distribution list? If found, expand to the members, or to the moderator. Route? If found, send to the route's target server. The domain's postmaster address, the catch-all: when nothing above matched, the resolution starts again with that address, which is why a mistyped address is delivered there rather than refused. If it is empty, 550 Unknown user. Since 6.3.4 one question is asked before all of these: is the address, or its domain, routed to a webhook? A webhook route that only posts takes the address's mail instead of its mailbox, alias or list - a list whose address is routed never sees the message, nor applies its posting rules to it. Route the addresses you mean rather than a list's whole domain; mail in to a webhook has the routes themselves. Aliases An alias is a second address that delivers to an existing target. In the Control Panel: Accounts & domains → Domains, select the domain, then the Aliases panel. The Control Panel and COM are Windows-only. On Linux, where there is neither, the Control Deck is the administration people click through, and it is the front end of the REST API, which both platforms have: What REST route A domain's aliases GET and POST /api/v1/domains/{domain}/aliases; DELETE /api/v1/aliases/{address} A domain's other names GET and POST /api/v1/domains/{domain}/domain-aliases; DELETE on .../{name} beneath it A domain's lists GET and POST /api/v1/domains/{domain}/lists; PUT and DELETE /api/v1/lists/{address} A domain's plus addressing PUT /api/v1/domains/{domain} (plus_addressing_enabled, plus_addressing_character) The Control Panel paths and COM properties below are for Windows. Field COM property Meaning Alias address Alias.Name The address people send to. Must be in this domain Target Alias.Value May be anything: another account here, another alias, a distribution list, an address covered by a route, or an address at a different server Active Alias.Active Unticked: RCPT TO is answered 550 Alias is not active. - not passed on to the catch-all The target replaces the address and resolution starts again, so an alias may point at an alias, which may point at a list. Beyond 25 hops the answer is 550 Mail server configuration error. Too many recursive forwards. An alias is only consulted when no account has that address. Creating sales@ as an account after sales@ exists as an alias silently changes where the mail goes. An alias to an external address is treated as inbound mail, not as relaying, so [email protected] → [email protected] works for external senders without opening a relay. Since 6.3.4 three more kinds of second address exist, and they are the person's own rather than the administrator's: the alias a mailbox move leaves (renaming or moving a mailbox keeps the old address as an alias of the new one by default); masked addresses (schema 6067), which the person makes in the webmail for one shop or sign-up and which, switched off, are refused as an address that never existed; and verified send-as (schema 6066), which receives nothing here - it is a From address, not an alias. Domain aliases. Accounts & domains → Domains → Properties → Names. The rewrite is total: [email protected] becomes [email protected] before anything else is looked at. You do not create per-address aliases for an alias domain; the sender address is rewritten too, which is what lets a list in membership mode recognise a member who posts from the alias domain; and the alias domain needs its own MX record. Since 6.3.7 adding one is the server administrator's: a credential confined to named domains - such as a domain administrator, or an API key issued for its domains - is refused, over REST and over COM, because a domain alias decides what names the server accepts mail for. Plus addressing is off by default and enabled per domain on Accounts & domains → Domains → Properties → Limits (default character +). With it on, [email protected] resolves to [email protected], and the full original address is still in the To: header. It applies to the recipient address only, after domain aliases and before the account, alias and list lookups. When off, alice+invoices@ is an unknown address. Distribution lists A distribution list delivers one message to many recipients. In the Control Panel: the Distribution lists panel of the domain, then Properties and Members. Mode Stored value Who may post Refusal Public - anyone can send 0 Anyone at all - Membership - only list members can send 1 Any address on the member list, after domain aliases are applied to both sides 550 Not authorized sender. Announcements only 2 Only the address in Require sender address 550 Not authorized owner. Anyone in the domain can send 3 Any sender whose domain is the list's own 550 Not authorized domain. (on any mode) Require SMTP authentication flag An unauthenticated session is refused first 550 SMTP authentication required. "Anyone in the domain" is a claim, not a proof. MAIL FROM is free text, so an outsider can assert a local address. Tick Require SMTP authentication with that mode or the list is open to the Internet. A fifth mode, 4, "Anyone with a server account can send", existed only in the type library and behaved as Public; put_Mode now refuses the value and the option is gone from the dialog. One more check runs for every mode, Public included: the poster must be allowed to reach every recipient of the list, so a list containing a members-only list the poster is not in is refused as a whole. Moderation. Set a Moderator address and a posting the rules above would refuse is accepted with 250 and forwarded to the moderator instead, stamped X-hMailServer-Moderation. The moderator approves it by resending it to the list from an authenticated session; that authentication is the security boundary and is unconditional. Three failures are numbered in the log: 6360 (the moderator was removed between the check and the expansion), 6361 (the moderator address does not resolve; the recipient is refused with 550) and 6363 (the list is its own moderator). Bounce address. Empty means bounces go to the poster. Set, it becomes the envelope sender of every copy, so a dead subscriber's bounce reaches the list owner, and an external member's server evaluates SPF against the list's own domain. A bounce address pointing at the list itself is ignored, with error 6362. Headers. An unmanaged list adds List-Id, List-Post (the literal NO for an announcements-only list), List-Help, List-Owner and a mailto:-only List-Unsubscribe, before DKIM signing. Nothing is added when the message already carries a List-Id, or when the list or owner address is not US-ASCII. A list that defines itself: the membership query, since 6.3.4 Schema 6074. A list can carry a membership query: one short text that says who its members are, answered against the server's accounts at the moment a message is sent to it. [email protected] with the query domain reaches every active account of the domain on the day, including the person who joined this morning and not the one switched off last night. Query Members (empty) A static list: the addresses under Recipients and nothing else domain Every active account of the list's own domain domain:other.example Every active account of that domain, which this server has to host pattern:sales-*@example.com Every active account whose address matches the wildcard; * is any run, ? exactly one, case ignored everyone Every active account of every active domain on the server It is a union with the list's own recipients, never a replacement. An address that is both is delivered to once. It answers accounts only - not aliases, other lists or outside addresses - and never a disabled account or an account of a disabled domain. It is resolved once per SMTP transaction, not once per RCPT TO, and nothing is stored. Who may set which form. A domain administrator, or an API key with a domain scope, may set domain or a pattern: in a domain it holds. everyone, domain: naming another tenant's domain, and a pattern whose domain part is itself a wildcard are the server administrator's. The same rule governs reading the answer: GET /api/v1/lists/{address}/members answers 403 to a confined credential when the query reaches past its domains. A query is judged when it is saved. domain: naming a domain this server does not host, a pattern: with no @, or any text that is none of the five forms is refused, by the same sentence over COM, over REST and in the Control Panel. "Answers nobody" and "could not be answered" are different. A query whose named domain has been deleted is not an error at delivery: the message goes to the list's rows and code 6980 is logged. But when the accounts cannot be read - the database locked by a backup - the recipient is refused 451 4.3.2 and the sender retries, with code 6982, and the members route answers 503. It is set as Membership query in the Control Panel's list dialog and the Control Deck, membership over REST, DistributionList.Membership over COM, and hmctl list update [email protected] --set membership=domain. The Deck's list editor shows Delivers to now, each address marked Kept by hand or From the query. The list manager: a mailing list, since 6.3.4 Schema 6092. A distribution list with the list manager switched on is one people join and leave themselves. Nothing changes for a list until it is switched on: every list an upgrade finds is unmanaged. On the list: Managed in the Control Panel's list dialog, managed in the Deck's list editor and on PUT /api/v1/lists/{address}, or DistributionList.Managed over COM. The links. The confirmation page, each member's page, the owner's approval page, the subscribe form and the archive are served by the REST listener, which is off until RestApiPort is set (turning the REST API on). Make it reachable over HTTPS, then set list_base_url (ListBaseUrl), such as https://lists.example.com. Empty, the default, offers no link at all. Only an https:// base gives RFC 8058's one-click. DKIM on the list's domain. The copies are signed as that domain after the list's fields are in place, so the signature covers List-Unsubscribe and List-Unsubscribe-Post. With DKIM off for the domain the copies go unsigned and the providers do not offer the one-click. Address What it does [email protected] Post to the list [email protected] Ask to join, and confirm by replying [email protected] Ask to leave: the member is sent a link and a token, and nothing more happens until they follow it [email protected] The help text [email protected] The person who runs the list [email protected] Delivery reports; with VERP, [email protected] None of them is an account; an account, an alias or a list of exactly one of those names is the administrator's and wins over the manager. Joining. subscribe_policy is 0 closed (the default: the administrator adds members), 1 confirm (the address asked for is sent a confirmation with a token) or 2 moderated (after the address confirms, the owner approves on a page, signed in to the webmail as the owner's own account, or on the Deck; a reply by mail approves nothing). The list answers at most twenty requests an hour from one requester and two hundred an hour in all. Delivery. Each member chooses each message, one digest a day, or none. With digest on, one digest goes at digest_hour (7 by default, the server's local time), at most 100 messages and 10 MB. With archive at 1 (members) or 2 (public), every distributed message is kept and shown by month, as text only, never as HTML. The rows of hm_listarchive are not in the backup. What a member's copy carries, over an https:// base with the archive on: List-Id: <team.example.com> List-Post: <mailto:[email protected]> (NO for an announcement list) List-Help: <mailto:[email protected]?subject=help> List-Subscribe: <mailto:[email protected]?subject=subscribe>, <https://lists.example.com/lists/[email protected]/subscribe> List-Unsubscribe: <mailto:[email protected]?subject=unsubscribe>, <https://lists.example.com/lists/unsubscribe?t=...> List-Unsubscribe-Post: List-Unsubscribe=One-Click List-Owner: <mailto:[email protected]> List-Archive: <https://lists.example.com/lists/[email protected]/archive> Precedence: list X-Loop: [email protected] X-hMailServer-List-Member: <a tag, see Bounces> Bounces. A 5.x.x is a hard bounce, a 4.x.x a soft one; a delayed warning counts for nothing. Anybody can write to the bounce address, so a report is believed only when it carries the member's tag, X-hMailServer-List-Member. Setting REST key Default ListHardBouncesToUnsubscribe list_hard_bounces_to_unsubscribe 1 ListSoftBouncesToUnsubscribe list_soft_bounces_to_unsubscribe 5 ListBounceWindowDays list_bounce_window_days 30 A member who reaches either count is set to none - paused, never unsubscribed - and the owner is told once. Public folders Accounts & domains → Public folders, in the Control Panel on Windows, creates shared IMAP folders; public folders have no REST route yet, so Linux has no administrator's page for them. On either platform a client holding the a right manages a folder's permissions over IMAP. Public folders live in their own namespace, #Public, and sub-folders are made by typing the whole path with the hierarchy delimiter, . by default: #Public.Team.Archive. #[email protected] is a different namespace: one account's own folders, shared. Each permission entry grants RFC 4314 rights to an account, a group or Anyone (every authenticated account). Right Letter What it allows Lookup l The folder appears in LIST/LSUB. Without it the folder does not exist as far as that user is concerned Read messages r SELECT and STATUS Keep seen/unseen state s Set or clear \Seen Set flags w Flags other than \Seen and \Deleted Insert i APPEND and COPY into the folder Post p Send mail to the folder's submission address Create sub-folders k CREATE beneath it Delete folder x DELETE Delete messages t Set or clear \Deleted Expunge e EXPUNGE Administer a SETACL, DELETEACL, GETACL, LISTRIGHTS An owner has every right implicitly. For anyone else the server walks up from the folder to the nearest ancestor that has any ACL rows, then uses the row for the account, else for a group the account is in, else for Anyone. The nearest ancestor with any rows wins, and rows are not merged. Adding one permission to a sub-folder removes everyone else's access to it. \Seen is shared in a shared folder, not per user. Grant lrs and delegates share one read/unread state; grant lr and they leave no trace. Turning ACL enforcement off opens public folders only. It does not extend to #Users, which stops existing entirely. What it does not do The list manager does not rewrite From for DMARC. A posting from a domain that publishes p=reject reaches outside members with a From that fails DMARC when the list changed the message, and Gmail and Yahoo may refuse it. No posting from the web, no list password, no approval by reply, no NNTP, no IMAP access to an archive, no weekly digest, and one owner address per list. A membership query has no directory-attribute form and no group: form, and no query can name another list's query. The list manager's fixture is not linked into the Linux test suite: MailingLists.md says its thirty-four tests are of the Windows suite. Where to read more Wiki: Aliases, Lists and Public Folders, Mailing Lists, Mail In to a Webhook Documents at the 6.3.4 tag: DistributionLists.md, MailingLists.md The public documentation: chapter 13 On this forum: Being trusted by the servers you send to for DKIM and DMARC, the 6.3.4 release announcement, the webmail in depth for masked addresses and send-as, and IMAP on the server for shared mailboxes
  • 0 Votes
    1 Posts
    131 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) holds a credential confined to named domains - such as a domain administrator, or an API key issued for its domains - to a public address when it changes a domain's relay host, so it cannot point the server's delivery at the server's own network, and makes a domain's or an account's rule's Send using route and Bind to address actions the server administrator's to set; an account given the serveradmin role with a list of domains is, over COM as over REST, confined to those domains, so a script's account that edits the routes or the relayer needs the role with no list. See the 6.3.7 announcement. There are four ways this server can decide where an outbound message goes, and they are tried in a fixed order. Almost every "it went to the wrong server" question is answered by knowing that order, so this topic starts with it. It then covers the smart host and its failover, per-domain relays, routes and what they do to an unknown recipient, sending an OAuth2 token to a relay and what 6.3.4 changed about it, the COM change that is in effect only after Save(), and three things new in 6.3.4 (27 September 2026, schema 6104) that are not about where mail goes: remote domain policies (one paragraph here; the fuller treatment is Requiring TLS: MTA-STS, DANE and remote domain policies) and restricted delivery, which decide whether mail may go, and message recall, which withdraws unread copies already delivered on this server. The one thing to know first: a route is a statement about the destination and beats everything; a per-domain relay is a statement about the sender; the server-wide relayer is the fallback for mail nothing else has an opinion about. The order of decision When a message is ready to leave, its recipients are grouped by domain and the server asks: Did a global rule apply the Send using route action? Then every recipient goes to that route's target host and nothing else is consulted. Is there a route whose domain matches the recipient domain? Wildcards are allowed. The route's host, port, connection security and credentials are used. Does the sender's local domain have a relay host of its own? That domain's relay host, port, security and credentials. Port 0 means 25. Is a server-wide SMTP relayer configured? The relayer's host, port, security and credentials. Port 0 means 25. Otherwise, an ordinary MX lookup for the recipient domain, then A or AAAA for each MX host. Targets that turn out to be identical are merged: a message for four domains that all relay through one smart host opens one connection, not four. A bounce never picks up a per-domain relay, because a bounce has no envelope sender, so there is no sending domain to consult. Since 6.3.4 one question comes before all of these for inbound mail: an address or a hosted domain routed to a webhook has each message posted to an application, instead of being delivered (deliver off) or as well (deliver on). That is a different thing from the routes below, which send a domain's mail to another mail server; mail in to a webhook has the webhook routes. Sending through a smart host On Windows, configure it in the Control Panel at Mail flow & delivery → Delivery of e-mail → Relayer. The Control Panel and the COM API are Windows-only; over REST, which Linux has too, the relayer's settings are keys of PUT /api/v1/settings (smtp_relayer_password is write-only). Setting COM name Default When it is wrong SMTP relayer SMTPRelayer empty: MX lookup A host that does not resolve gives the bounce text "The host specified as SMTP relay server could not be found" with 4.3.5, a temporary failure, so the message is retried Port SMTPRelayerPort 25 Looks like a connection timeout in the SMTP log Connection security SMTPRelayerConnectionSecurity None Required against a host without STARTTLS defers every message Relayer requires authentication SMTPRelayerRequiresAuthentication off Off against a host that requires it gives 530/535 and the mail stays queued Number of delivery retries SMTPNoOfTries 4 0 means one attempt and then a bounce Minutes between retries SMTPMinutesBetweenTry 60 Outbound delivery security SMTPConnectionSecurity STARTTLS (optional) Applies to MX-resolved delivery, not to the relayer Several smart hosts. Separate the names with a vertical bar: smtp1.example.net|smtp2.example.net Each name is resolved in turn and every address it resolves to is appended, giving one ordered candidate list tried top to bottom. A single name with several A records already gives that failover. All hosts in the list share the port, security and credentials. A permanent 5xx rejection is a real delivery failure and the next host is not tried: trying it would deliver the message twice if the first server had in fact accepted it. Relaying everything through an ISP: Open Mail flow & delivery → Delivery of e-mail → Relayer. SMTP relayer: smtp.isp.example. Port: 587. Connection security: STARTTLS (required) - required, not optional, so a downgrade cannot send your credentials in clear. Tick Relayer requires authentication and fill in the user name and password. Save changes, send one message, and read Monitoring & troubleshooting → Live logs. The line that proves the relayer was used is: "SMTPDeliverer - Message 41: Relaying to host smtp.isp.example." If the message went to an MX host instead, the relayer field is empty, or a route or a per-domain relay took precedence. Per-domain relay. Accounts & domains → Domains → Properties → Relay sets where mail from that domain leaves through. It is for a server hosting several independent domains where each has bought its own delivery provider. An empty host means the domain has no opinion. Since 6.3.7 a credential confined to named domains - such as a domain administrator, or an API key issued for its domains - may set the host to a public address only, over REST and over COM; a host on the server's own network is the server administrator's to set, and one the server administrator set stays the domain administrator's to save, since only a change of the host is judged. Routes A route overrides delivery for a specific recipient domain. Mail flow & delivery → Routes: type the domain and target host, press Add, then Properties for everything else. Over REST the routes are /api/v1/routes. Field Default Note Domain name - Wildcards are matched, so *.partner.example works. The first matching route in name order wins; a wildcard broader than you meant silently captures other domains Target SMTP host, port -, 25 A name or a literal IP address; the host1\|host2 failover applies here too Number of delivery retries 3 Replaces SMTPNoOfTries. The delivery path applies it as tries − 1, so a route set to 3 schedules 2 further attempts Minutes between retries 10 Where one message has recipients on several routes, the largest count and the largest interval win Deliver to all addresses on Off: only the addresses listed are routed. Off with an incomplete list is the single most common route mistake Connection security None None, SSL/TLS, STARTTLS optional, STARTTLS required Treat sender domain as local off Turn it on only for a domain you actually control Treat recipient domain as local off On, unauthenticated senders may send to it from the Internet range What a route does to an unknown recipient: Route setting Domain has a catch-all? Address in the route's list? Result Deliver to all addresses - - Routed Specific addresses only no no 550 Recipient not in route list. Specific addresses only yes no Delivered to the catch-all, not routed Specific addresses only yes yes Routed; the catch-all does not intercept it Routes also make ETRN work: when a remote site issues ETRN <domain> and a route matches, the messages held for it are released for immediate delivery. OAuth2 to a relay A route or the SMTP relay whose host is on OutboundOAuth2Hosts (default smtp.office365.com) is sent AUTH XOAUTH2 with the server's own token: a client-credentials token fetched from OutboundOAuth2TokenUrl with OutboundOAuth2ClientId, OutboundOAuth2ClientSecret and OutboundOAuth2Scope (default https://outlook.office365.com/.default), or OutboundOAuth2FixedToken used verbatim instead of fetching. The default host list is inert on its own: with OutboundOAuth2TokenUrl and OutboundOAuth2FixedToken both empty, no bearer is set and the delivery proceeds with LOGIN and the password, with no log line. Since 6.3.4 the token is sent over TLS only. A route or the SMTP relay whose host is on OutboundOAuth2Hosts and whose connection security is none - or STARTTLS optional against a server that does not offer it - used to send AUTH XOAUTH2 with the token in the clear. Such a delivery now sends nothing in its place, fails with a temporary 450 4.7.0, and the error log says to set the security to TLS or STARTTLS required (HM5912); the message waits for the retries the route allows. Exchange Online offers STARTTLS on 587, so a route set up as Microsoft documents it is not affected. Also since 6.3.4, a relay that refuses the login no longer gets the token into the bounce, which now reads hMailServer sent: AUTH XOAUTH2 <Token removed>; the logs write AUTH XOAUTH2 ***. These are [Settings] keys, and since 6.3.4 the database is the settings store: a line edited in the file changes nothing, the stored value is used and named as HM5804, and the line is put back where the service can write the file (when a saved setting takes effect). Change them in the Control Panel, with PUT /api/v1/settings/ini/{name} or with hmctl ini set. A COM change is in effect only after Save() Since 6.3.4 a COM change to a route or an incoming relay is in effect only after the object's Save(), and a property set and never saved changes nothing; the list of objects this applies to does not name the server-wide SMTP relayer's settings. COM is Windows-only, so this concerns scripts on Windows: the COM API topic has the whole list, what an object read before the save shows, and error 7390. Remote domain policies, in one paragraph Since 6.3.4 (schema 6045). A route says where mail for a domain goes; a remote domain policy says what this server will and will not do when it talks to one domain it does not host, however that domain's server was found, and it applies to a routed delivery exactly as to an MX one. One record per name or pattern (bank.example, *.example, *), the most specific active one governing, holds the outbound TLS the domain demands (none, encrypted, verified or dane; a delivery that cannot meet it is deferred with 4.7.0, never sent in the clear), TLS required of its senders (530 5.7.0), a largest message (5.3.4), connection and rate ceilings (deferred with 4.4.5), whether automatic replies and forwards may go there, a recipient callout for a domain this server is a backup MX for, and gateway encryption to the partner's own key. It is server-administrator only, and GET /api/v1/remote-domains/effective?domain=mail.bank.example answers which record a delivery will use. Restricted delivery, since 6.3.4 Schema 6084. Accounts, or a whole domain, that may only exchange mail with named domains: a school whose pupils write to the school and to nobody else. It is not a route: it decides whether mail may go, not where. The list is the domain's: Domain.RestrictedDomains over COM, restricted_domains on GET/PUT /api/v1/domains/{domain}, the Restricted delivery tab of the Control Panel's domain dialog, the same group in the Deck's domain editor. At most 2,000 characters, names separated by commas or spaces, ASCII only, an internationalised name in its xn--... form. Written as Covers partner.example that domain, and nothing beneath it .example example and everything beneath it, anchored at a label boundary An account follows its domain unless it says otherwise (Account.RestrictedDelivery, restricted_delivery on the account routes): Value Word Meaning 0 domain Follows the domain. The default 1 restricted Restricted even when the domain names nothing; the account is then confined to its own domain 2 exempt May exchange mail with anyone An empty list, the default, means nothing runs, so an upgraded server delivers exactly as it did. Always allowed: the account's own domain under its own name or any alias domain, the domain's postmaster, the null sender MAIL FROM:<>, and mail this server writes itself. Other domains this server hosts are not allowed unless named. It is enforced in three places: Inbound, at RCPT TO. A restricted recipient whose envelope sender is not allowed is answered 550 5.7.1 <address>: this mailbox accepts mail from named domains only., per recipient, so one refused RCPT does not end the transaction. Every session is judged, authenticated or not. Outbound, at RCPT TO. A restricted submitting account is answered 550 5.7.1 <address>: this account may send to named domains only. for a recipient outside. The webmail's POST /api/v1/me/messages answers 400 with the sentence and queues nothing. At local delivery, for a recipient reached by expansion: a distribution list member, an alias target, a catch-all. The restricted recipient is dropped, and a non-delivery report goes to the sender only when the sender is local. A restricted account's forward - the account forward, an account rule's forward, a Sieve redirect - to a domain not allowed is not made, and the message is still delivered to the mailbox. Its out-of-office reply is not sent to a sender who is not allowed. Every refusal is a DEBUG log line; nothing goes to the error log, since a refusal is the feature working. Message recall within the server, since 6.3.4 Schema 6085. An unread message sent to accounts on this server can be withdrawn by its sender. Nothing does this across the Internet: a message that has left for another server is that server's. No recall notice reaches the recipients; the Sent copy's own mark is the record. What is recorded. A message accepted from one of the server's own accounts - an authenticated SMTP submission, credited to the account that signed in, or the webmail's send - gets one row in hm_recall per recipient that resolved to a local account. Nothing is recorded for: mail from outside, and mail this server writes itself; an unauthenticated submission from a local IP range in a local account's name, because a recall keyed on a claim would let anybody delete mail from other people's mailboxes; a message with a Message-ID longer than 255 characters, and a message that expires; the 201st and later recipient rows of one sender in a rolling day. The recall. In the webmail, Recall in the menu of a message in the Sent folder. For a program, POST /api/v1/me/recall with {"message_id": "<the Message-ID>", "sent_id": n}; it is refused with 403 on an app password. Each recipient is judged on their own: Outcome Meaning read Some live copy of theirs carries \Seen. Nothing was done: whoever has read it keeps it recalled No copy was read; every unread copy has been deleted the way the webmail deletes a message gone No copy of theirs is there any more: deleted by the reader, never delivered, or still in the queue failed The database did not answer; a later call decides them again The answer also counts elsewhere, the To and Cc addresses whose domain this server does not host, and says in sent_marked whether the Sent copy carries the keyword $Recalled. Read is \Seen and nothing more: a POP3 client, or an IMAP client that peeks, has the message and the recall cannot know. RecallDays (recall_days on PUT /api/v1/settings) is 14 by default, 1 to 365; a domain or an account may hold a shorter window, never a longer one. There is no COM property and no Control Panel page. What it does not do A route does not decide whether mail may go, and restricted delivery and a remote domain policy do not decide where. A per-policy SMTP port or credential is a route's job. Restricted delivery does not check the From header, does not filter what is already in the mailbox, and does not gate a journal rule's copy or an alias or list in the domain that reaches outside. Recall does not reach another server, does not touch the queue, and does not recall a message submitted without authentication. A 5xx from one smart host is not retried on the next. Where to read more Wiki: Routes and Relays, Settings Reference for the OutboundOAuth2* keys, APIs Reference, Diagnosing Stalled Mail Documents at the 6.3.4 tag: RemoteDomains.md, RestrictedDelivery.md, MessageRecall.md, Upgrading.md The public documentation: chapter 14, Routes and relays On this forum: Being trusted by the servers you send to, the 6.3.4 release announcement, requiring TLS and remote domain policies, and diagnosing stalled mail for a message that will not leave the queue
  • 0 Votes
    1 Posts
    137 Views
    ProgressiverobotP
    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. 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: 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. 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). The message is split into local and external recipients. External recipients go to relays, routes and MX delivery. 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 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 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 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: <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: 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 require ["vacation"]; vacation :days 7 :subject "Out of the office until 22 September" :addresses ["[email protected]", "[email protected]"] "I am away until 22 September. For anything urgent please write to [email protected]."; :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. 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 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, The COM API and Scripting Documents at the 6.3.4 tag: OutOfOffice.md, SealedMail.md, MailFlowRules.md, EventScripts.md The public documentation: chapter 12 and the rules cookbook On this forum: the 6.3.4 release announcement, the Linux guide, mail flow rules and domain rules, event scripts, and when a saved setting takes effect for the ManageSieve keys
  • 0 Votes
    1 Posts
    126 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) refuses a state-changing REST request that carries a credential when the browser making it reports it as cross-site (Sec-Fetch-Site, or an Origin that is not this server's), so a page on another site cannot post to these routes with a credential, whether a key or the administrator password the browser holds; a program or script that sends no such header, as the curl examples here do not, is unaffected. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026) answers 400, saying why, before a route reads a member, to a body that is not one JSON object, names a member twice, holds an escaped U+0000 or passes 100,000 values or 32 levels, so correct a program that sends such a body to these routes; and it raises account.locked only for a locked name that is an account's own. See the 6.3.8 announcement. Since 6.3.4 hMailServer has three ways for another program to work with the server's mail: a send API, through which an application sends as one sender and asks afterwards what became of each recipient; mail in to a webhook, where the mail for an address or a whole domain is posted to a URL; and outbound webhooks, which tell a program what the server's mail did. Nothing is on until a key, a route or a subscription is made. The one thing to know first: each of the three takes a different credential, and the difference is deliberate. Feature Routes Who may use it The send API /api/v1/send An API key that names a sender and whose role holds send_through_api. Not the administrator password, not an account. Mail in to a webhook /api/v1/inbound-webhooks view_configuration to read, manage_inbound_webhooks to change. Meant for an API key's role, confined to the application's domain. Outbound webhooks /api/v1/webhooks The administrator password, or an account whose role holds the permission. No API key reaches these routes; the refusal is 401. All three are made and managed over the REST listener, so RestApiPort has to be on; outbound webhook subscriptions can also be managed over COM, on Windows. The send API: the key POST /api/v1/me/messages is the webmail's send and stays the webmail's. The send API is for programs. Route What it does POST /api/v1/send Sends a message as the key's sender. 202 {queue_id, message_id, recipients, recorded} GET /api/v1/send The key's own sends, newest first, paged GET /api/v1/send/{queue_id} One of them, with the state of each address its body named A key sends when two things are true of it: its role holds send_through_api, and it names a sender. Either alone sends nothing. Three fields are set when the key is made, on POST /api/v1/apikeys with the administrator password or on the Control Deck's API keys page. Field What it is send_from The one sender the key may use: an address of a domain this server hosts, or *@domain for any address of that domain. Empty, the default, means the key cannot send. send_per_minute The key's own ceiling on messages a minute. 0, the default, sets none. send_per_day The key's own ceiling on messages in any 24 hours. 0 sets none, which only a key whose sender is an account's address may have. The built-in serveradmin role holds the permission, so a key made with "scope": "full" holds it. No other built-in role does. A custom role is how a key that sends and does nothing else is made: POST /api/v1/roles {"name": "mailer", "description": "sends mail", "permissions": "send_through_api"} POST /api/v1/apikeys {"label": "shop confirmations", "role": "mailer", "send_from": "[email protected]", "send_per_day": 5000} The route refuses with 400, among other things, a send_from at a domain this server does not host (a domain alias does not count), at a domain that is switched off, on a read-only key, or outside the domains a key is confined to. A send_from that is *@domain, or an address that is no account's, needs a send_per_day above 0, because such a sender has no account's sending quota to hold it. A key's sender cannot be changed afterwards: a new sender is a new key. What *@domain grants. To the server, such a key is every mailbox of that domain sending as itself. It can post to a members-only list as any member and send as the domain administrator's mailbox. The display name and reply_to are the application's own and are not checked against anything; what the key vouches for is the address. On Windows, the desktop Control Panel's key page does not show these three fields and cannot set them, so a key made there cannot send. The Control Deck and the route do. The send API: sending and asking afterwards curl https://mail.example.com:8443/api/v1/send \ -H "Authorization: Bearer hmapi_0123…" \ -H "Content-Type: application/json" \ -d '{"to":["[email protected]"],"subject":"Your order","text":"Thank you."}' Member Rule from Optional when the key's sender is an address, required when it is *@domain. "address" or "Name <address>". to, cc, bcc Arrays of mailboxes; at least one recipient between them. Bcc goes to the envelope and nowhere else. reply_to Optional. subject Required; it may be empty. text, html At least one. Both makes a multipart/alternative. headers Optional: X- fields other than the server's own X-hMailServer- fields, List-Unsubscribe, List-Unsubscribe-Post, In-Reply-To and References. Any other field is 400. attachments Optional: [{filename, content_type, data}], data in base64, at most 20 and twelve megabytes together. A line break is refused with 400 in every value that becomes a header, and a member the route does not take (body for text, for example) is 400 and named, not dropped. What the server does to the message. It is written and checked by the same code as the webmail's send. Every recipient goes through what RCPT TO asks of an authenticated sender, with the server's recipient ceiling (MaxSMTPRecipientsInBatch) and the suppression list. The domain's signature and disclaimer are added, the rules that block are asked (403, policy_rejected), and the message is held to the maximum message size (413). It is DKIM-signed at delivery by the domain's key. The envelope sender is the From address, so a bounce comes back to it. A Received field names the server, the send API and the key's label, never the key's id. No Sent copy is kept anywhere. When the sender is an account's address, that account's sending quota is consumed as an SMTP submission would consume it. Ceiling Answer send_per_minute 429, Retry-After: 60 send_per_day 429, Retry-After: 3600 The sender account's sending quota 429, Retry-After one twenty-fourth of the account's period The REST request budget, 200 requests in 10 seconds 429, Retry-After: 10 The recipient ceiling, the size 400, 413 A message is counted only once every other check has passed, and when a later ceiling refuses, every earlier one is given back. A refused message is never a counted one. The answer to a message that is taken is 202: {"queue_id": 4711, "message_id": "<…@mail.example.com>", "recipients": 2, "recorded": true} queue_id is the one to ask about afterwards with GET /api/v1/send/4711. The answer names only the addresses the body named, each with a state. State Meaning queued In the queue and not yet tried deferred An attempt failed and will be retried delivered The trace's delivered event bounced Refused for good, or the retries ran out refused The suppression list stopped it at delivery unknown Nothing names the address; the answer says why It needs the message trace, and the trace is off by default (MessageTraceEnabled), because it records who corresponds with whom. With it off, a send reads queued or deferred while it is in the queue and unknown once the queue has let it go; trace in the answer is false and a trace_note says so. Turn the trace on, and choose its retention, before relying on the answer. The record of a send is kept for MessageTraceRetentionDays, thirty by default. Only a send this key made is answered; any other id is the same 404. The backup does not carry the records. Mail in to a webhook: a route SendGrid and Mailgun call this inbound parse. A route is made over REST or on the Control Deck's Inbound webhooks view. There is no COM member and no Control Panel page, on either platform. POST /api/v1/inbound-webhooks { "address": "[email protected]", "url": "https://helpdesk.example/hooks/mail", "secret": "a-long-random-string" } Field What it is Default address [email protected], or example.com for every address of the domain. The domain must be one this server hosts. required url https:// to the endpoint; plain http:// only to this machine. Printable ASCII only. required secret Write-only. Every request is signed under it and it is never returned. required deliver false: the route alone receives the mail. true: what the address reaches without the route receives it as well. false on_failure keep or bounce keep format form (multipart/form-data in SendGrid's field names) or json form attachments include, names or none include include_raw Also send the message as it arrived false max_size_kb A larger message is not posted. At most 81920. 10240 enabled A route switched off is as though it were not there for new mail true A route decides that the address exists. RCPT TO is accepted for a routed address whether or not an account, an alias or a list has it, and a route is asked before any of them. With deliver off, the route takes the mail instead of whatever has the address, so a list whose address a route takes never sees the message. Route a list's domain with care. An address route wins over its domain's, and a recipient with a plus tag ([email protected]) is found by the address as written, then by the address before the tag, then by the domain, with the tag kept in what is posted. What arrives is a signed POST: POST /hooks/mail HTTP/1.0 Content-Type: multipart/form-data; boundary=hMailServer-4193-... X-hMailServer-Timestamp: 1789000042 X-hMailServer-Signature: sha256=<hex> X-hMailServer-Event: message.inbound X-hMailServer-Delivery: 4193 X-hMailServer-Attempt: 1 X-hMailServer-Delivery is the same on every attempt. The form format carries SendGrid's fields (headers, from, to, cc, subject, text, html, envelope, sender_ip, SPF, dkim, charsets, attachments, attachment-info, attachment1 onward) with a few of this server's own beside them (spam, dmarc, route, queue_id, attachments_left_out). Mailgun's field names are not sent. The json format is this server's own shape. The SPF, dkim and dmarc results are the ones this server wrote into its own Authentication-Results field, and only while AuthenticationResultsEnabled is on. Retries and what is kept. The message is taken after the global rules, the virus scan and the sandbox have had it, and nothing is posted from the delivery thread. Any 2xx delivers. Anything else, a redirect included, is a failure. The retries are the outbound webhooks': WebhookMaxAttempts attempts (5 by default), with waits of 0, 1, 5, 15 and then 60 minutes. When they run out: keep: the message is kept and never swept. It can be read with GET /api/v1/inbound-webhooks/deliveries/{id}/message, tried again with POST /api/v1/inbound-webhooks/deliveries/{id}/retry or removed with DELETE /api/v1/inbound-webhooks/deliveries/{id}. The error log says so once, as HM7401. bounce: the message is returned to its sender with a delivery-status report (4.4.7), without repeating what the endpoint answered. HM7402 records the return. Where the sender cannot be told, the message is kept instead. A message larger than the route's ceiling is not posted at all: it is returned at once (5.3.4) or kept at once (HM7403). Delivered and returned rows are removed after WebhookRetentionDays (7 by default), and the rows survive a restart. Where an endpoint may be depends on who made the route. The server's administrator may name an address only this server can reach, but never a link-local or cloud metadata address (169.254.0.0/16, fe80::/10 and the clouds' own metadata addresses). Every other credential, an API key of any scope or role included, may name a public address only. Both are judged when the route is saved and again at every connection. No redirect is followed and HttpProxy is never used. Who may. Only serveradmin holds manage_inbound_webhooks as shipped, since routing a domain's mail to a URL is as much as reading all of it. A reader without it is answered a route's URL as its scheme and host alone, so put a token in the secret, not in the URL. Reading a kept message needs view_mailbox as well. POST /api/v1/inbound-webhooks/run makes one pass now and needs a credential that reaches every domain. Outbound webhooks An outbound webhook does not carry the message. No body, no attachments, no header block. It is a notification. The one piece of content it can carry is the subject, and only where the domain's webhook_subjects and the subscription's include_subject are both on; both are off by default, and even then a subject travels on message.received and message.quarantined only. Event Raised when message.received The message has been saved and the server has taken responsibility for it, once per local recipient message.delivered A recipient, local or remote, has been delivered to message.bounced A non-delivery report has been written and queued for the sender, once per failed recipient message.quarantined The held copy and its row exist account.locked Repeated authentication failures have locked a name A delivery attempt that failed and will be retried is not a bounce, and no event says deferred. POST /api/v1/webhooks { "name": "ticketing", "url": "https://tickets.example/hooks/mail", "secret": "a-long-random-string", "events": "message.bounced,account.locked", "domains": "example.com", "max_per_minute": 120 } An empty events, domains or addresses list means every one. A subscription confined to a send API key's sender ("addresses": "[email protected]") hears message.delivered and message.bounced for the recipients outside this server, with the message trace on or off. Plain http off this machine is refused when the subscription is made. The secret is write-only, protected at rest, and an empty secret in a change keeps the one that is set. Permissions. Reading needs view_configuration; deleting needs manage_settings; creating, changing, testing and running a pass need manage_server, because whoever can do those can make the server connect to a host of their choosing. A role confined to named domains is refused all eight routes. The queue. The delivery pass runs once a minute, attempts at most 200 rows and runs for at most 60 seconds. A delivery succeeds on any 2xx; a request that has taken more than ten seconds is a failure. After WebhookMaxAttempts the row is dead-lettered and the error log says so once as HM6971. Past a subscription's ceiling per minute, events are dropped and counted as dropped, not queued. Setting REST key Default WebhooksEnabled webhooks_enabled on WebhookMaxAttempts webhook_max_attempts 5 WebhookMaxPerMinute webhook_max_per_minute 60 WebhookRetentionDays webhook_retention_days 7 WebhooksEnabled silences the outbound webhooks and not the inbound routes. From a shell: hmctl webhook create --set name=ticketing --set url=https://tickets.example/hook \ --set-string secret="$SECRET" --set events=message.bounced --set domains=example.com hmctl webhooks test --event message.bounced --domain example.com hmctl webhooks run hmctl webhooks deliveries --state dead-lettered webhooks test raises a real event through the path a real one takes and says how many subscriptions took it, so 0 is an answer. In PowerShell the functions are Get-HmWebhook, New-HmWebhook, Set-HmWebhook, Remove-HmWebhook, Get-HmWebhookDelivery, Invoke-HmWebhookDelivery and Test-HmWebhook. Over COM, on Windows, Utilities.GetWebhookSubscriptions(name) and Utilities.SaveWebhookSubscription(json) take and answer the same JSON documents. Verifying a signature One recipe signs all of them: the outbound webhooks, the inbound routes and the webhook of an alert rule. The signature is HMAC-SHA256 over the timestamp, a full stop, and the raw request body, under the shared secret, as lower-case hex, in X-hMailServer-Signature. Verify it against the bytes received, before parsing them. import hmac, hashlib, time def verify(secret, headers, raw_body): timestamp = headers["X-hMailServer-Timestamp"] signature = headers["X-hMailServer-Signature"] # Refuse anything older than five minutes, or the signature can be replayed. if abs(time.time() - int(timestamp)) > 300: return False expected = hmac.new(secret.encode(), (timestamp + ".").encode() + raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest("sha256=" + expected, signature) Compare in constant time. Check the timestamp: a signature with no freshness check can be replayed. De-duplicate on X-hMailServer-Delivery together with X-hMailServer-Event. A retry is the same body with a fresh timestamp and so a fresh signature, and an inbound and an outbound delivery can carry the same number. To rotate a secret, PUT the new one and accept either signature at the receiver while the change is made. There is no overlap window in the server. The documents also give the recipe in JavaScript. An alert's webhook carries three of the headers: X-hMailServer-Timestamp, X-hMailServer-Signature and X-hMailServer-Event. What they do not do The send API has no templates, no substitutions, no scheduled send and no per-recipient ceiling of the key's own, and gives no state without the trace once a message has left the queue. An inbound route makes one post per route per message, not one per recipient. It has no setting of its own and no per-route retry ladder. An outbound webhook does not carry the message and has no per-subscription retry policy. Neither kind of webhook is in the configuration backup. Export the routes with GET /api/v1/inbound-webhooks and the subscriptions with hmctl webhook list; the secrets are in no read. One pass at a time is per process, not per database, for both kinds of webhook. Where to read more The operator documents: SendApi.md, InboundWebhooks.md, Webhooks.md and, for the alerts' webhook, AlertsAndAuditTrail.md. The wiki: Mail In to a Webhook and The REST API. The public documentation: chapter 24, The REST API. On this forum: The Control Deck, the 6.3.4 announcement, the REST API guide and hmctl and the PowerShell module; turning the message trace on is in Troubleshooting by symptom, and the alert rules whose webhook is signed the same way are in Alerts, the audit trail, syslog and the journal.
  • 0 Votes
    1 Posts
    140 Views
    ProgressiverobotP
    Since 6.3.4 the delivery queue shapes what it sends. It pauses a destination that asked it to slow down, caps how many sessions it holds open to one destination and how many deliveries one tenant has in flight, and holds a new outbound address to a daily ceiling at each large provider. Everything in this topic is new in 6.3.4 except MaxOutboundPerDestinationPerMinute, a fixed per-destination rate that 6.3.3 already has. What to know first: of what this topic covers, two things change behaviour on upgrade without a switch. The back-off is on by default, and a deferral this server chose does not spend a message's retries until the message has been queued for longer than its retry schedule spans; only a failure at a remote does. The second covers every hold in this topic, and so changes an upgraded server that already sets MaxOutboundPerDestinationPerMinute. The connection ceilings, the tenant share and the warm-up do nothing until you set them. The order of the checks For each batch of recipients on one target, the server judges in this order, before any connection is opened: The remote domain policy's largest message: 5.3.4, permanent, measured on the queued message. The destination's back-off window. Held until the window ends; then one delivery goes through as the probe, and any other arriving meanwhile waits a minute. The policy's messages a minute (max_messages_per_minute). The connection ceiling: the policy's max_connections where the policy sets one, otherwise the server-wide MaxOutboundConnectionsPerDestination. The tenant share, MaxOutboundConnectionsPerTenant. The server-wide MaxOutboundPerDestinationPerMinute. DNS: the MX lookup (or the route's host) and MTA-STS; then the IP warm-up's daily ceiling; then, where DANE applies, whether the MX set is DNSSEC-validated. Gateway encryption, for a domain whose policy encrypts. The copy is made here, so a batch that is only going to be deferred is never encrypted. The connection attempts. Every one of 2 to 6 is a deferral with 4.4.5 ("mail system congestion") and a sentence naming the rule. The target each check is keyed by is what the batch is delivered to: the recipient domain of an MX delivery, or the host of a route or a relay, or its address when the route names an IP literal, lower-cased. The back-off: a destination said slow down When a destination answers a delivery with a request to slow down, it gets a back-off window: nothing is sent to it until the window ends, one delivery then probes it, and the same answer doubles the window. Deliveries to every other destination are untouched. What is read as a signal. Reply Read as Code 421, unless an exclusion below applies A signal, whatever the text, at the banner or anywhere after it A 4xx whose enhanced status is 4.7.0, 4.7.28, 4.7.500, 4.7.650, 4.7.652 or 4.3.2 A signal A 4xx whose text, lower-cased, contains one of the phrases below A signal The phrases: rate limit, rate-limit, ratelimit, too many messages, too many connections, too much, unusual rate, unexpected volume, temporarily deferred, temporarily rate, throttl, try again later, server busy, back off, slow down. They are English, and a remote that says the same in another language is read by its codes alone. The table is fixed and cannot be edited. What is deliberately not a signal, each of which wins over the text and over the 421 rule: any 5xx: a refusal is not a request to wait; 4.7.1, which greylisting and policy refusals answer, and any reply whose text says greylist or graylist; 4.2.x: one mailbox, never the destination; 4.5.3: too many recipients for one message; 4.4.x: 421 4.4.2 timeout exceeded is the remote's idle timeout on a slow DATA; a connection that was refused or timed out, a DNS failure, and this server's own deferrals. The window and the probe. The first slow-down reply opens a window of OutboundBackoffMinutes. While it is open a batch for that destination is held with 4.4.5, and the application log says: Delivery to <target> deferred: the destination asked this server to slow down at <time> (<reply>); next attempt after <time>. When the window has passed, the first delivery goes through as the probe. If it is delivered, or fails with anything that is not a slow-down reply, the window is cleared. If it is answered with a slow-down reply, the window doubles, up to OutboundBackoffMaxMinutes. A probe nobody ended - a delivery thread that died with it - goes stale after fifteen minutes and the next delivery takes it over. Setting Default Range Meaning OutboundBackoffMinutes 2 0 to 10080 The first window. 0 turns the back-off off OutboundBackoffMaxMinutes 60 1 to 10080 The ceiling a doubling window stops at The two refuse to cross each other on every surface: to move both, move the ceiling first when raising and the base first when lowering. A relay is one destination. With SMTPRelayer set, or a route through a smart host, one slow-down reply from it holds every message that goes through it for the window. One miss, stated in the document. The delivery client keeps a recipient's first non-fatal reply, so a genuine 421 from a second MX host after a plain 4xx from the first is not seen for that recipient. Connection ceilings and the tenant share Both use the same counting semaphore, and it never blocks: a delivery thread that waited for a slot would be a thread not delivering anybody else's mail. Over the ceiling the batch is deferred one to five minutes. Setting Default Range What it bounds MaxOutboundConnectionsPerDestination 0, off 0 to 1000 Sessions open at once to one target that no remote domain policy sets max_connections for MaxOutboundConnectionsPerTenant 0, off 0 to 1000 Deliveries one tenant may have in flight at once The per-domain ceilings are on the remote domain policy, whose TLS half is in Requiring TLS: Policy field 0 means Beyond it Largest message (KB) no limit of ours Not attempted: no connection is opened, and the message is bounced at once with 5.3.4, naming the policy and both sizes Simultaneous connections unlimited Deferred with 4.4.5 for one to five minutes. The count covers every domain the policy's pattern matches Messages a minute unlimited Deferred with 4.4.5 for one to five minutes. Independent of MaxOutboundPerDestinationPerMinute; both apply A policy's connection ceiling wins over the server-wide one wherever the policy sets it. Who a tenant is. The hosted domain of the envelope sender, with an alias domain resolved to its domain. A sender this server does not host - mail relayed for an IP range or a route, forwarded external mail, the null sender - is the one tenant (external), so relayed mail cannot starve the hosted domains either. The tenant share is not a volume control. It is a share of connections, bounded by MaxDeliveryThreads: a share larger than the thread count is no share at all. The per-account sending limits are the volume control. This server's own delivery reports have the null sender, so they share (external) with relayed mail. Where the settings are. The four new settings are on the Control Panel's Delivery of e-mail page beside MaxOutboundPerDestinationPerMinute (Windows), on the Deck's Settings view, over COM as Settings.OutboundBackoffMinutes, OutboundBackoffMaxMinutes, MaxOutboundConnectionsPerDestination and MaxOutboundConnectionsPerTenant (Windows), and over REST on /api/v1/settings as outbound_backoff_minutes, outbound_backoff_max_minutes, max_outbound_connections_per_destination and max_outbound_connections_per_tenant. In hm_settings the two connection rows are named MaxOutboundConnsPerDestination and MaxOutboundConnsPerTenant, because the column holds thirty characters. A deferral this server chose is not counted When every non-fatal failure of a delivery pass was one of this server's own deferrals, the message's next try is set to the longest deferral of the pass with the tries counter untouched, and the log says so: Delivery deferred by this server's own shaping, not by a remote server. Scheduling it for later delivery in N minute(s); the attempt is not counted against the retry limit. When any recipient of the pass failed at a remote, the counted path runs as it always has. Before this, with a small retry count, a message could be bounced having never been offered to anybody. The hold is bounded. A message queued for longer than the span its counted schedule would have allowed - (retries + 1) times the minutes between tries, from the time it was accepted - takes the counted path from then on, so that a destination that says "slow down" for ever still ends in a bounce: Deferred by this server's own shaping, but it has been queued for N minute(s), longer than the M minute(s) its retry schedule spans. This deferral is counted, so that the message is not held for ever. A held pass is a first pass only once. DKIM and ARC signing, the mirror copy and a rule's policy notice happen once, however many passes a message is held through. Nor does a held pass forward, copy, reply or call a rule's script function again: those four actions run once per counted attempt. IP warm-up Schema 6093. A schedule holds one outbound address to a daily ceiling of recipients per provider, rising day by day along a fixed plan; the queue sends up to the ceiling and holds the rest, usually until the next UTC day. Nothing is held until an administrator makes a schedule. A schedule is about one address, and the address has to be bound. It is the local address a delivery leaves from: a global rule's bind to address action when one applies, otherwise SMTPDeliveryBindToIP (Bind outbound connections to IP, smtp_delivery_bind_to_ip). A delivery that binds no address leaves from whatever the operating system chooses, and no schedule is consulted for it. So a schedule does nothing, even enabled, until the address is configured on the host and one of those binds it. The server can check the setting and nothing else: the listing says in_bind_setting, and the Deck warns when it is false. The provider is decided from the mail host, never from the recipient's domain. A company on Google Workspace is reached through Google's MX hosts, and it is Google that judges the address. The first host the batch will try decides. Provider Key Mail host names Google google google.com, googlemail.com, aspmx.l.google.com Microsoft microsoft outlook.com, hotmail.com, protection.outlook.com, olc.protection.outlook.com Yahoo yahoo yahoodns.net, yahoo.com, aol.com Apple apple icloud.com, apple.com Proton proton protonmail.ch, proton.me Every other provider other everything else, counted together at five times the named providers' ceiling Mail relayed through a smart host that is not one of those names is other, whoever the smart host delivers it to. The plans, in recipients per named provider per day. After a plan's last day there is no ceiling, and the schedule holds and counts nothing. Day of standard Ceiling other 1 50 250 2 100 500 3 200 1,000 4 500 2,500 5 1,000 5,000 6 2,000 10,000 7 5,000 25,000 8 to 10 10,000 50,000 11 to 14 20,000 100,000 15 to 21 50,000 250,000 22 to 30 100,000 500,000 The plans side by side: Plan Length Day 1 Last day Rule standard 30 days 50 100,000 the table above gentle 60 days 25 50,000 half of each ceiling, twice the days fast 15 days 100 200,000 twice each ceiling, half the days Days are UTC days, whatever the server's time zone, and a start later than today is refused. Recipients, not messages. A message to forty people at Gmail is forty to Google. Before a delivery connects, its recipients reserve room out of the day's allowance; when the attempts are over, every recipient that was delivered is added to sent and the reservation is let go. A recipient that was not delivered is not counted. So a provider that refuses the warming address is offered as many recipients a day as the queue has; its slow-down replies are answered by the back-off. The counts are written to hm_warmupcounts at every change, so a restart resumes the day. Local delivery is not counted. The hold. Recipients that do not fit - the last of the batch, not chosen by any priority - are marked 4.4.5. When the day's ceiling was reached, the next try is one to five minutes after the next midnight UTC: SMTPDeliverer - Message 1234: 1 recipient(s) held by the IP warm-up of 192.0.2.10: day 1 of its standard schedule allows 50 recipient(s) a day at Google, and today's have been sent. Next attempt after midnight UTC, in 723 minute(s); the attempt is not counted against the retry limit. How long a backlog is carried. With the default schedule - four retries, sixty minutes apart, a five-hour span - a recipient queued in the morning comes back on each of the next five UTC days and is returned if the fifth still has no room. Send no more from a warming address than the sum of those days' ceilings, or raise the retry count for the duration of the warm-up. Managing schedules. On the Control Deck's Delivery queue page, in the Warm-up box, or over REST. The desktop Control Panel has nothing for it, and there is no COM interface. Route What it does GET /api/v1/warmup Today's UTC date and the schedules, each with its day, in_bind_setting, and per provider sent, deferred and ceiling POST /api/v1/warmup Adds one: address (required), plan (standard by default), started (YYYY-MM-DD, today by default), enabled PUT /api/v1/warmup/{id} Changes plan, started or enabled. The address cannot be changed DELETE /api/v1/warmup/{id} Removes a schedule and its counts GET /api/v1/warmup/{id}/history The counts, newest first, with the day and ceiling in force then Reading takes view_queue and changing takes manage_queue; a credential confined to named domains is refused. A second schedule for one address is 409. Back up settings carries the schedules but not the counts. When the database does not answer, the queue does not stop: schedules that cannot be read (7210) and a day's count that cannot be read or started (7211, 7212) leave the delivery going as though the address had no schedule. A count that cannot be saved (7213) is kept in memory; if the service restarts first, the day can go past its ceiling by that much. Where to see it GET /api/v1/queue/shaping (view_queue, server-wide): the open back-off windows - destination, since, until, the window's minutes, the reply and the host that gave it, how many deliveries it has held, whether a probe is out - and the connections open under each ceiling and each tenant. DELETE /api/v1/queue/shaping/{destination} (manage_queue) clears one window once you have fixed the cause: 200 with {"cleared":true}, 404 when no window is open for that name, and an event on the audit trail. The Deck's Delivery queue shows the same as Slowed destinations, with a Resume button, and the connection counts as they were when the page was drawn. The application log says, for each batch held, which rule held it and until when. What is per process. The windows and the connection counts live in memory. A restart forgets every window, which costs at most one more slow-down reply per destination. Nothing is shared between the nodes of a warm-standby pair. What it does not do The signal phrases, the providers and the plans are fixed. A sixth provider, or a plan of your own, is not possible. No per-tenant volume quota beyond the per-account one: this shares connections, not messages a day. No pool of addresses. A schedule is about one address, and mail that did not fit waits. No reputation feedback. The ceilings rise by the calendar, not by how the providers are answering. No delayed delivery report. A sender learns of a long hold only when it ends in a bounce. A deferral by a rate or a ceiling costs one delivery pass per message every one to five minutes for as long as the backlog lasts. The warm-up counts are not exported as metrics. Not proved by any test, according to IpWarmup.md: room held by deliveries in progress; a later host of the other address family skipped for being reached from another warming address; what a database failure does (7210 to 7213); the audit trail's entry; the configuration backup's round trip of the schedules; and an MX lookup's own host names reaching the classification, since every delivery in the fixture goes through a route. Where to read more Operator documents at the v6.3.4 tag: OutboundShaping.md, IpWarmup.md and RemoteDomains.md. Wiki: Outbound Shaping and Routes and Relays. Public documentation: 26.7 The delivery queue and the retry schedule and 14.1 Sending through a smart host. On this forum: The Control Deck, Upgrading to 6.3.4, Requiring TLS for the remote domain policy's TLS half, and DKIM rotation, DMARC and TLS reports, complaints, bounces and the reputation watch for bounces, complaints and block lists.
  • 0 Votes
    1 Posts
    147 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) accepts the UTF8 that classic Outlook for Windows (Outlook 2016 to 2024) sends after the POP3 login, at which its downloads had stopped; SMTP, POP3 and ManageSieve refuse a SASL PLAIN sign-in that asks to act as another account, and IMAP takes an authzid naming the client itself as none; IMAP's COPY and MOVE by message number read their set in the client's numbering and refuse one that names a message another session has just expunged (NO [EXPUNGEISSUED]), FETCH BODY[N.MIME] and BODY[N] of a multipart are the stored octets, and a refusal by the access control is NO [NOPERM]; and the IMAP and POP3 listeners can take the client's address from a PROXY protocol header, as the SMTP listener does, off by default. An authenticated submission is DKIM-signed as another hosted domain only when the account may send as the address its From names. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026) signs a client in only when its user name is the account's stored address, compared in lower case after a domain alias and the default domain. A user name that differs from it in more than the case of the letters A to Z, or has white space at either end, may no longer sign in: such a name fails as a name that does not exist fails, so correct the user name in the client. See the 6.3.8 announcement. This topic covers connecting a mail client to hMailServer 6.3.4: ports, authentication, folders, automatic configuration, app passwords and devices. The one thing to know first: a new installation of 6.3.4 refuses a password from any machine but itself until the session is encrypted, and an upgraded server is not changed. On either, the TLS ports 465, 993 and 995 exist only once you create them. The settings your users need Setting Value Why, and what goes wrong Incoming server (IMAP) mail.yourcompany.com, port 993, SSL/TLS Implicit TLS: the handshake comes before the greeting. On a port not configured as SSL/TLS the client hangs, then times out Incoming server (POP3) mail.yourcompany.com, port 995, SSL/TLS The same. Only use POP3 if you have a reason to Outgoing server (SMTP) mail.yourcompany.com, port 587, STARTTLS Port 25 is for other servers, not for your users; many ISPs block outbound 25 from residential lines User name The full email address, [email protected] A bare name works only with Maintenance → Advanced → Default domain (for unqualified logons) set Password The account's password, or an app password if the account is enrolled in TOTP See the app passwords section below Encryption Never "none", never "auto-detect" "Auto-detect" is how a client ends up sending a password in the clear The ports, and what 6.3.4 changes on a new installation A new database is seeded with four rows - SMTP 25, SMTP 587, POP3 110 and IMAP 143 - and up to 6.3.3 all four were without encryption. Add 465, 993 and 995 yourself under Connections & protocols → TCP/IP ports in the Windows Control Panel, or on the Control Deck, once you have a certificate of your own. Connection security What the server does None (0) Plain text; STARTTLS is not advertised. Everything, including the password, is in the clear SSL/TLS (1) Handshake first, banner after. Correct for 465, 993 and 995 STARTTLS optional (2) Advertises STARTTLS and accepts both. The right setting for port 25 only: on a submission port it lets a misconfigured client send its password in the clear STARTTLS required (3) Answers 530 5.7.0 Must issue STARTTLS first. to every command except NOOP, EHLO, STARTTLS and QUIT. Right for 587, and for 143 and 110 if you keep them; on port 25 it silently refuses mail from every server that cannot do TLS With TLS chosen and no certificate selected the listener does not start at all: HM5113, Error initializing SSL. Certificate not set., in the application log, connection refused, nothing in the SMTP log; pick a certificate on the port. A port already in use is HM4316, and the rest of the server starts normally. A new installation of 6.3.4 has all four seeded listeners at STARTTLS optional with a self-signed certificate, which a mail client warns about until it is replaced, and the Internet IP range requires TLS for authentication; My computer (127.0.0.1) is left as it was. So from another machine, until the client sends STARTTLS: SMTP AUTH is not offered, and is refused with 530 A SSL/TLS-connection is required for authentication. if it is sent; IMAP advertises LOGINDISABLED, and LOGIN and AUTHENTICATE answer BAD A SSL/TLS-connection is required for authentication.; POP3 USER and AUTH answer -ERR. After STARTTLS every one of them works: the range's rule, not the port's, is what protects the password. When ACME's first certificate arrives it is given to every listener holding the self-signed one: see Let's Encrypt certificates are automatic. An upgraded or attached database is never changed. On Linux the defaults are applied at the unit's first start only when it comes within a day of --create-database. Which databases get them, the verb that applies them by hand and the checklist item that judges an upgraded server are in Hardening a public mail server. The IP range, not the port. RequireTLSForAuth lives on the IP range (Access & abuse protection → IP ranges, the Require auth tab). A range that requires TLS is offered no authentication mechanism at all on a cleartext connection, and IMAP additionally advertises LOGINDISABLED, so a client learns before it sends the password. DisableAUTHList is a comma-separated list of local ports on which SMTP AUTH is neither advertised nor accepted; use it to keep authentication off port 25. Since 6.3.4 such [Settings] keys are kept in the database: change them in the Control Panel, over REST (PUT /api/v1/settings/ini/{name}) or on Linux with hmailserver --set-setting NAME=VALUE. The database's value is the one used; where the service can write hMailServer.ini, a line edited there is put back (HM5804). Which port, decided once. For sending, 587 with STARTTLS required, or 465 if the client does not do STARTTLS properly; for receiving, 993, or 995 for POP3 on exactly one device. Never 143 or 110 without STARTTLS required, and deny 143 and 110 at the firewall once no client is left on them. What the server will actually accept Protocol Mechanism Advertised when Default on a fresh install SMTP AUTH LOGIN AUTH is enabled on the port Yes SMTP AUTH PLAIN Allow plain-text authentication is on No - authallowplaintext is seeded 0 SMTP AUTH SCRAM-SHA-256 Always, when AUTH is enabled Yes SMTP AUTH SCRAM-SHA-256-PLUS On a TLS connection Yes, on a TLS connection SMTP AUTH XOAUTH2 / OAUTHBEARER OAuth2 is enabled, and by default over TLS No SMTP AUTH EXTERNAL The handshake verified a client certificate on that port No IMAP LOGIN command Unless the port or the IP range requires TLS on a cleartext connection Yes IMAP AUTH=PLAIN, AUTH=SCRAM-SHA-256 Allow SASL PLAIN authentication is on No - EnableImapSASLPlain is seeded 0 IMAP SASL-IR Allow SASL initial client response is on No POP3 USER/PASS, SASL PLAIN, SASL SCRAM-SHA-256 Unless authentication is refused on this cleartext connection Yes The switches are on Connections & protocols → Protocols, the SMTP and IMAP tabs. Two consequences look like client bugs when they are not. On a default install, an IMAP client that insists on AUTHENTICATE PLAIN finds no AUTH= capability at all and must fall back to LOGIN. And SCRAM is always the account's own password, so an account with a second factor enrolled, or whose password has passed PasswordPolicyMaximumAgeDays, cannot sign in over SCRAM at all. Outlook on the desktop will not use SCRAM: it needs LOGIN or PLAIN. What the conversation looks like An extract of IMAP on a default install: * OK IMAPrev1 a002 LOGIN alice secret a002 NO Invalid user name or password. Please use full email address as user name. a003 LOGIN [email protected] secret a003 OK LOGIN completed The hint is given because the user name has no @. SMTP submission on 587, STARTTLS required: 220 MAIL01 ESMTP EHLO laptop.example.net 250-MAIL01 250-SIZE 20971520 250-8BITMIME 250-PIPELINING 250-CHUNKING 250-BINARYMIME 250-SMTPUTF8 250-ENHANCEDSTATUSCODES 250-DSN 250-STARTTLS 250 HELP MAIL FROM:<[email protected]> 530 5.7.0 Must issue STARTTLS first. STARTTLS 220 Ready to start TLS There is no AUTH line in that first answer, because the port requires TLS. After the handshake the client must send EHLO again, since the session starts afresh (RFC 3207 section 4.2). The second answer carries 250-AUTH LOGIN SCRAM-SHA-256 SCRAM-SHA-256-PLUS. Folders your users will look for A brand-new mailbox contains INBOX and nothing else. Drafts, Sent, Trash and Junk are created only when Create Drafts, Sent, Trash and Junk for new accounts is switched on (Connections & protocols → Protocols → IMAP), and the setting is seeded off. That is the usual explanation for "my sent mail is not on the server": the client fell back to a local folder because it found nothing designated \Sent. Fact Value Hierarchy delimiter ., so a subfolder of Work is Work.Invoices Public folders namespace #Public Other users' folders namespace #Users, only when ACL (shared folder permissions) is on - it is on by default Special-use designations \Drafts \Sent \Trash \Junk, advertised as SPECIAL-USE and CREATE-SPECIAL-USE IMAP or POP3. hMailServer advertises EXPIRE NEVER, so "leave mail on server" means for good, and a second POP3 session on the same mailbox gets -ERR [IN-USE]. What 6.3.4 changed in IMAP's answers Dovecot's imaptest was first run against this server on 25 September 2026: 31 of its 71 scripts failed, and 4 after 6.3.4 made the server answer as the RFCs say. The capability list is unchanged. A client that relied on the old answers may notice these: When the client 6.3.4 answers Names a mailbox that does not exist, or needs a right the account lacks NO, where it used to answer BAD; a failed SELECT or EXAMINE leaves no mailbox selected Sends an unknown LIST selection or return option BAD Fetches any BODY[<section>] without .PEEK Sets \Seen, BODY[HEADER] and BODY[1.MIME] included Appends a message Stores the literal byte for byte, where it used to add a CRLF, so RFC822.SIZE of a newly appended message is two octets smaller Pipelines a command behind an APPEND with a non-synchronising literal Answers it; it used to be thrown away, which stopped mbsync after one message Sorts, or threads Uses every criterion and builds the RFC's trees, so either can order messages differently Is the first session to be told of new messages Counts them as \Recent, where it said * 0 RECENT Automatic client configuration The server can configure clients, so users type only their address and password. AutoconfigEnabled is already 1; giving the web-services listener a port is what turns it on (WebServicesHttpPort=80, WebServicesHttpsPort=443), at the next reinitialise or restart, since the listener binds when it starts. Both port keys default to 0. Then point autoconfig.yourcompany.com (Thunderbird) and autodiscover.yourcompany.com (Outlook) at the server; both are ordinary host records. URL path Format Fetched by /mail/config-v1.1.xml, /.well-known/autoconfig/mail/config-v1.1.xml Thunderbird autoconfig XML Thunderbird and its relatives /autodiscover/autodiscover.xml Outlook POX autodiscover XML Outlook /email.mobileconfig, /mail/config.mobileconfig Apple configuration profile iOS and macOS Mail Which port ends up in the answer. The server reads your actual port rows and picks the best one per protocol: Rank Port configuration socketType written 100 SSL/TLS (implicit) SSL 90, 80 STARTTLS required on port 587; on any other port STARTTLS 70, 60 STARTTLS optional on port 587; on any other port STARTTLS 10 No encryption plain A protocol with no port at all is omitted. The host name is AutoconfigClientHost if set, otherwise the server's configured host name; with both empty the endpoint answers 404. Delegated mailboxes (since 6.3.4). The Outlook answer also names an AlternativeMailbox for every account that has opened a folder to the address asked about, but only to a request with HTTP Basic credentials, over HTTPS, for that very address. An Outlook IMAP profile never sees it: Outlook reads the element only for an Exchange account, and sends credentials only after a 401, which this server never returns here. What puts a delegated mailbox into a client's folder tree is the IMAP #Users namespace. None of this has been measured in Thunderbird or Outlook. Accounts with a second factor: app passwords A mail client has nowhere to type a six-digit code. Once a TOTP secret exists on an account, the account password is no longer a mailbox credential, and an app password is the only one. The order matters, and the Windows Control Panel puts the tabs in that order on purpose: Open the account: Accounts & domains → Domains → your domain → Accounts → the account. App passwords tab: create one per device, and write down the generated value; it is shown once. Two-factor tab: enrol the secret. Do it the other way round and every configured client stops working with no way back in. App passwords work for Active Directory accounts too, and are unaffected by password expiry, so enabling PasswordPolicyMaximumAgeDays does not silently break configured clients. A person makes their own on the webmail's Security page, which asks for the account's password again. Passkeys and single sign-on do not reach a mail client (since 6.3.4). They sign a person in to the webmail and the Control Deck and nothing else. An account that single sign-on or SCIM made has a random password nobody was told, so it needs a password set by an administrator before its person can make an app password for a phone. A recovery code is not a mail-client credential either. A mailbox encrypted at rest takes its own password only (since 6.3.4). It cannot enrol a TOTP second factor; on IMAP and POP3 its app passwords, an OAuth2 token, a client certificate and a Kerberos ticket are refused, and SCRAM does not open a key that is closed, so the client signs in with IMAP LOGIN or AUTHENTICATE PLAIN, or POP3 USER/PASS or AUTH PLAIN; and a client that was signed in when the switch was made is disconnected once. Devices: what revoking does Every client that has authenticated is one entry on the account's Devices list. Revoking a mail client's entry drops a live IMAP, POP3 or SMTP connection where it stands; a CalDAV, CardDAV or ManageSieve client has no connection to drop, so revoking one means revoking its app password. A revoke is not a ban: a client whose credential still works signs in again, so turn off its app password too, or change the account's password, which drops every connection of the account. Where the list is and what revoking does exactly are in Signing in. When it does not work What the user sees What the server said Cause Fix "Invalid user name or password", with a hint NO Invalid user name or password. Please use full email address as user name. Bare user name, no default domain Use the full address "Invalid user name or password", no hint NO Invalid user name or password. Wrong password, inactive account or domain, a TOTP-enrolled account given its account password, or, since 6.3.8, a user name that differs from the stored address in more than the case of the letters A to Z or has white space at either end Check both are active; issue an app password; give the address as stored "Authentication required" on sending 530 5.7.0 SMTP authentication is required. The client is not logged in for outgoing mail, or is on port 25 Tick "my server requires authentication"; move to 587 "Must issue STARTTLS" 530 5.7.0 Must issue STARTTLS first. Client encryption set to none on a STARTTLS-required port Set the client to STARTTLS A new 6.3.4 installation refuses the password from another machine 530 A SSL/TLS-connection is required for authentication. The Internet range requires TLS for authentication Set the client to STARTTLS, or use 465, 993, 995 once created Client hangs on connect, then times out Nothing: no session is created Implicit TLS expected on a STARTTLS port, or the reverse Match the port's connection security Repeated logon failures, then silence NO Too many invalid logon attempts. and a BYE Ten failures on one connection, the per-name lockout, or the per-IP auto-ban Access & abuse protection → Auto-ban Large attachment rejected 552 5.3.4 Message size exceeds fixed maximum message size The maximum message size, default 20480 KB Max message size, or the domain's own limit While diagnosing on Windows, look at Monitoring & troubleshooting → Live logs and Server status, which lists the listeners the service actually brought up. Per client: if Thunderbird offers "no encryption" it found a plain port, so fix the port, not the client. Where to read more Wiki: Connecting a Mail Client, Ports Reference, Encryption and Certificates, Sign-in and Identity. Documents: SecureDefaults.md, DevicesAndSessions.md. The public documentation: 7. Connecting a mail client, 7.2 Automatic client configuration, 22. Ports reference. On this forum: Let's Encrypt certificates are automatic, IMAP and POP3 on the server for shared mailboxes, when a saved setting takes effect, First install on Linux, Upgrading to 6.3.4 and the 6.3.4 announcement.
  • 0 Votes
    1 Posts
    124 Views
    ProgressiverobotP
    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
  • 0 Votes
    1 Posts
    125 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.6 (6 October 2026) carries Linux packages for x86-64 and AArch64 and one for each of ten distribution releases, both AppImages, an ARM64 Windows installer (a preview) and the first container image. See the 6.3.6 announcement. hMailServer 6.3.7 (17 October 2026) is a security release: mail from outside whose From names a domain hosted here is no longer signed with that domain's DKIM key, an authenticated submission is signed as another hosted domain only when its account may send as the address its From names, and a forward of outside mail is given an envelope sender in the forwarding domain's organisation only when the message passed DMARC for it on arrival. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026), the current release, is a security release: it fixes vulnerabilities the project found in its own code, so upgrade every server running 6.3.7 or earlier. See the 6.3.8 announcement. This tutorial is for someone who has not run a mail server before. It follows a message from one person to another, shows where hMailServer sits on that journey, and says what the software does and does not do. The one thing to know first: port 25 has no password, and it has to be that way. Any server on the internet may connect to your port 25 and offer you mail for your own domains, or nobody could write to you. Your own users send through a different door, port 587, which requires a login. Most of what follows is about keeping those two doors apart. What hMailServer is hMailServer is a mail server: the software that owns an email domain. When someone sends a message to [email protected], their mail server looks up where yourcompany.com receives mail and delivers it to whatever is listening there. If you run hMailServer, that is your machine. Progressive Robot Ltd forked hMailServer in 2026 and maintains the hMailServer 6 line; the current release is 6.3.8, of 2 November 2026. Job Protocol What it means Accept mail from the world SMTP Other mail servers connect and hand you messages for your users Let your users read mail IMAP and POP3 Outlook, Thunderbird, Apple Mail and phones connect to fetch messages Let your users send mail SMTP submission Your users hand you messages and you deliver them onward Where it sits, and what is not yours Running your own mail server means owning the middle of three boxes. DNS is your registrar's, and the mail clients are your users'. The wiki puts it plainly: almost every problem people describe as "hMailServer is broken" turns out to be in one of the two boxes it does not own. You provide hMailServer provides A machine reachable from the internet The SMTP, IMAP and POP3 servers, and the whole delivery pipeline A host name with an A record, and an MX record pointing at it The MTA-STS policy derived from that MX, and a live check of every record A PTR record, which only your ISP can set Nothing can substitute for it SPF, DKIM and DMARC records in your zone The DKIM key generation with a copyable record, the signing, and the inbound evaluation of everyone else's Port 25 reachable inbound, and outbound (many consumer ISPs block outbound 25) Nothing can work around a blocked port; a smart host is the usual answer A certificate, or nothing, and let ACME get one Built-in Let's Encrypt issuance and hourly renewal Somebody who will read a log occasionally The logs, the diagnostics, the message trace and the metrics to read A message's journey You send mail to [email protected]. This is what happens: Your mail client submits the message to your hMailServer on port 587. You log in first. hMailServer answers 250 Queued. It asks DNS who receives mail for example.com. The MX record answers: mail.example.com. It looks up the A and AAAA records for that host, plus TLSA and the MTA-STS policy. It delivers the message by SMTP on port 25: no login, TLS if offered. The recipient's server answers 250 OK and stores the message in Alice's mailbox. Alice's mail client reads it over IMAP. An open relay is the thing to avoid. Never let an unauthenticated stranger send mail through you to the outside world. The wiki's warning is that it will get your server blacklisted within hours. hMailServer refuses this by default. A note on ports. A fresh install seeds ports 25, 587, 110 and 143. The TLS ports 465, 993 and 995 exist only after you create them on the TCP/IP ports page. A submission, and the checks in the order the server runs them The order matters, because it decides when a sender is told no: before the message body is transferred, or after. Stage What is checked Reply when it fails Any command On a STARTTLS required port, everything but NOOP, EHLO, STARTTLS and QUIT 530 Must issue STARTTLS first. MAIL FROM Free disk space: MinimumFreeDiskSpaceMB, default 100 452 4.3.1 Insufficient system storage MAIL FROM Send-As, only when SmtpAuthenticatedSenderCheck=1 (default 0) 550 5.7.1 Sender address rejected MAIL FROM Per-IP rate: MaxSubmissionsPerIPPerMinute (default 0, unlimited) 421 Too many messages from your IP address MAIL FROM Declared SIZE against the domain's maximum message size (default 20480 KB) 552 5.3.4 Message size exceeds fixed maximum message size RCPT TO Is the address deliverable at all? 550 Unknown user, or 451 4.3.2 Unable to verify the recipient at the moment RCPT TO Mailbox full 452 4.2.2 RCPT TO Relay permission for this IP range 550 Delivery is not allowed to this address. RCPT TO Authentication required and not supplied 530 SMTP authentication is required. RCPT TO Greylisting, when it is on (default off) 451 Please try again later. end of data Actual size, bare LF check, post-transmission spam tests 552 5.3.4, 554 5.6.0 Rejected - Message containing bare LF's, 554 5.7.1 4xx and 5xx. A 4xx reply always means "come back later", while a 5xx means "never". The difference decides whether a legitimate sender's message waits in their queue or is destroyed, which is why a full mailbox is 452 and not 550. Refusing early. Every check at MAIL FROM and RCPT TO happens before a single byte of the message crosses the wire. Refusing early costs the sender nothing, and it generates no bounce to an address a spammer only claimed to be. The timing in the last reply is real. A submission ends with a line such as 250 2.0.0 Queued (0.412 seconds): the server times the accept-and-save stage and reports it. A number that climbs into whole seconds is the first sign of a slow spam test, a stalled virus scanner or a saturated work queue. Since 6.3.4 the same session has more places to say no, each of them off until an administrator sets it: country blocking, a domain white and black list, restricted delivery, mail flow rules and others. One thing differs without a switch, and only on a new installation, not an upgrade: the Internet IP range requires TLS for authentication, so from another machine AUTH is not offered before STARTTLS. Who may send through you This is the decision that separates a mail server from an open relay, and hMailServer makes it per IP range. Mail from other domains to yours is taken on port 25 without a password, or nobody could write to you. From the Internet range, mail that gives one of your own domains as its sender needs a login, whoever it is for, and relaying from one remote address to another is allowed on both shipped ranges only with authentication: a stranger who has not logged in is answered 530 SMTP authentication is required. A range that allows relaying without authentication must never exist (the Control Panel's status page warns of one as an open relay). Ranges are matched by priority, highest first, so a narrow range for your own network can sit in front of the broad Internet range; IP ranges, auto-ban, lockout and the rest has the whole grid and the shipped values. After "250 Queued": the delivery pipeline Acceptance and delivery are separate. The 250 means the message is on disk and in the database; delivery happens on the delivery threads afterwards, in this order: The OnDeliveryStart script event. Virus scanning. Global rules. The OnDeliverMessage script event. DKIM signing, then mirroring, each on the first attempt only. Local delivery: account rules, Sieve, forwarding, distribution lists, public folders. External delivery for any recipient outside this server: routes or an MX lookup, MTA-STS, DANE, TLS, then SMTP. A temporary failure is rescheduled: 4 tries, 60 minutes apart by default. A permanent failure, or retries exhausted, produces a delivery status notification to the sender. Where the filters sit Anti-spam runs in two phases, before and after the message is transmitted, and a test's phase is fixed by the test. Two thresholds decide what a score means: mark (default 5) adds spam headers, and delete (default 20) refuses the message, or quarantines it when the quarantine is on. On a fresh install almost every test is off. zen.spamhaus.org, bl.spamcop.net and multi.surbl.org are seeded but inactive; the HELO, PTR, MX and SPF tests are off; SpamAssassin is off; greylisting is off. DMARC is on (ASDMARCEnabled 1), and it evaluates SPF and DKIM itself for its own purposes. Anti-spam does not run at all for an authenticated session, for a whitelisted sender, or for an IP range with spam protection switched off. Reading mail back: IMAP or POP3 IMAP POP3 Mail lives on the server on one device Folders yes, and shared/public folders too INBOX only Read/unread, flags shared across devices per device New-mail notification IDLE pushes it polling only Server-side search yes no Concurrent sessions many one at a time; a second gets -ERR [IN-USE] Default ports here 143, and 993 once you create it 110, and 995 once you create it The wiki's advice is short: use IMAP. The three records that make mail arrive SPF: a list of the servers allowed to send mail for your domain. DKIM: a cryptographic signature proving the message really came from you and was not altered. DMARC: a policy saying what to do when SPF and DKIM disagree, and where to send reports. Without them your mail will go to spam. The wiki calls this the most common reason a new mail server "doesn't work". Note the asymmetry: hMailServer signs your outgoing mail with DKIM as soon as you configure a key, but checking SPF on incoming mail is off by default. Being trusted by the servers you send to has the detail. What hMailServer is not It was not a webmail client; since 6.3.2 it is one. The page at /portal on the REST listener became a mail client in 6.3.2, and a separate webmail such as Roundcube is no longer needed, though it still works over IMAP. Of 6.3.4's paging of a folder of a hundred thousand messages, the wiki says: built, and not yet measured. See The webmail portal at /portal. It is not a spam filter you can point at someone else's mail. It filters mail it receives, for domains it hosts. It is not Exchange. There is no ActiveSync, no MAPI and no Teams integration, so Outlook connects over IMAP and SMTP like any other client. Since 6.3.4 it carries the pieces of groupware that ride on standards, such as calendars over CalDAV. Who should run it: a small business that wants its own mail on its own server; a developer who needs a real SMTP/IMAP endpoint for testing; a hosting provider running mail for many domains. Who should not: if nobody in your organisation is comfortable editing DNS records and watching a log file occasionally, a hosted mailbox provider will serve you better. What you run it on Windows Linux Platform 64-bit Windows 10 1607 / Server 2016 or later Since 6.3.0. 6.3.4's packages are x86-64 only: a .deb for Ubuntu 26.04 and 24.04, an .rpm for Fedora 43 and 44 and Enterprise Linux 10 Administration The Control Panel, the COM API, the Control Deck and the REST API The Control Deck and the REST API. The Control Panel and the COM API are Windows-only Event scripts VBScript or JScript JavaScript, since 6.3.4 The AArch64 packages and the AppImage were last published with 6.3.3. 6.3.4 is installed by hand; no live update installs it. hMailServer on Linux: what you get and what you do not and First install on Linux go further. "Additive and off by default" The design rule is that an existing installation upgrades without behaving differently until you turn something on. Category State on a fresh install Optional listeners: the REST API and /portal, web services, metrics, ManageSieve Off. Each stays inert until its port setting is non-zero Optional features: quarantine, message trace, full-text index, archive Off Outbound transport security: MTA-STS, DANE, DNSSEC validation On (MtaStsEnabled, DaneEnforcementEnabled, DnssecValidationEnabled). Each can be switched off A new installation of 6.3.4, not an upgrade A self-signed certificate, STARTTLS offered on 25, 587, 110 and 143, TLS required for authentication from the Internet range: see Hardening a public mail server Since 6.3.4 the database is the settings store: a [Settings] line edited in hMailServer.ini is ignored in favour of the stored value, and put back where the service can write the file. Words you will meet Term Meaning MTA Mail Transfer Agent: a mail server that accepts and forwards messages. hMailServer is one MSA Message Submission Agent: the server your own users hand mail to, normally on port 587, normally with authentication MUA Mail User Agent: the mail client Relay Accepting a message for a domain you do not host and passing it on Smart host Another server you send all your outbound mail through, often your ISP's STARTTLS Upgrading a plaintext connection to an encrypted one, in-band Implicit TLS TLS from the first byte, no upgrade step: ports 465, 993, 995 Envelope vs header The MAIL FROM/RCPT TO addresses the servers use, versus the From:/To: a person reads. They need not match Deferral Holding a message for a later attempt after a temporary failure Backscatter Bounces sent to a forged sender address MX record The DNS record saying which server receives a domain's mail PTR record Reverse DNS: maps an IP back to a host name Where to read more Wiki: How Email Works, What hMailServer Is and Is Not and the Glossary. The public documentation: chapter 1, chapter 2 and the glossary. On this forum: Read this before you post and Where to post what; for the next steps, Your first domain and mailbox, and the DNS records your domain needs, Connecting a mail client, The anti-spam stack end to end and Every port hMailServer listens on.
  • 0 Votes
    1 Posts
    119 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) adds the PROXY protocol to the IMAP and POP3 listeners: each takes the client's address from a PROXY header as the SMTP listener does, from the peers on a list of its own, and both are off by default; their switches and peer lists are on the IMAP and POP3 cards of the Control Panel's Protocols page. The SMTP listener's PROXY protocol and XCLIENT are a card on Incoming relays, not on Server limits & expert settings, and Ctrl+K takes every old title to its page. A state-changing REST request that carries a credential is now refused when the browser reports it as cross-site (Sec-Fetch-Site, or an Origin that is not this server's); scripts, hmctl and the PowerShell module send no such header and are unaffected. The ACME HTTP-01 challenge responder, which served one connection at a time, serves up to 64 at once. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026) no longer charges a failed sign-in through a proxy to the proxy: at the web services listener one from loopback or an address in WebServicesTrustedProxies, and at the REST listener an account's from loopback, is counted against the name alone, never towards a ban of the address, where up to 6.3.7 a ban could be written that shut the proxy, or 127.0.0.1, out of the SMTP, POP3, IMAP and ManageSieve listeners. The web services listener now also refuses, before the TLS handshake, an address whose IP range allows none of SMTP, POP3 and IMAP, an auto-ban's or one of your own, though never loopback or an address in WebServicesTrustedProxies: where mail reaches the server only through a relay or a VPN and the Internet range allows nothing, that range now closes ACME's http-01 validation when this listener answers it, MTA-STS, CalDAV, CardDAV, Autodiscover, autoconfiguration and the Web Key Directory to the addresses it holds, so check before upgrading that the certificate authority, the sending MTAs and these services' clients do not come from it. See the 6.3.8 announcement. This topic is for a server that does not face the internet alone: behind a hosted spam filter or a filtering appliance, behind a load balancer, or with a reverse proxy terminating HTTPS in front of the webmail, the Control Deck, CalDAV and CardDAV. The one thing to know first: hMailServer judges the address that connected to it, and behind a front end that address is the front end's until you tell the server otherwise. Auto-ban, the rate limits, the IP ranges and every spam test then reason about the wrong machine: SPF fails on every message, and a block list that lists your gateway blocks all your mail. Each kind of front end is handled in its own way, and behind an HTTP reverse proxy the address stays the proxy's. Which mechanism fits which front end Front end What reaches hMailServer What tells it the truth A filtering gateway, hosted or an appliance, that accepts the mail and sends it on A new SMTP session from the gateway, the sender's address in its Received header Incoming relays, XCLIENT if the gateway can send it, and the gateway setting profile A load balancer or TLS terminator in front of the SMTP listener The client's session, from the balancer's address HAProxy PROXY v1 or v2 A reverse proxy terminating HTTPS for /portal, / and /api/v1 HTTP from the proxy X-Forwarded-Proto: https from the proxy on the same machine, for the cookies; the address stays the proxy's A reverse proxy in front of CalDAV, CardDAV and the other web services HTTP from the proxy X-Forwarded-Proto: https, believed from loopback and from WebServicesTrustedProxies Incoming relays: what listing one changes Mail flow & delivery → Incoming relays in the Control Panel (Windows) lists servers that legitimately forward mail to you, each a name and an IP range (a lower and an upper address); a save applies at once. The same list is in the Control Deck, at /api/v1/incoming-relays over REST since 6.3.3 (a key confined to named domains is refused), and in COM Settings.IncomingRelays on Windows, where since 6.3.4 a change takes effect only at Save(). Listing a relay changes two things: When spam protection runs. A connection from a listed relay is switched from pre-transmission to post-transmission testing. DNSBL, PTR and HELO checks judge the connecting IP, so they wait until the body has arrived and the real origin can be read from it. Spam is then no longer refused before the body: with quarantine on, a message reaching the delete threshold is held for review; without it, it is refused with 554 5.7.1 after the body. Which IP counts as the origin. The Received chain is walked from the top, and the first address that is not a listed relay becomes the originating IP. SPF, DMARC and ARC are evaluated against it. Received: from gw2.example.net [198.51.100.8] listed relay: keep walking Received: from gw1.example.net [198.51.100.7] listed relay: keep walking Received: from mail.sender.example [203.0.113.44] not listed: the originating IP Since 6.2.24 the address taken is the one the relay observed, not the client's own HELO literal; thresholds tuned before then need checking again. Since 6.3.4 a listed relay changes more: A remote domain policy's inbound rule, Require TLS on mail from this domain (530 5.7.0 at MAIL FROM), is not applied to it. The hop that rule is about happened before the gateway, so require TLS on the gateway. Requiring TLS and remote domain policies covers the policies. Mail it hands over counts as coming from inside. One rule, MessageOrigin::IsExternalSender, decides that for the external-sender tag, the first-contact note and impersonation protection, so none of the three applies behind a listed gateway. DomainTransforms.md: the choice is between the tag and the relay declaration, and there is no third answer today. Mail flow rules and what a domain does to a message covers the tag. A spam trap never bans it; the sender is still judged. The domain white and black list sees the relay's own HELO name, so key rows for mail behind a relay on the envelope domain. Keep the range to the exact gateway addresses. An attacker inside a range listed too wide can insert a forged Received header and choose which IP you evaluate. And leave spam protection on for the IP range the gateway connects from: clearing Enable anti-spam on a range turns off every spam test, greylisting and the recipient tarpit for it, relay or not. Symptom Cause Every message is scored as though it came from your own filter; SPF always fails The front end is not listed as an incoming relay A DNSBL hit on the gateway's address blocks all mail The same Adding a relay stopped spam being rejected before the body Expected: testing moved to after the body. Quarantine and scoring still apply PROXY protocol and XCLIENT If the front end can pass the real client address itself, that is better than reconstructing it from headers. Two mechanisms have been accepted since 6.2.23-alpha1, both off by default and both honoured only from the addresses you list: Setting Default What it does SMTPProxyProtocolEnabled 0 HAProxy PROXY v1 and v2 on the SMTP listener, read before TLS and before the banner SMTPProxyProtocolTrustedIPs empty The real TCP peers allowed to send a PROXY header, comma-separated addresses or CIDR ranges. Empty means nobody SMTPXClientEnabled 0 The Postfix XCLIENT verb SMTPXClientTrustedIPs empty The peers allowed to use it. Empty means nobody Trust is decided against the real TCP peer, never an address a header supplied, so chained proxies cannot bootstrap trust. An entry that does not parse matches nothing and is logged once per run (HM6281). Once an address is listed for PROXY, the header is required from it. Configure the proxy to send it (in HAProxy, send-proxy or send-proxy-v2) or its connections are dropped; and a PROXY line from a peer that is not listed closes the connection without a reply. Change the proxy and the settings together. XCLIENT is advertised in EHLO only to a trusted peer, as XCLIENT ADDR NAME PORT PROTO HELO LOGIN; an untrusted peer never learns the verb exists and gets 550 if it tries. With LOGIN the upstream vouches for a signed-in account: RoomsAndEquipment.md counts such a submission as a signed-in session, like SMTP AUTH. Listing an address for XCLIENT trusts it with that too. What then sees the real client: auto-ban, the rate limits, the IP ranges, the spam tests, and since 6.3.4 the compromised-account detection's address signal (which never scores behind a proxy that speaks neither) and, with PROXY, the live view of one connection. What does not: country blocking (since 6.3.4). It judges the address the TCP connection came from, the proxy, and exempts loopback and private ranges; put the geographic policy on the proxy. Since 6.3.7 the IMAP and POP3 listeners take the client's address from a PROXY protocol header too, each from the peers on a list of its own (IMAPProxyProtocolEnabled and IMAPProxyProtocolTrustedIPs, POP3ProxyProtocolEnabled and POP3ProxyProtocolTrustedIPs), both off by default, the header read before an IMAPS or POP3S port's TLS handshake and before the greeting. No document describes either mechanism for ManageSieve. Where they are set. On Windows, the Control Panel's Maintenance → Server limits & expert settings (card Front-end proxies (PROXY protocol and XCLIENT); in 6.3.7's Control Panel the PROXY protocol and XCLIENT are a card on Incoming relays) or COM Settings.SetIniSetting. On either system, PUT /api/v1/settings/ini/{name} (the administrator password only; an API key is refused) or hmctl ini set <name> <value>; on Linux also hmailserver --set-setting. The Control Deck has no editor for these keys. All four apply when saved. Since 6.3.4 the database holds them, so a line edited in hMailServer.ini changes nothing the server uses; the store, the file's copy and HM5804 are in Settings: what is not stock and when a save takes effect. # Over REST with the administrator password (an API key is refused), from any machine that reaches the listener; applies when saved hmctl ini set SMTPProxyProtocolTrustedIPs 10.0.0.5,10.0.0.6 hmctl ini set SMTPProxyProtocolEnabled 1 # On a Linux server; a running service takes it at its next save, reinitialise or restart sudo hmailserver --set-setting SMTPProxyProtocolTrustedIPs=10.0.0.5,10.0.0.6 sudo hmailserver --set-setting SMTPProxyProtocolEnabled=1 The gateway profile (since 6.3.4) A setting profile is a named, reviewed set of settings, shown as a diff first. gateway is for a server whose inbound mail a filtering gateway has already judged. Its 17 entries include: Entry Value Why (Profiles.md, shortened) usegreylisting 0 It judges the connecting address, which is the gateway's even through Incoming relays SMTPXClientEnabled 1 Honoured only from SMTPXClientTrustedIPs, and from nobody while that is empty ASArcFilteringEnabled 1 With DMARC on, the DMARC penalty is offset when a sealer named in arc_trusted_sealers sealed a pass MaxSubmissionsPerIPPerMinute, SmtpTarpitCount 0 All inbound mail comes from the gateway's few addresses QuarantineEnabled, RejectFullMailboxAtRcpt 1 A message at the delete threshold is held, not refused back to the gateway; a full mailbox is refused at RCPT so the gateway returns the message usespf, ascheckhostinhelo, ascheckptr, ASDMARCEnabled 1 Through Incoming relays they judge the real sender AutoBanOnLogonFailureEnabled, SmtpAuthenticatedSenderCheck 1 For people who sign in here directly The rest switch on DKIM verification, the MX check and attachment blocking, all three off as shipped, and keep the reason headers on. The first four rows turn a check off or trust an address range, so applying asks for an explicit confirmation wherever one of them would change something. A profile never writes an incoming relay, an IP range, a port's TLS or the ARC sealer. The steps are yours, in this order: Add the gateway's addresses to Incoming relays before applying the profile, and leave spam protection on for their IP range. Until they are there, the tests judge the gateway's own address. If the gateway sends XCLIENT, put its addresses in SMTPXClientTrustedIPs. Name the ARC sealer the gateway signs as in arc_trusted_sealers (PUT /api/v1/settings/antispam, or COM AntiSpam.ArcTrustedSealers). Until then no seal is trusted. Read the diff and apply it, on the Control Panel's Setting profiles page under Maintenance, in the Deck's checklist, over REST or with hmctl, confirming the entries that ask. The apply is all or nothing and saves the values it replaces first; while an earlier profile is neither reverted nor discarded, another apply is refused with 409. The routes, the diff's token, the way back and the command-line verbs are in the profiles section of the unattended installs topic. Check "Require SSL/TLS when authenticating" on the Internet IP range, or make the submission port STARTTLS-required, for people who sign in here directly. A new installation turns it on; an upgraded one keeps what it had. On the listener the gateway delivers to, leave TLS optional unless the gateway offers it: a port's security is the port's, not a setting. A filter on the same machine Since 6.3.4 SPF is RFC 7208, and 127.0.0.0/8 and ::1 no longer pass unconditionally. A content filter that re-injects mail over loopback should authenticate, or connect from an IP range with anti-spam off. The HTTP listeners behind a TLS-terminating proxy REST listener Web-services listener Serves The webmail at /portal, the Control Deck at /, the API under /api/v1 ACME challenges, MTA-STS, security.txt, autoconfiguration, CalDAV, CardDAV, the OpenPGP key directory Plain HTTP behind the proxy Only on loopback: TLS is required unless bound to 127.0.0.1, localhost or ::1 WebServicesHttpPort Whose X-Forwarded-Proto: https counts The proxy on this machine Loopback, and WebServicesTrustedProxies (since 6.3.3) What it decides Secure on the session and sign-in cookies Whether CalDAV and CardDAV treat the request as HTTPS; the Apple .mobileconfig profile; the key directory A changed port or bind address (RestApiPort, RestApiBindAddress, WebServicesHttpPort, WebServicesHttpsPort) waits for a reinitialise (Apply now). WebServicesTrustedProxies applies when saved, and no document names a Control Panel field for it: set it with PUT /api/v1/settings/ini/{name}, hmctl ini set, COM Settings.SetIniSetting on Windows or hmailserver --set-setting on Linux (which a running service takes at its next save, reinitialise or restart). The cookies, since 6.3.4. hmailsession and the sign-in cookies hmoidc, hmsso and hmpasskey are marked Secure over TLS, or when the proxy on this machine says X-Forwarded-Proto: https. Before 6.3.4 only a TLS listener counted, so behind the usual proxy a browser would also send the session cookie over plain HTTP. A proxy that does not set the header still leaves them without Secure; a proxy on another machine can only reach a REST listener that speaks TLS itself. CalDAV (since 6.3.4) and CardDAV take HTTP Basic over HTTPS only: over plain HTTP the answer is 403 with the reason, unless a trusted proxy says X-Forwarded-Proto: https. From any other peer the header is ignored; an entry that does not parse matches nothing (HM6281). The Apple profile honours the header on the same terms, and the OpenPGP key directory (since 6.3.4) answers a plain request with the same 404 as an unknown address; point openpgpkey.<domain> at the proxy and forward /.well-known/openpgpkey/ with the header. The CalDAV and CardDAV topic covers the rest. The client address stays the proxy's. Nothing reads X-Forwarded-For, so: The auto-ban leaves 127.0.0.1 and ::1 alone, and behind a proxy on this machine every webmail and Deck request comes from there. What brakes guessing is the per-name lockout, when AccountLockoutThreshold is set, and since 6.3.4 each owner's second-factor budget: five wrong codes in fifteen minutes refuse that owner's codes for the next fifteen (429, second_factor_locked). The forgotten-password route (since 6.3.4) holds loopback to thirty requests in any sixty seconds, all together. A proxy on another host shares one per-client bucket of twenty an hour. Neither has a setting: put the proxy on the mail server or accept the cap. For CalDAV, CardDAV and Autodiscover, since 6.3.8 a failed sign-in from loopback or an address in WebServicesTrustedProxies is counted against the name alone, never towards a ban of the address, and the listener never refuses those addresses by an IP range, so only proxies belong in that list, and the per-name lockout (AccountLockoutThreshold, off by default) is the limit on the guesses through one. Up to 6.3.7 each such failure was charged to the proxy's address, and a ban written for it could shut the proxy, and every client behind it, out of the SMTP, POP3, IMAP and ManageSieve listeners (AutoBan.md). Passkeys (since 6.3.4). By default the relying party id is the Host header, which behind a TLS-terminating proxy is still what the browser sent, so the check holds. X-Forwarded-Proto is not consulted, which is why the origin check accepts https at the page's host and plain http only on a loopback name. A server reached at several names needs passkey_relying_party and passkey_allowed_origins together, the origins written as the browser writes them, port included; the topic on signing in has the rest. The address people use. The server cannot learn it from the proxy. The links in a message that expires, a password-protected message and a password reset use confidential_base_url (then quarantine_digest_base_url, then import_oauth_redirect_base_url); the quarantine digest's links use quarantine_digest_base_url alone, a mailing list's use list_base_url, and single sign-on (since 6.3.4) uses oidc_redirect_base_url. Which paths to publish is in The webmail portal at /portal. Compression: let one side do it (since 6.3.4) Both listeners compress when the client asks, and there is no setting. Negotiation. Accept-Encoding is the whole of it: gzip or deflate, gzip where both are acceptable, never br. deflate is the zlib format of RFC 1950, which a raw inflater fails on; ask for gzip. What. Text, JSON, XML, a manifest, message/rfc822 and an icon, between 1,024 bytes and 4 MB. An image, a PDF or a zip goes as it is, and a download (/api/v1/me/messages/{id}/attachments/{index}, /files/{token}) is never compressed. Headers. Content-Length is the bytes sent; no chunked transfer encoding, no byte ranges. Vary: Accept-Encoding is on every answer of a type it would compress, which matters for the web-services listener, whose answers may be cached; the webmail and the API are Cache-Control: no-store. Behind a reverse proxy, either leave Accept-Encoding alone and pass the answer through (the server will not encode twice, and the proxy must not either), or strip Accept-Encoding from the request and compress at the proxy. What does not is forwarding Accept-Encoding: gzip, receiving Content-Encoding: gzip, and handing the bytes to a client that did not ask. What it does not do It reads no forwarded-for header. XCLIENT is an SMTP setting only; since 6.3.7 the PROXY protocol is taken on the IMAP and POP3 listeners too, each from its own list of peers. A listed gateway turns off the external-sender tag, the first-contact note and impersonation protection for what it hands over. Compression has no switch. A client that wants none sends no Accept-Encoding, or Accept-Encoding: identity. A second machine behind a balancer is the warm standby and high availability topic. Where to read more Wiki: Routes and Relays §14.3, Security Hardening §17.8, Stopping Spam §10.1, Settings Overview, and chapter 14.3 of the documentation page. Documents at the 6.3.4 tag: Profiles.md, ResponseCompression.md, DomainTransforms.md, PasskeysAndSingleSignOn.md, DevicesAndSessions.md, AccountRecovery.md, CountryBlocking.md, SettingsApply.md, RoomsAndEquipment.md, and README.md for the [Settings] notes on PROXY, XCLIENT and the web services. On this forum: The webmail portal at /portal, The Control Deck, the anti-spam stack end to end, IP ranges, auto-ban, lockout and tarpits and TLS on the listeners. A way past one of these trust lists is a vulnerability: report it by a confidential GitLab issue or by email to the project's Service Desk address, contact-project+hmailserver-hmailserver-86730559-issue-@incoming.gitlab.com, which GitLab files as a confidential issue, and not in a forum post.
  • 0 Votes
    1 Posts
    122 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) is a security release that bears on the send-as and sign-in sections here. An authenticated submission is now DKIM-signed as another domain hosted here only when its account may send as the address its From names (its own address, an alias pointing at it, a mailbox that granted it the post right, an address it proved, or a masked address of its own, the addresses the send-as table below lists); otherwise the mail is still accepted and sent, unsigned as that domain, so give such an account the alias or the grant before upgrading. A domain's or an account's rule replies only from an address its owner may send as. SMTP, POP3 and ManageSieve refuse a SASL PLAIN sign-in that asks to act as another account; with SRS on, mail to an SRS address this server handed out is held to the relay rules unless it is a bounce; the IMAP and POP3 listeners can take the client's address from a PROXY protocol header as the SMTP listener does, each from a list of peers of its own, both off by default; and the Control Panel's IP ranges page, which could show no list of ranges at all, is fixed. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026) is a security release that bears on the auto-ban, the lockout and the IP ranges here. The auto-ban saves the IP range it adds under a name of its own, the user name cut so that the name and its suffix fit in the 100 characters a range's name holds, so that it bans the address: up to 6.3.7 a second or later ban for a long user name could be refused (HM6099) and the address given a whole new set of attempts, and now an address's counted failures are cleared only once its range is saved. A sign-in's name is matched to its account as the per-name lockout counts it, so a mail client that signs in with another spelling of the account's address than the stored one, in more than the case of the letters A to Z, or with white space at either end, may no longer sign in, and each such try is counted as a failure: correct the name in the client. With the web services listener on, an address whose IP range allows none of SMTP, POP3 and IMAP, an auto-ban's or one of your own, is now refused there too, before the TLS handshake, loopback and the addresses in WebServicesTrustedProxies apart; such a range of your own closes to its addresses ACME's http-01 validation when this listener answers it, MTA-STS, CalDAV, CardDAV, Autodiscover, autoconfiguration and the Web Key Directory, so before upgrading check that the certificate authority, the sending MTAs and these services' clients do not come from one. See the 6.3.8 announcement. This topic covers who may use this server and how hard it is to abuse it. Know this first: only the IP ranges and auto-ban work out of the box; every other brake here is off until you set it. Until you do, nothing else stops a botnet spending one guess per address, or a stolen password sending thousands of messages. The secure defaults of a new installation, TLS for sign-in and compromised-account detection are in Hardening a public mail server. Where each one is set Since 6.3.4 the database is the settings store, and for a key it holds, a [Settings] line edited in hMailServer.ini changes nothing; on 6.3.3 these keys were lines of the file, applied at the next start. The store, and when a save takes effect, are in the settings topic. What Kept in Set with Takes effect IP ranges the database Control Panel Access & abuse protection → IP ranges; the Control Deck's IP ranges view; /api/v1/ipranges; hmctl iprange; COM Settings.SecurityRanges at once Auto-ban's four settings the database Control Panel Access & abuse protection → Auto-ban; the Deck's settings; PUT /api/v1/settings; COM at once Lockout, tarpits, rate limit, send-as check, hashing, policy the settings store, under their [Settings] names Control Panel; PUT /api/v1/settings/ini/{name}; hmctl ini set <name> <value>; COM Settings.SetIniSetting; on Linux hmailserver --set-setting NAME=VALUE when saved [SendingLimits], [SendingLimitsOverrides], PasswordPepper hMailServer.ini only the file, by hand or through the Control Panel (below) sending limits within seconds; the pepper after a restart The Control Panel and COM are Windows-only. Of these [Settings] keys the Deck edits only SmtpTarpitCount and SmtpTarpitDelaySeconds. A value set with hmailserver --set-setting while the service runs waits for the next save, reinitialise or restart. IP ranges: priority and the relay grid When a client connects, every range containing its address is found and the highest priority wins. The shipped ranges are Internet (priority 10, 0.0.0.0–255.255.255.255) and My computer (priority 30, 127.0.0.1); a range added on the Control Panel's page starts at 15, so a narrow range sits in front of Internet without editing it. At every RCPT TO two decisions are made, in order: may this combination be delivered at all (if not, 550 Delivery is not allowed to this address.), and must the client authenticate first (if so, 530 SMTP authentication is required.). "Local" and "remote" describe the addresses, not the client: an address is local when its domain is a hosted active domain or a route says so. Combination REST fields: allowed / auth required Internet My computer Local → local deliver_local_to_local / require_auth_local_to_local allowed, auth required allowed, no auth Local → remote deliver_local_to_remote / require_auth_local_to_remote allowed, auth required allowed, no auth Remote → local deliver_remote_to_local / require_auth_remote_to_local allowed, no auth allowed, no auth Remote → remote (relay) deliver_remote_to_remote / require_auth_remote_to_remote allowed, auth required allowed, auth required Anti-spam, anti-virus spam_protection, virus_protection on on Require TLS for auth require_tls_for_auth on in a new 6.3.4 installation; otherwise as the server had it off Remote → remote is the open-relay box. Both shipped ranges allow it with authentication required, even from 127.0.0.1, so a client that has not signed in gets 530; a range that does not allow it at all gets 550. The Control Panel's Server status page raises a Critical warning for any range that allows it without authentication. Remote → local is inbound mail and must need no authentication. Local → local needs it on Internet so that nobody outside can forge mail from one of your addresses to another. The protection switches reach further than their names. Clearing Enable anti-spam turns off every spam test, greylisting and the recipient tarpit for the range; clearing Enable anti-virus means its mail is never flagged for scanning. Since 6.3.4 SPF gives loopback no pass, so a content filter that re-injects mail should authenticate or use a range with anti-spam off. Require TLS for auth belongs to the range, not the port. With it set, AUTH is not advertised on a cleartext connection from that range, on SMTP, POP3, IMAP and ManageSieve. A device that cannot sign in gets a range of its own above Internet. For a scanner at 192.0.2.50 that sends from your domains to your own mailboxes only: hmctl iprange create --set name=scanner --set lower=192.0.2.50 --set upper=192.0.2.50 \ --set priority=20 --set allow_smtp=true --set allow_imap=false --set allow_pop3=false \ --set deliver_local_to_local=true --set require_auth_local_to_local=false \ --set deliver_local_to_remote=false --set deliver_remote_to_local=false \ --set deliver_remote_to_remote=false --set spam_protection=true --set virus_protection=true hmctl iprange update <id> and PUT /api/v1/ipranges/{id} (since 6.3.3) change any subset of the fields. The routes refuse a domain-restricted API key; a role needs manage_settings. The Control Panel's Restore defaults (SecurityRanges.SetDefault()) discards every range, your own and the auto-ban ones, and writes back the two shipped ones as the create scripts make them, without Require TLS for auth. Auto-ban, and why its ranges sit at priority 100 Auto-ban counts failed logons per connecting address; SMTP AUTH, POP3, IMAP, ManageSieve, the REST API and the web services listener's CardDAV, CalDAV and Autodiscover sign-ins feed one counter, but since 6.3.8 neither REST, for a failure from loopback (all of 127.0.0.0/8, and ::1), nor the web services listener, for one from loopback or an address in WebServicesTrustedProxies, counts it towards a ban of the address: an account's failed sign-in from there is counted against the name alone. Up to 6.3.7 an account's failed sign-in at REST from loopback was counted towards a ban of 127.0.0.1, which could then be refused at the SMTP, POP3, IMAP and ManageSieve listeners; on 6.3.2 to 6.3.7 AutoBanNeverBan with 127.0.0.1, ::1 avoids it. Behind a reverse proxy on the same machine (the Linux package binds REST to 127.0.0.1), the webmail's failed sign-ins are never auto-banned; account lockout, below, is what limits them there, and it is off until you set it. At the limit auto-ban clears that address's counted failures, creates an expiring range named Auto-ban: <user> at priority 100, and disconnects; SMTP says 535 Authentication failed. Too many invalid logon attempts. Priority 100, above both shipped ranges, is what makes a ban effective; AutoBanNeverBan, below, exempts an address. Setting (COM name) Default Trap AutoBanOnLogonFailure on — MaxInvalidLogonAttempts 3 0 disables auto-ban entirely, whatever the switch says MaxInvalidLogonAttemptsWithin 30 minutes Not a sliding window: how long a failure record survives, deleted by a pass every minute that runs only while auto-ban is on AutoBanMinutes 60 0 disconnects the client but creates no range, so nothing is banned Their REST keys include auto_ban_on_logon_failure and max_invalid_logon_attempts. The office profile (since 6.3.4) raises the limit to 5. AutoBanNeverBan (since 6.3.2): addresses and CIDR blocks whose failures are never counted; the per-connection disconnect still applies, and a bad entry is reported once as HM6524. Server status warns "Localhost is currently banned in the IP ranges." when a ban covers 127.0.0.1. Control Panel sign-in over COM is outside auto-ban and lockout, so guessing an administrator's mailbox name cannot lock them out of the tool. Spam traps and the multi-recipient null-sender check ban through the same ranges (Auto-ban: spam trap <address>, Auto-ban: multi-recipient null sender <address> see Allow and block lists and the anti-spam stack. Firewall bans (AutoBanFirewall, AutoBanCommand, fail2ban) are in Reading the logs. Releasing a banned address. Clear logon-failure list (POST /api/v1/settings/logon-failures/clear, COM Settings.ClearLogonFailureList()) clears counted failures only, for every address at once; a ban stays until its range expires. To release one now: Have the person correct the password in their client first: the counted failures were cleared when the ban was made, so a client still retrying the old one reaches the limit again as soon as the range is gone. Find the range: the IP ranges page, the Deck's IP ranges view, hmctl iprange list or GET /api/v1/ipranges. Its name begins Auto-ban: and its priority is 100. Delete it: on the page, hmctl iprange delete <id>, or DELETE /api/v1/ipranges/<id>. With AutoBanFirewall on, the firewall is reconciled on every range deletion, so its rule goes too. Five error-log codes mean the control has quietly stopped working: Code Meaning HM6114 A failed logon could not be recorded, so no ban will trigger while this persists HM6099 The auto-ban range could not be saved: the address has not been blocked HM6113 Spent failures could not be cleared, so the address may be banned again as soon as its ban expires HM6112 Expired IP ranges could not be removed, so an expired ban keeps blocking HM6115 Expired failures could not be cleared, so occasional failures may add up to a ban Account lockout, by name A distributed attack never crosses any one address's threshold. Lockout counts the name being guessed at, across SMTP AUTH, POP3, IMAP, ManageSieve, REST and the webmail's sign-in. It is off until set; the office profile sets 10. AccountLockoutThreshold=10 ; failed logons per username before a lockout. 0 = off AccountLockoutWindowMinutes=30 ; the window they must fall inside AccountLockoutMinutes=30 ; how long the lock lasts It counts names, not accounts, so a nonexistent name locks as a real one would, and reveals nothing. A locked name is refused before the password is checked, with the ordinary invalid-credentials reply; only its timing differs, deliberately. A lockout refusal is not charged to the connecting address, so an owner retrying from an office NAT does not get everyone behind it auto-banned. Passkeys and single sign-on are not counted, being no guesses. To lift one early: POST /api/v1/accounts/{address}/unlock, which a help desk scoped to the account's domain may call, COM Application.UnlockAccount, or Unlock on the Deck's Accounts row. A password reset clears it too. Tarpits and the submission rate limit All 0, off, until set; these values are a starting point. LogonTarpitSeconds=2 ; per failure on this connection, capped at 30 s. 0 = off SmtpTarpitCount=10 ; recipients an unauthenticated session may name before... SmtpTarpitDelaySeconds=5 ; ...each further RCPT TO waits this long, capped at 30 s MaxSubmissionsPerIPPerMinute=30 ; MAIL FROM commands per source IP per minute. 0 = off The logon tarpit (Auto-ban page) makes a failed logon's refusal wait LogonTarpitSeconds times the failures so far on that connection, on SMTP, POP3 and IMAP. A correct password never waits. The recipient tarpit (Anti-spam settings page; COM AntiSpam.TarpitCount and AntiSpam.TarpitDelay; REST tarpit_count and tarpit_delay on /api/v1/settings/antispam) spares authenticated sessions and ranges with anti-spam off. MaxSubmissionsPerIPPerMinute counts every MAIL FROM, authenticated or not, and refuses the excess with 421 Too many messages from your IP address, which contains a compromised account. The office profile sets 60; gateway keeps 0, since all inbound mail comes from the gateway's few addresses. Since 6.3.4 a save restarts its one-minute window. Behind a load balancer, give the SMTP listener SMTPProxyProtocolEnabled with SMTPProxyProtocolTrustedIPs (or XCLIENT), or auto-ban, the rate limit and the ranges all reason about the proxy's address: see Behind a gateway or reverse proxy. Per-account sending limits The brake on a compromised account, counted against the authenticated user name (never MAIL FROM) over a rolling period. The server level stays in the file: [SendingLimits] MaxMessagesPerAccountPerPeriod=500 MaxRecipientsPerAccountPerPeriod=2000 PeriodHours=24 ; clamped to 1-168 StateSaveIntervalSeconds=10 ; clamped to 1-3600 [SendingLimitsOverrides] [email protected]=5000:50000 [email protected]=0:0 Both limits default to 0, no limit; recipients are the stricter measure. An override replaces the global pair for that address, 0:0 exempts it, and an optional third field sets its period in hours; a malformed line is ignored with a log entry and the global limit applies. On Windows edit C:\Program Files\hMailServer\Bin\hMailServer.INI with an elevated editor, or use the Control Panel's Maintenance → Server limits & expert settings on the server itself; on Linux, sudoedit /etc/hmailserver/hMailServer.ini. They are not in the settings store, so no environment variable or setting profile writes them. What the sender sees: 452 4.7.0 Sending limit for this account has been reached. Please try again later. at MAIL FROM, or 452 4.5.3 Recipient sending limit for this account has been reached. Please try again later. at RCPT TO. The session stays open, the refusal spends no budget, and the application log, not the error log, says SendingLimit refused submission. Counters survive a restart in hmailserver_sendinglimits.dat in the data directory. It fails open: an unreadable file, a limiter exception or a full table of tracked accounts (error 5813) lets the message through. Since 6.3.4 there are three levels. A domain and an account may hold sending_max_messages and sending_max_recipients, which tighten the level above and never loosen it; an address's override line is its server level. Set them on the Levels tab of the domain and account dialogs, in the Deck's editors, with PUT /api/v1/accounts/{address}, or hmctl account set <address> sending_max_messages 100 (inherit returns to the level above); they apply at once. Also since 6.3.4 the webmail's send and the send API consume the same quota, answering 429; a scheduled draft is not counted when it goes. Who may send as whom SmtpAuthenticatedSenderCheck (Control Panel Connections & protocols → Protocols, SMTP tab) came in 6.2.25 and is off by default, since an installation may depend on what off allows: any account sending as anyone. The office and gateway profiles set it to 1. Turned on, it applies to every authenticated session, whose MAIL FROM must then be: Kind Given by The account's own address always An alias resolving to it, up to five hops the administrator A mailbox whose owner granted the post (p) right on their INBOX that owner, with any IMAP client's SETACL An address elsewhere proved by a code mailed to it (since 6.3.4) the person, in the webmail's Settings, Other accounts, Send mail as A masked address the account made, while it is on (since 6.3.4) the person, in the webmail's Settings, Masked addresses Anything else is refused 550 5.7.1 before the message is accepted. The webmail's From picker lists exactly these, so what it offers is what the server takes. The null sender is always allowed; unauthenticated sessions are unaffected. The post right counts only while IMAP ACL enforcement is on (enableimapacl, on by default). Before turning the check on, grant p to every delegate who sends as a shared mailbox. Mail sent as a proved outside address is not aligned with that address's domain, so a receiver honouring its DMARC policy may quarantine or refuse it. How passwords are stored In the Control Panel, Access & abuse protection → Authentication, card Password storage. Schemes: 0 none, 1 Blowfish, 2 MD5, 3 SHA-256, 4 PBKDF2-HMAC-SHA256 (the default), 5 Argon2id, 6 DPAPI (stored secrets only), 7 scrypt. PreferredHashAlgorithm=4 ; 3, 4, 5 or 7. Anything else is refused (HM5528) and read as 4 MinimumAcceptedHashAlgorithm=0 ; refuse a logon whose stored hash is weaker. 0 = off PasswordPepper= ; a server-wide secret mixed into Argon2id and scrypt hashes PasswordHashIterations=0 ; PBKDF2. 0 = 210,000. Range 10,000 to 10,000,000 PasswordHashMemoryKB=0 ; Argon2id. 0 = 19,456 KiB. Range 4,096 to 1,048,576 PasswordHashTimeCost=0 ; Argon2id passes. 0 = 2. Range 1 to 20 Hashes upgrade at a successful logon, only upward. Raising the scheme or a work factor re-derives each hash when its owner next signs in; lowering either affects new hashes only. MinimumAcceptedHashAlgorithm set too high is a lockout. The refusal follows a correct comparison and the upgrade needs a successful logon, so an account below the floor never climbs out: its password must be reset. Raise the preference first, let accounts sign in, then set the floor. Argon2id and scrypt are peers, so 5 accepts either. A work factor out of range is reported as HM5561 and read as 0; HM5607 means Argon2id was asked for on a build whose OpenSSL has none, and another scheme is used. Argon2id allocates PasswordHashMemoryKB on a connection thread at every logon. PasswordPepper must be set before accounts exist: changing it invalidates every Argon2id and scrypt password. PBKDF2 is not peppered, so it stays usable for SCRAM. Since 6.3.4 it lives in hMailServer.ini only and no backup carries it, so keep a copy of the value with the backups: a database or archive restored onto a machine whose file has no pepper, or a different one, refuses every Argon2id and scrypt password, and a warm standby needs the same value in its own file. Where the service cannot write the file (a new Windows installation running as NT SERVICE\hMailServer, or a Linux package installation), COM and REST refuse it: set it with an elevated editor (sudoedit on Linux) and restart the service. The password policy All off until set, applied where a password is chosen, never at logon; on Windows under Access & abuse protection → Administrative access, Password tab. PasswordPolicyMinimumLength=12 PasswordPolicyRequireMixedCase=1 PasswordPolicyRequireDigit=1 PasswordPolicyRequireNonAlphanumeric=1 PasswordPolicyRejectCommon=1 ; refuse passwords from a built-in common list PasswordPolicyHistoryCount=5 ; refuse the last N passwords PasswordPolicyMaximumAgeDays=0 ; 0 = passwords never expire The office profile sets a minimum of 10 and the common list. Expiry needs care: IMAP, POP3 and SMTP cannot change a password and /portal refuses an expired one, so an administrator renews it, or its owner through an app password, which still signs in to /portal. Active Directory accounts are exempt. The breach check is in Hardening a public mail server; the rest of sign-in is in Signing in. What it does not do None of it spam-tests your own users: an authenticated session never is. A setting profile never writes an IP range. Where to read more Wiki: Security Hardening sections 17.1, 17.2, 17.5, 17.7 and 17.9; Settings Reference sections 9.1, 9.9 and 10; Stopping Spam section 10.1; Troubleshooting sections 19.3 and 19.7. Documents: SettingsApply.md, SettingLevels.md, SendAs.md, Profiles.md, Roles.md, ServiceAccount.md. Public documentation: SecurityRange and SecurityRanges, for the COM names of the range flags. On this forum: Being trusted by the servers you send to, The Control Deck, Upgrading to 6.3.4, hMailServer on Linux.
  • 0 Votes
    1 Posts
    118 Views
    ProgressiverobotP
    This topic covers how much mail an hMailServer 6.3.4 server keeps, and for how long: each mailbox's limit and the quota policy 6.3.4 adds around it; retention, the administrator's and each person's, with the recover-deleted window; and the server archive under ArchiveDir. The one thing to know first: out of the box nothing is deleted because of its age. A new account has no limit (0), no domain has a retention policy, the archive is off, and once it is on, ArchiveRetentionDays is 0, keep for ever. The limit, and what a full mailbox does Where it is set. Account.MaxSize over COM, max_size_mb over REST, in megabytes; 0, the default, is no limit. Set it on the Control Panel's account dialog (Quota (MB), Windows only), in the Control Deck's account editor, with PUT /api/v1/accounts/{address}, in the max_size_mb column of an accounts CSV file (since 6.3.4), or with Set-HmAccount. A domain's Limits tab caps the whole domain (Maximum domain size (MB), 0 for none; once reached, delivery to its mailboxes fails) and sets the quota new accounts get. What counts is each message's original size: compression does not lower it, and a mailbox encrypted at rest keeps its sizes in the clear. The kept copy of a message the person sent to expire counts against their quota (413, quota_exceeded). Where At the limit SMTP RCPT TO, with RejectFullMailboxAtRcpt on (1, the default) A mailbox already at or over its limit, and past any grace (below), is answered 452 4.2.2 Mailbox is full., a temporary refusal: the sending server keeps the message and retries A message that arrives while there is room and would overflow it Caught at delivery, with a non-delivery report to the envelope sender RejectFullMailboxAtRcpt at 0 Accepted, then bounced to an envelope sender that spam has usually forged. Leave it on IMAP APPEND, COPY, MOVE Refused with [OVERQUOTA], so a client can tell a full mailbox from any other refusal APPEND into a folder shared from another mailbox Since 6.3.4 charged to the folder's owner, as COPY and MOVE were A migration or PST import job Pauses with the account has no room left under its quota and tries again; raise the limit or make room A full disk is a different refusal. Below MinimumFreeDiskSpaceMB (100) free on the store's volume, MAIL FROM gets 452 4.3.1 Insufficient system storage. and IMAP APPEND NO [UNAVAILABLE], for every mailbox. The log warns first, below DiskSpaceWarningThresholdMB (1024). What the person sees. The webmail's Storage page (since 6.3.2, GET /api/v1/me/storage the share used, every folder by size, the twenty largest messages, and a clean-up of Trash and Junk older than 7, 30 or 90 days, or everything. See The webmail portal at /portal. GET /api/v1/me answers quota with limit_mb and used_bytes. IMAP QUOTA: QUOTA=RES-STORAGE is advertised; GETQUOTA and GETQUOTAROOT answer one unnamed root for the whole account with STORAGE in kilobytes, and an empty list for an account with no limit. SETQUOTA is refused: the limit is the administrator's. It is on as seeded; the switch is imap_quota_enabled on PUT /api/v1/settings (IMAPQuotaEnabled over COM). The quota policy (since 6.3.4) The server-wide warning at QuotaWarningPercent (90) has been there since 6.2.22-pre6. Schema 6061 adds a domain's own warning share and words, a grace and a send-stop, each a share of the limit; schema 6088 lets an account hold its own shares too. Point Server Domain (-1 takes the server's) What it does Warning QuotaWarningPercent, 90; 0 warns nobody quota_warning_percent The delivery that takes the mailbox across this share sends its owner one notice, from the domain's postmaster Grace quota_grace_percent, 0 quota_grace_percent How far past the limit mail from other people is still accepted Send-stop quota_send_stop_percent, 0 (never) quota_send_stop_percent The share at which the account can no longer send Over COM (Windows): Settings.QuotaGracePercent, Settings.QuotaSendStopPercent, and Domain properties of the same names. The warning is sent once, by the delivery that crosses the threshold; nothing records who was warned, so a mailbox emptied and filled again is warned again. The grace is for mail other people send, the mail otherwise lost. A 1 GB mailbox with a 50% grace receives until it holds 1.5 GB; the owner's own saves and copies over IMAP and the webmail stop at the limit. The send-stop refuses the account, not the address. An authenticated MAIL FROM is refused with 552 5.2.2 and the reason, keyed on the account that signed in, even under a domain alias, not the address it named. Receiving goes on. QuotaPolicy.md describes the refusal at SMTP only; test the webmail's send and the send API before relying on it there. The words are the QUOTA_WARNING_SUBJECT and QUOTA_WARNING server messages, with %MACRO_MAILBOX%, %MACRO_QUOTA% (MB), %MACRO_USED% (MB) and %MACRO_PERCENT%. Server messages are server-wide (the Control Panel's Mail flow & delivery → Server messages, the Deck's Server messages page, PUT /api/v1/settings/messages/{name}). A domain sets its own subject and text, quota_warning_subject and quota_warning_text (Domain.QuotaWarningSubject, Domain.QuotaWarningText), so its people can be warned in their own language. They are chosen apart: a domain that sets only the text keeps the server's subject. Levels. The warning is a preference: the deepest level holding a value wins. The grace and the send-stop are limits: an account's own value may tighten its domain's, never loosen it, while a domain's may sit either side of the server's. The domain's shares are the domain administrator's, and a domain-restricted key's. <name>_effective and <name>_source on the domain and account routes say why a mailbox was not warned. An account's own quota shares are tested only through what the routes answer, and only on Windows. The mechanism is in Settings: what is not stock, when a save takes effect, and the three levels. Where it is set. Control Deck: the domain editor's Quota policy group; the server's grace and send-stop on the Settings page. Control Panel (Windows): the domain dialog's Limits tab; the server's grace and send-stop under SMTP, beside QuotaWarningPercent. REST: the five domain keys on PUT /api/v1/domains/{name}; the server's two on PUT /api/v1/settings. An account's own value: the Levels tab of the Control Panel's account dialog, the Deck account editor's levels card, hmctl account set, or PUT /api/v1/accounts/{address}. QuotaWarningPercent is a key of the settings store: set it in the Control Panel (Windows) on Connections & protocols → Protocols, SMTP tab, with PUT /api/v1/settings/ini/{name} or hmctl ini set, which answer to the administrator password alone (never an API key, nor an account holding a role), or, on the Linux server itself, hmailserver --set-setting, not by editing hMailServer.ini. Every value here applies when it is saved through the server, at every level; when a save takes effect explains why a line edited in the file does not hold and when a value stored by --set-setting arrives. A backup taken before the policy restores with the server's values. One domain warned at 85%, stopped from sending at the limit and given a 20% grace, with a German subject; then one account put back on its domain's grace: hmctl domain set example.com quota_warning_percent 85 hmctl domain set example.com quota_send_stop_percent 100 hmctl domain set example.com quota_grace_percent 20 hmctl domain update example.com --set "quota_warning_subject=Ihr Postfach ist fast voll" hmctl account set [email protected] quota_grace_percent inherit Who is nearest their limit GET /api/v1/reports/quota lists every mailbox with a limit and at least one message, nearest first: megabytes, quota_megabytes, percent, the warns_at and sending_stops_at points that apply to it, and a state of ok, warned, stopped or over. It takes domain, top (1 to 200, default 10) and format=csv, and ignores the date window. Its test has run on the Windows bench only. The Deck's Reports page shows it as Near their quota. Other than the administrator password, a credential needs view_configuration and view_message_trace. The sections hmctl report documents do not include it: curl -u Administrator:your-admin-password \ "http://127.0.0.1:8443/api/v1/reports/quota?domain=example.com&top=200&format=csv" Nothing records what a mailbox held yesterday; per-domain storage by day is the storage section, in Alerts, the audit trail, syslog and the journal, and per-domain reports. Retention: the administrator's policy MessageRetentionDays (since 6.2.25) is set per domain and per account; there is no server value. On a domain, 0 (the default) is no policy and a number deletes mail older than that many days. On an account, 0 (the default) takes the domain's, a number is its own, and -1 keeps for ever whatever the domain says. The sweep. MailboxRetentionTask runs at start-up and every six hours. It removes delivered messages whose stored creation time is older than the window, in every folder of the mailbox, through the same deletion an IMAP EXPUNGE performs, so the quota, the QRESYNC record and an open IMAP session all see it. Utilities.RunMessageRetention (COM, Windows only) runs it now and returns the count; no document names a REST route or hmctl verb for it, so on Linux wait for the next pass. Where it is set. On Windows the Control Panel's domain dialog (Limits tab) and account dialog (General tab); the Deck's domain and account editors; message_retention_days on the domain and account routes; hmctl domain set and hmctl account set. A domain's retention is the server administrator's (COM refuses a domain administrator, the domain route a restricted key); an account's own value is the domain administrator's, so an account's -1 can switch its domain's policy off for that account. Before switching a domain's policy on: Give -1 to each account that must keep everything. Decide whether what the sweep removes should be recoverable for a while (recover_deleted_days, below). Off, a removed message is gone. Set the domain's days. It applies when saved; the next pass, at most six hours away, deletes. Easy to miss. A restore of the built-in backup does not carry a domain's retention days: the wiki's Backup and Restore page lists them as silently reset to 0, so re-enter them. And a legal hold outranks retention: a held account is left alone by the policy and by the person's own choices, and the log says so. Each person's own retention (since 6.3.4) Three choices on the webmail's preferences page: Empty Trash after, Empty Junk after and Archive Inbox mail after, each never, 7, 30 or 90 days, or a year, kept as retention_trash_days, retention_junk_days and retention_archive_days. The same six-hourly sweep runs them after the administrator's policy. Trash and Junk (the folders designated \Trash and \Junk, or named as clients name them) are emptied of what is older; Inbox mail older than the choice is copied to the \Archive folder, made as a top-level Archive if there is none, then removed from the Inbox. That is a folder of the person's mailbox, not the server archive. A value that is not a whole number of days from 1 to 3650 is off. The recover-deleted window (since 6.3.4) recover_deleted_days on PUT /api/v1/settings or the Deck's Settings view (0 to 365; 0, off, by default) keeps a copy of what a person deletes, and of what either retention removes, for the person to put back from the webmail's Recover deleted items. A domain or an account may keep copies for fewer days than the level above, never more. The copies, under <data directory>/Recoverable/<account id>/, count against no quota and are not in the backup, so size the window with the disk in mind; the six-hourly task ends those past the window, and all of them at 0. The person's side is in The webmail in depth: search, offline mail, expiring messages, send-as and recovery. The server archive With ArchiveDir set, the server writes a copy of every message received by SMTP into a tree of its own, as received and in the clear: encryption at rest does not cover it, and the mailbox retention policy does not reach it. The layout, as the Settings Reference writes it (Windows separators, names lower-cased): Message Where the copy goes From a local sender ArchiveDir\<senderdomain>\<sendername>\Sent-<file> From anybody else ArchiveDir\Inbound\<file> For each local recipient ArchiveDir\<recipientdomain>\<recipientname>\<file>, made from the first copy when there is one A bounce or report with a null sender The archive's Error folder, with no index row The archive's settings: Key Default What it does ArchiveDir empty (off) The folder the copies go to ArchiveDomains empty (every message) Comma-separated: a message is archived only when its local sender or a recipient is in a listed domain, and only the listed domains' copies are made ArchiveHardLinks 0 Hard-link instead of copy: a promise that nothing outside the server edits a message file in place ArchiveRetentionDays 0 (keep for ever) Deletes archived copies older than this many days Its sweep runs at start-up and every twelve hours. It removes only .eml files, follows no symbolic link or junction, is depth-capped, and asks the index first: a held copy stays however old. The index (since 6.2.25, schema 6029): a row per copy in hm_archiveindex, with who, when, what, where and whether it is held; copies made before schema 6029 have none. Search it in the Deck's Archive view (since 6.3.4), with GET /api/v1/archive (domain, mailbox, sender, recipient, subject, since, until, hold=1, limit 1 to 1000, default 200, newest first), or with Utilities.SearchArchive over COM (Windows). A role needs view_archive; a key restricted to named domains must name one of its own, and the Inbound copies are the administrator's alone. Searching the words of archived mail is an eDiscovery case's work. A hold on one copy, POST /api/v1/archive/{id}/hold or Utilities.SetArchiveHold, keeps its file and row from the sweep and from an address's erasure; DELETE lifts it. Holds on whole accounts and domains are eDiscovery's: see Compliance: eDiscovery, legal holds, personal data, erasure and the audit trail. curl -u Administrator:your-admin-password \ "http://127.0.0.1:8443/api/v1/archive?domain=example.com&[email protected]&since=2026-09-01&limit=50" curl -u Administrator:your-admin-password -X POST http://127.0.0.1:8443/api/v1/archive/4471/hold Where it is set. On Windows, the Control Panel's Maintenance → Advanced, Copies of mail tab (ArchiveDir, ArchiveHardLinks, ArchiveDomains), and ArchiveRetentionDays on the SMTP tab of Connections & protocols → Protocols. The Deck has no page for these keys: use PUT /api/v1/settings/ini/{name} or hmctl ini set <name> <value> (the administrator password only), which take effect when saved, or hmailserver --set-setting NAME=VALUE (with sudo) on the Linux server itself, which, while the service runs, waits for the next save, a reinitialise (POST /api/v1/server/reinitialize) or a restart. Setting one up: Choose a folder outside the data directory: a full restore with the messages option replaces that directory, and an archive inside it too, held copies included. On Windows, a new installation runs the service as NT SERVICE\hMailServer, which writes only where it is granted: approve the folder by running hMailServer.exe /GrantServiceAccess with --approve-modify "<folder>" from an elevated Command Prompt in the installation's Bin folder. It must be a full path on a fixed local drive, outside the program folder, Program Files, the Windows folder and the profiles folder: never a share, a drive's root, or a path through a junction or link. On Linux the service runs as User=hmailserver, and the packaged unit lets it write only under /var/lib/hmailserver, the data directory itself, and /var/log/hmailserver. Every other folder is read-only to it (/home is not even visible), and an ArchiveDir it cannot write stops archiving. Make the folder writable by the hmailserver user, add it to the unit with a drop-in (sudo systemctl edit hmailserver, then under [Service] a ReadWritePaths= line naming the folder) and restart the service. SecureDefaults.md gives this drop-in for a data folder; no document gives it for the archive. Set ArchiveDomains first if only some domains are archived, then ArchiveDir. Send a test message and find its row in the Deck's Archive view. Before relying on it. The backup does not carry it. The backup copies the data directory, and hm_archiveindex is not carried: back up the archive folder, and the database with its own tool for the index. Moving it strands the index. hm_archiveindex.archivepath is matched as an exact string by the sweep, holds and erasure; do not rewrite it in bulk. If the folder moves, move it back or accept that older rows point at the old place. A mailbox move leaves the archive under the old address, and an address's erasure with the archive included leaves the copies in other people's trees and in Inbound until ArchiveRetentionDays ages them out. It is not journaling, which sends a copy elsewhere and keeps nothing here: Journaling and dual delivery. SMTP, as documented. No 6.3.4 document says whether mail the server queues itself, such as the webmail's send, or mail a fetch account collects, is archived. What it does not do No message-count quota: storage is the only quota enforced. No server-wide retention policy, and no documented way to run the retention sweep on demand on Linux. No write-once archive: a hold is a flag an administrator can lift. The archive only grows while ArchiveRetentionDays is 0, and the recover-deleted store is bounded by its window alone: watch the disk they are on. Where to read more Documents at v6.3.4: QuotaPolicy.md, SettingLevels.md, Reports.md, RecoverDeleted.md, Discovery.md, Backup.md, ServiceAccount.md, RelocatingAnInstallation.md, PersonalData.md, Journaling.md, SecureDefaults.md, SettingsApply.md. Wiki: Settings Reference (section 9.13), Your First Domain and Mailbox, Compliance and eDiscovery, Backup and Restore, APIs Reference. The documentation page: Storage, retention and archive, Queue, quarantine and archive. On this forum: Backup and restore, for what a restore resets; Relocating an installation, and moving or renaming a mailbox, for the archive index; the service account on Windows and the Linux unit, for the folders the service may write; The Control Deck.
  • 0 Votes
    1 Posts
    174 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) changes what an upgraded server does without anything being switched on, and some of it looks like a fault. A forward, a redirect or a rule's forward of mail from outside that did not pass DMARC for the forwarding domain's organisation keeps the envelope sender it came with, with no SRS address or rewritten sender; where the copy would still carry an identity of that organisation (an empty envelope sender under this server's host name, or an envelope sender at this server's host name or at a hosted domain in that organisation) it is not sent to an outside address at all: the message stays in the mailbox, and the application log names each recipient. An authenticated submission is signed as another hosted domain only when the account may send as the address its From names, and is otherwise sent unsigned as that domain. 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 a Sieve script may no longer add or delete them: one stored before the upgrade still runs without that action, and the application log names it. RuleLoopLimit 0 stops out-of-office replies. A delivery whose mail servers' DNS lookup failed tells the sender that the DNS query failed (4.4.4) and retries. On Windows the x64 installer's MariaDB Connector/C 3.4.11 has seven authentication plugins where 6.3.6's had nine, so a database account that signs in with MySQL Enterprise's Windows authentication can no longer sign in and the server cannot open its database. An error log that was quiet may now name database failures that were hidden (HM5032), and the Control Panel's API & monitoring page is Monitoring & alerts. Fixed from reports: the Control Panel's IP ranges page that could show no list at all, DANE enforcement deferring mail to a DNSSEC-signed domain whose MX answer came compressed (live.com among them), and classic Outlook's POP3 downloads stopping at the UTF8 it sends after the login. See the 6.3.7 announcement. After the upgrade to hMailServer 6.3.8 (2 November 2026), TlsMode=verify-full on MySQL or MariaDB with no TlsCaFile set is refused with HM7535 on a connection to a database server on this machine, and the database is not reached; on Windows a TlsCaFile or PostgreSQLSslRootCert whose path holds characters outside ASCII may be refused with HM7534, with the same result, and a TLS certificate or key file so named with HM7536, so that the port it is for does not start; and a Linux server that ran under a LANG that is not UTF-8 no longer finds a file or folder named outside ASCII until it is renamed to its UTF-8 name. A client that signs in with another spelling of the account's address than the stored one, in more than the case of the letters A to Z, or with white space at either end, may no longer sign in. With the web services listener on, an address whose IP range allows none of SMTP, POP3 and IMAP - an auto-ban's, or one of your own that allows nothing - is closed there with no answer, and with it CalDAV, CardDAV, Autodiscover, autoconfiguration, MTA-STS and, when this listener answers it, ACME's http-01 validation. See the 6.3.8 announcement. This topic is the wiki's Troubleshooting chapter arranged by the sentence support threads open with: mail from outside does not arrive, a client cannot send, the Control Panel or the Deck will not connect, the service will not start. It also covers the server's replies, the Diagnostics self-test, the message trace and what only looks like a fault. The one thing to know first: on Windows the service tells the Service Control Manager it is running before it has opened its database or a single port, so "the service is running" proves nothing. Today's ERROR_hmailserver_<date>.log does: it almost always holds the reason a start failed. Start here Three pages, in this order, under Monitoring & troubleshooting in the Control Panel (Windows): Diagnostics, the built-in self-test (below). It names most problems outright. Live logs, while you reproduce the problem, with debug messages on if the answer is not obvious, and off afterwards. Delivery queue: is mail stuck, and what do NextTry and Tries say? On Linux the Deck's Diagnostics, Logs and queue views read the same, and three commands start every investigation: systemctl status hmailserver journalctl -u hmailserver -n 100 --no-pager sudo tail -n 100 /var/log/hmailserver/ERROR_hmailserver_$(date +%F).log Which problem is it? Not running, or nothing listening: The service will not start. A sender got an SMTP error: the reply names the cause. A sender timed out, or outbound mail sits in the queue: Diagnosing stalled mail. Inbound with nothing at all: I can't receive. Outbound that never reaches the queue: I can't send. "I can't receive mail from outside" Work down the table in order, cheapest check first. Check How (Windows) A failure means Is the service running? Get-Service hMailServer Start it, then read the error log for why it stopped Is anything listening on 25? Get-NetTCPConnection -LocalPort 25 -State Listen The port is not configured, is bound to one address only, or another program has it: Connections & protocols → TCP/IP ports Is 25 open in Windows Firewall? Get-NetFirewallRule -Enabled True \| Where-Object DisplayName -like "*hMail*" Open it: see the firewall paragraph below Does the machine itself answer on 25? Test-NetConnection 127.0.0.1 -Port 25 Then it is the server, not the network Is 25 forwarded by your router or cloud firewall? Its configuration The far end sees a refusal or a timeout Does your ISP block 25? Ask them Common on consumer lines: a relay host or a business line Is the MX record right? The MX query page, or Resolve-DnsName example.com -Type MX Fix DNS Can the world reach you? From outside: Test-NetConnection mail.example.com -Port 25 It is the path in Is the domain in hMailServer and Active? Accounts & domains → Domains An inactive domain is not accepted for Is the mailbox Active, and full? The account dialog A full mailbox gets a temporary failure, and the sender retries A port bound to one internal address answers only on the LAN. Server status lists the listeners the service actually brought up. The firewall. The installer opens no port, so nothing from outside reaches the server until you open them, on the machine and on the router or cloud firewall in front of it. Which ports, and the rules for Windows and Linux, are in Every port hMailServer listens on or connects to. "I can't send mail" The reply names most sending faults. In an ESMTP session an enhanced status code follows the number: 530 Must issue STARTTLS first. arrives as 530 5.7.0 Must issue STARTTLS first. Reply Likely cause What to do 550 Delivery is not allowed to this address. ("relay denied") The client is not authenticating, or uses 25 instead of 587 Port 587 with authentication; a printer that cannot authenticate gets its own range under Access & abuse protection → IP ranges 530 SMTP authentication is required. The same, said earlier As above 530 Must issue STARTTLS first. The port requires TLS first STARTTLS or SSL/TLS in the client 535 Authentication failed. Wrong password, not the full address as user name, or an inactive account Also check the auto-ban 535 Authentication failed. Too many invalid logon attempts. The auto-ban: 3 failures, 60 minutes, by default Below 550 Too many recipients. Too many recipients in one transaction Below 552 5.3.4 Message size exceeds fixed maximum message size. Size: N KB, Max size: M KB Over the server's maximum message size (20480 KB), or the domain's own Connections & protocols → Protocols, SMTP tab, or the domain dialog 452 4.3.1 Insufficient system storage. Free space on the store's volume below MinimumFreeDiskSpaceMB (100 MB) Free space; senders retry A banned client. The ban is an expiring IP range: Access & abuse protection → IP ranges lists it, and deleting it releases the client (DELETE /api/v1/ipranges/<id>, or the Deck's IP ranges view). 550 Too many recipients. Two wiki pages disagree. Troubleshooting §19.3 ties it to maxsmtprecipientsinbatch (100) and says split the batch or raise the limit; the Architecture page's RCPT TO chart refuses at 50,000 recipients and uses MaxSMTPRecipientsInBatch to batch outbound deliveries. Splitting the message is right either way; raising the setting also changes the outbound batch size. What the server's replies mean A 4xx is temporary and the sender retries; a 5xx is permanent and the sender bounces. Reply Meaning 421 4.7.0 Too many invalid commands. Bye! Disconnected for misbehaving: a scanner, or a broken client 451 4.3.1 Server temporarily overloaded while accepting the message; please retry. The accept pipeline exceeded FinalizationTimeout: Diagnosing stalled mail 451 4.3.2 Unable to verify the recipient at the moment. Please retry later. The database did not answer, so the mail is deferred, not bounced. Check /readyz 451 4.3.2 The server is shutting down and cannot accept the message. A graceful stop with ShutdownDrainSeconds set 452 4.2.2 Mailbox is full. At or over quota, refused during the conversation; the holder had a QUOTA_WARNING 550 Unknown user No such mailbox, alias or list 553 or 554 from a remote server The far end refused you; its text is the diagnosis, usually SPF, DKIM, PTR or a block list "My mail goes to spam" Almost always DNS, in this order: the PTR record (nothing compensates for a missing or generic one), SPF, DKIM, DMARC even at p=none, then the block lists. With split-horizon DNS, the outside view is the one that counts. Being trusted by the servers you send to covers it. The Control Panel, the Deck or the REST listener will not connect The Control Panel (Windows) talks to the running service over COM: no service, no connection. Use the administration password, not a mailbox's. After a restart it reconnects by itself; after you stop the service it deliberately does not. Remotely it needs port 135 and DCOM. Every page saying "Server unavailable" means the engine is paused: Server status → Resume. Since 6.3.4, under NT SERVICE\hMailServer, a change of the administrator password is refused because the service may not write the file: run hMailServer.exe /SetAdministratorPassword from an elevated prompt, then restart the service. More in The Control Panel on Windows. The Deck and the REST API share one listener, off until RestApiPort is set. It refuses to start without an administrator password, beyond loopback without a certificate, or with a certificate it cannot load (HM5113 names the file), each logged once as RestApi: Refusing to start; the three lines and their fixes are in The REST API: turning it on. Only a RestApi: Listening on line proves it is up. Since 6.3.4 the port waits for a reinitialise (Apply now, or POST /api/v1/server/reinitialize), and a line edited into hMailServer.ini after the first start loses to the stored value (HM5804). With the listener off, on Linux: sudo hmailserver --set-setting RestApiPort=8045 sudo systemctl restart hmailserver On Windows, the Control Panel's API & monitoring page (Monitoring & alerts since 6.3.7). "Web administration page not installed" means the WebAdmin files are missing, or ProgramFolder does not point at the directory holding them. Repeated bad passwords get the address refused for five minutes; loopback is exempt. The Deck itself is The Control Deck. The service will not start The start, in order; where it stops names the problem: Windows is told the service is running, before any of what follows. hMailServer.INI is read, alone, which is how the database is found. A [Database] Type it does not recognise abandons the start, with nothing logged about a database. On Linux, sudo -u hmailserver hmailserver --check-config then prints Database type: 0. HM5005: Server or Database is missing for the configured Type. The database is connected, retried ConnectionAttempts times (6), ConnectionAttemptsDelay seconds apart (5). HM4354: the database server is down or moved, or the credentials changed. On Windows the [Database] password is DPAPI-protected per machine and cannot be copied from another server. Since 6.3.4, HM7530 (an unknown TlsMode) and HM7531 (verify-full with a missing TlsCaFile) stop it too, and since 6.3.8 HM7535 (verify-full on MySQL or MariaDB with no TlsCaFile set, to a database server on this machine: name the CA that issued its certificate in TlsCaFile, or set TlsMode=require) and, on Windows, HM7534 (a TlsCaFile or PostgreSQLSslRootCert name the client cannot be handed as named: give the file and the folders above it ASCII names); --check-database makes one connection and says what it got. The schema version, compared both ways. HM5010: Database version could not be detected. HM5011 too old: DBUpdater.exe from Bin on Windows, sudo -u hmailserver hmailserver --upgrade-database on Linux. HM5011 too new: install the matching or a newer server; never downgrade the database. Since 6.3.4, [Database] UpgradeOnStart=1 makes the service upgrade the schema as it starts: off by default, no backup first, one node at a time. Settings are read again from the database, since 6.3.4 with any [SettingsOverride] keys over them. A configuration that will not load abandons the start. One listener per TCP/IP ports row. A row whose protocol is off on Protocols → Services is skipped in silence, the commonest reason for nothing on 143. HM4316: something else holds the port (IIS on 25 or 443, another mail server, a leftover instance); that port alone stays down. HM5113 with Error initializing SSL. Certificate not set.: that listener does not start. The optional listeners (metrics, REST, web services, ManageSieve), each only with a non-zero port. MetricsServer: Invalid bind address means MetricsServerBindAddress is not an IP literal (localhost is refused too); that listener stays down and the server runs. A failure in steps 2 to 6 stops the whole server, and one with a code writes it to the ERROR log; steps 7 and 8 cost one listener and go to the application log. Windows. A service that stops at once with nothing logged is the account's rights on Bin, Data, Logs and Database. Since 6.3.4 a new installation runs as NT SERVICE\hMailServer (an upgrade keeps its account), and a folder outside the program folder needs approving: run hMailServer.exe --confirm /GrantServiceAccess elevated in Bin, and approve only a folder you recognise, since the service may then write in it. Local SQL Server with integrated authentication needs a login for NT SERVICE\hMailServer. What the account may not do, and moving between it and LocalSystem, are in Installing on Windows. The installer's recovery settings restart a failed service after 1, 2 and 5 minutes (sc.exe qfailure hMailServer). Linux, under systemd. A start that fails before any listener opens puts hMailServer could not start: in the journal, with the reason on standard error in case the log directory is the fault. The unit runs as hmailserver, and ProtectSystem=strict makes anything outside its ReadWritePaths (/var/lib/hmailserver and /var/log/hmailserver) read-only to the service: a data, backup, archive or ACME directory elsewhere needs a drop-in (sudo systemctl edit hmailserver). HM5094: the MySQL or MariaDB client library is not installed. HM5603 on every TLS listener: /usr/bin/dh2048.pem is missing; reinstall the package. The Diagnostics self-test Monitoring & troubleshooting → Diagnostics in the Control Panel; since 6.3.4 also the Deck's Diagnostics view and POST /api/v1/diagnostics/run, on either platform. Ten tests: Test Fails when Server details Never; since 6.3.4 it also says whether the database connection is encrypted Installation paths (6.3.0) A configured path does not exist Test IPv6 Never Test outbound port Outbound 25 is blocked, or the smart host's name, port or TLS mode is wrong Test backup directory No destination, or the service cannot write there Test MX records Public DNS is wrong, or the resolver is not answering Test local connect Hairpin NAT fails, 25 is not forwarded, or the MX points elsewhere Test message file locations Files outside the data directory, or full paths stored, which stops the built-in backup Test IP range configuration 127.0.0.1 has been auto-banned Error logs Any ERROR* file exists Fill both domain boxes first: the MX and local-connect tests, and the outbound test without a smart host, are skipped rather than failed when they are empty. Copy puts the result on the clipboard for a post. "Did we ever get it?": the message trace The message trace records what the server did with each message, keyed on its queue id. It is off by default (MessageTraceEnabled), because it records who corresponds with whom, and nothing is recorded while it is off: turn it on before you need it. Turning it on. Since 6.3.4 it applies when saved: the Control Panel's Message trace card on Monitoring & troubleshooting → Logging, PUT /api/v1/settings/ini/{name}, or hmctl, both with the administrator password: hmctl ini set MessageTraceEnabled 1 On 6.3.3 and earlier it is MessageTraceEnabled=1 under [Settings] and a restart. Rows are kept MessageTraceRetentionDays (30): since 6.3.4 an hourly task deletes older ones; on 6.3.3 and earlier only Remove expired on the Message trace page, or MessageTrace.DeleteExpired over COM, did. Reading it. The Control Panel's Message trace page searches an address as sender or recipient, newest first, and Follow this message shows one queue id. Over REST since 6.3.4: GET /api/v1/message-trace, /api/v1/message-trace/address/{address} and /api/v1/message-trace/queue/{id}, which need the view_message_trace permission. hmctl api GET /api/v1/message-trace/address/[email protected] What a row says. One row per recipient per delivery attempt: when, the event, sender, recipient, source address and status code. Events include delivered and failed; on 6.3.4 also deferred (an attempt that will be retried) and suppressed (stopped by the suppression list). A refusal at RCPT carries queue id 0. A forward, a list or a rule that re-sends makes a new queue entry. Answering the question. A delivered row: you had it; look at the account's folders and rules. A row with queue id 0: refused at RCPT, and the status says why. No row: either it never reached you, or it is still queued or held, came while the trace was off, or is past the retention. The trace records delivery attempts only, with no acceptance, spam or virus event, so an empty trace does not prove the server never had the message. The per-domain reports' traffic, failures, senders and recipients are counted from it, and say so while it is off: Alerts, the audit trail and per-domain reports. Things that look like faults and are not What you see Why A new sender's first message is minutes late Greylisting doing its job (6.3.4) A value typed into [Settings] is back after a restart, HM5804 in the ERROR log The database is the settings store: set it in the Control Panel, over REST (PUT /api/v1/settings/ini/{name}) or with hmctl ini set (6.3.4) HM5807 at every start A [SettingsOverride] section is forcing keys; remove it once the stored value is right (6.3.4, Linux package) HM5803 at every start The service cannot write its copy into the root-owned file; the stored value is in effect 6.2.28 to 6.3.3: "The update feed answered HTTP 404." Set UpdateFeedUrl=https://updates.progressiverobot.com/releases.json and restart "Release 6.3.4 carries no Sigstore bundle for its installer, so it cannot be verified." Install 6.3.4 by hand: Upgrading to 6.3.4 Where a message waits, since 6.3.4. Neither delivered nor queued may mean held on purpose: by the sandbox, by compromised-account detection, in a back-off window, under an IP warm-up ceiling or behind a drain; Diagnosing stalled mail says where to look. And 550 5.7.1 Too many delivery attempts before the greylisting delay had passed is greylisting's abuse lock, lifted after GreyListingAbuseLockMinutes (60); a 5.7.10 bounce to a partner domain is gateway encryption refusing to send in the clear, the partner's key having expired. What it does not do It does not diagnose a mail client (Connecting a mail client), read logs line by line (Reading the logs), or work a stalled queue. The Control Panel and COM are Windows-only. When nothing here names the fault, Read this before you post says what a request needs. Without a GitLab account, email the Service Desk, contact-project+hmailserver-hmailserver-86730559-issue-@incoming.gitlab.com, which GitLab files as a confidential issue. Where to read more Wiki: Troubleshooting, Ports Reference, Monitoring and Health, Installing hMailServer, Installing on Linux, The Control Panel, The REST API, Upgrading hMailServer, Architecture. Documents: ServiceAccount.md, DatabaseTls.md, Reports.md, SettingsApply.md. On the forum: Settings: when a save takes effect (HM5804 and the reinitialise), IP ranges, auto-ban and lockout, Choosing a database and encrypting the link (TlsMode, HM7530 and HM7531) and The hmailserver command line on Linux (the unit and its drop-ins). Public documentation: Troubleshooting and Troubleshooting a Linux installation.
  • 0 Votes
    1 Posts
    136 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) changes some of what a client gets. IMAP COPY and MOVE by message number read their set in the client's numbering, and a set that names a message another session has just expunged is refused NO [EXPUNGEISSUED] for the client to send again; a COPY or MOVE that fails part way leaves the destination as it was; SEARCH, SORT and THREAD ask a criterion named again beside itself once; and FETCH BODY[N.MIME], and BODY[N] of a multipart, are the stored octets. POP3 accepts the UTF8 classic Outlook for Windows (2016 to 2024) sends after the login, until the session's first STAT, LIST, UIDL, RETR, TOP, DELE or RSET, so its downloads work again. SMTP, POP3 and ManageSieve refuse a SASL PLAIN sign-in that asks to act as another account. The IMAP and POP3 listeners can take the client's address from a PROXY protocol header, as the SMTP listener does, each from the peers on a list of its own, both off by default. The send-as grant now bears on DKIM too: an authenticated submission is signed as another hosted domain only when its account may send as the address its From names - its own, an alias pointing at it, a mailbox that granted it the post right, an address it proved, or a masked address of its own - and is otherwise accepted and sent unsigned as that domain. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026) signs a name in to an account only when it is the account's stored address, compared in lower case after a domain alias and the default domain, on IMAP, POP3 and SMTP alike, SCRAM-SHA-256 among them: a client that signs in with another spelling of the address, in more than the case of the letters A to Z, or with white space at either end, may no longer sign in, since such a name fails as a name that does not exist fails and is counted as a failure. Correct the name in the client. See the 6.3.8 announcement. This topic is the server side of IMAP, POP3 and SMTP in hMailServer 6.3.4: what each listener advertises and which switch gates it, search limits and the full-text index, QRESYNC's expunge history, connection limits, shared mailboxes, and what 6.3.4 fixed that a client may notice. Know this first: a capability missing from an answer is nearly always a switch or the session's state, not a fault. Two IMAP switches ship off, and since 6.3.4 IMAP offers STARTTLS only before TLS and sign-in, and COMPRESS=DEFLATE only after sign-in. What a mail client needs is in Connecting a mail client; the ACL rights table is in Aliases, distribution lists, mailing lists and public folders. Seeing what the server advertises Ask the server, from any machine with OpenSSL. For SMTP type EHLO test.example.org; for IMAP, CAPABILITY: openssl s_client -connect mail.yourcompany.com:25 -starttls smtp -quiet openssl s_client -connect mail.yourcompany.com:993 -quiet On 6.3.4 ask IMAP before LOGIN and after: the answers differ by design. For POP3, connect to 995 the same way and send CAPA. Ports 993 and 995 exist only once you have created them (Connections & protocols → TCP/IP ports in the Windows Control Panel, or the Control Deck). IMAP: what is always there On every connection, as in 6.3.3: IMAP4 IMAP4rev1 IMAP4rev2 CHILDREN THREAD=ORDEREDSUBJECT THREAD=REFERENCES NAMESPACE RIGHTS=texk MOVE ID SPECIAL-USE CREATE-SPECIAL-USE UNSELECT UIDPLUS ENABLE STATUS=SIZE ESEARCH CONDSTORE QRESYNC LIST-EXTENDED SEARCHRES WITHIN UTF8=ACCEPT LIST-STATUS PREVIEW MULTIAPPEND REPLACE SAVEDATE METADATA OBJECTID BINARY LITERAL- APPENDLIMIT APPENDLIMIT is bare before authentication and APPENDLIMIT=<bytes> after. LITERAL- caps non-synchronising literals at 4096 bytes, so an oversized APPEND can still be refused with TOOBIG. A client opts in to IMAP4rev2 per session with ENABLE IMAP4rev2, which implies UTF8=ACCEPT. ENABLE recognises exactly four names: QRESYNC (implies CONDSTORE), CONDSTORE, UTF8=ACCEPT and IMAP4rev2. After ENABLE IMAP4rev2 the session drops the RECENT response and the [UNSEEN] code on SELECT and EXAMINE, drops RECENT as a STATUS item, and answers a SEARCH with no RETURN options as ESEARCH. IMAP: what is gated, and by what Capability Advertised when Shipped IDLE enableimapidle is on On QUOTA QUOTA=RES-STORAGE enableimapquota is on On SORT enableimapsort is on On ACL enableimapacl is on; it also switches the #Users namespace On AUTH=PLAIN, AUTH=SCRAM-SHA-256 EnableImapSASLPlain is on and authentication is available Off SASL-IR EnableImapSASLInitialResponse is on Off COMPRESS=DEFLATE IMAPCompressionEnabled=1, the client has signed in (since 6.3.4), and the session is not yet compressed On STARTTLS The port is STARTTLS-optional or -required, TLS is not active, and (since 6.3.4) the client has not signed in Per port LOGINDISABLED Authentication is refused on this cleartext connection (STARTTLS-required port, or RequireTLSForAuth on the IP range) Per range The rest depend on the connection: AUTH=SCRAM-SHA-256-PLUS only on TLS (and behind the same SASL switch), AUTH=XOAUTH2 AUTH=OAUTHBEARER with OAuth2Enabled and, unless OAuth2RequireTLS=0, only over TLS, AUTH=EXTERNAL on a verified client certificate that names an address, and UNAUTHENTICATE once signed in. IDLE reports changes in the selected folder (EXISTS, RECENT, EXPUNGE, FLAGS) as they happen. A folder another client has just created is seen at the next folder-list refresh, as there is no IMAP NOTIFY; that is IMAP, not a fault. QUOTA answers with one unnamed root, "", and a STORAGE resource in kilobytes from the account's maximum size; an account with no limit gets an empty list. SETQUOTA is refused, because the quota is the administrator's. Refusals of APPEND, COPY and MOVE for a full mailbox carry OVERQUOTA. SORT: with message indexing off, as it ships, SORT reads each message's header from disk. EnableImapSASLPlain gates SCRAM as well as PLAIN, despite its name. Roadmap.md says it gates the whole AUTHENTICATE command, while the Capabilities wiki gives OAuth2 and EXTERNAL conditions of their own: with it off, test XOAUTH2, OAUTHBEARER and EXTERNAL over IMAP before relying on them. COMPRESS DEFLATE (RFC 4978) compresses both directions of the rest of the session; a client that does not ask is unaffected. A second one is NO [COMPRESSIONACTIVE], STARTTLS under compression is BAD, and IMAPCompressionEnabled=0 withdraws it, the command then answering BAD like an unknown one. POP3 and SMTP: the same question POP3 CAPA always carries UIDL, TOP, RESP-CODES, AUTH-RESP-CODE, IMPLEMENTATION hMailServer, PIPELINING, UTF8 and EXPIRE NEVER. The SASL mechanisms depend on TLS, STLS on the port, and LOGIN-DELAY <n> appears only when Pop3LoginDelaySeconds is above 0 (0 ships). That key advertises and enforces a minimum interval between sign-ins, refusing an early one with -ERR [LOGIN-DELAY] …; on Windows it is the POP3 tab's card How often a client may check for mail. SMTP EHLO always carries SIZE <n> (the maximum message size in KB times 1024; bare SIZE when 0), 8BITMIME, PIPELINING, CHUNKING, BINARYMIME, SMTPUTF8, ENHANCEDSTATUSCODES, DSN and HELP. BINARYMIME is for local delivery: a recipient needing onward relay is refused at RCPT TO with 554 5.6.3. Conditionally: XCLIENT only when SMTPXClientEnabled=1 and the real peer is in SMTPXClientTrustedIPs; an untrusted peer that tries it gets 550. STARTTLS on a cleartext connection to a STARTTLS-optional or -required port. AUTH only when authentication is enabled, withheld on cleartext when the port requires STARTTLS or the range sets RequireTLSForAuth, and never on the ports in DisableAUTHList. Not a keyword, but decided the same way: the HAProxy PROXY protocol is accepted only with SMTPProxyProtocolEnabled=1 and from a peer in SMTPProxyProtocolTrustedIPs, and nothing in EHLO announces it. On SMTP and POP3 the AUTH line and its SASL responses take up to 12,288 octets; every other line keeps 510 (SMTP) or 500 (POP3). What 6.3.4 added to SMTP is refusals, not capabilities. Where the switches are set, and when a change applies What Windows Control Panel Elsewhere Applies IDLE, QUOTA, SORT, ACL, the two SASL switches, the connection limits Connections & protocols → Protocols, IMAP, SMTP and POP3 tabs COM Settings (IMAPIdleEnabled and its neighbours); REST PUT /api/v1/settings (imap_idle_enabled, imap_quota_enabled, imap_sort_enabled, imap_acl_enabled, imap_sasl_plain_enabled, imap_sasl_initial_response_enabled); the Deck's Settings view; hmctl settings set At once Message indexing Maintenance → Performance, Indexing tab COM Settings.MessageIndexing; PUT /api/v1/settings/indexing (enabled); hmctl settings, group indexing At once IMAPCompressionEnabled, IMAPSearchTimeout, IMAPSearchMaxMegabytes, IMAPExpungeRetentionRecords, Pop3LoginDelaySeconds, IndexerFullText and its tuning keys Cards on the IMAP and POP3 tabs; the Indexing tab COM Settings.SetIniSetting; PUT /api/v1/settings/ini/{name} (administrator password only); hmctl ini; on Linux hmailserver --set-setting NAME=VALUE. No Deck page When saved The Control Panel and COM are Windows-only. The last row's keys were the [Settings] section of hMailServer.ini; since 6.3.4 the database holds them, none of them waits for a reinitialise or a restart (docs/SettingsApply.md), and the server does not take a line edited in the file, so set them through an interface. Settings: when a save takes effect covers the store, HM5804, and a value stored by --set-setting while the service runs. The REST lines assume the listener on 127.0.0.1:8443, as the REST API wiki's examples do; RestApiPort ships at 0 (off) and waits for a reinitialise. # Switch IDLE off; in effect when the answer comes back curl -u Administrator:your-admin-password -X PUT -H "Content-Type: application/json" \ -d '{"imap_idle_enabled":false}' http://127.0.0.1:8443/api/v1/settings # A [Settings] key: the administrator password only, never an API key curl -u Administrator:your-admin-password -X PUT -H "Content-Type: application/json" \ -d '{"value":"120"}' http://127.0.0.1:8443/api/v1/settings/ini/IMAPSearchTimeout # On Linux, then a restart so the running server takes it sudo hmailserver --set-setting IMAPSearchTimeout=120 sudo systemctl restart hmailserver Search: two ceilings and the full-text index Without the full-text index, SEARCH BODY and TEXT read every message in the mailbox, attachments whose media type is text included (decoded, bounded at 1 MB). Two ceilings, measured from the moment a command starts and never re-armed by progress, bound SEARCH, SORT and THREAD: IMAPSearchTimeout (60 seconds) and IMAPSearchMaxMegabytes (2048 MB of message content read); 0 turns either off. They are the IMAP tab's card IMAP search limits, and README.md says to raise them for mailboxes of several hundred thousand messages. What a client gets at a ceiling is stated two ways. Roadmap.md records NO with RFC 9051 LIMIT and no untagged result, so a cut-short list cannot pass for a complete one; README.md's comment beside IMAPSearchTimeout says the search returns the matches found so far. Until that is settled, treat a search that hit a ceiling as incomplete, and let nothing act on its result in bulk. The full-text index is off, and needs two switches: IndexerFullText=1 indexes nothing unless message indexing (Settings.MessageIndexing) is on too. It costs a term table of a few kilobytes per message and a backfill pass over every message already delivered, which is why no upgrade turns it on. The index can only prove that a message cannot match; everything else is read and scanned as before, so results are identical on or off. The webmail's search and eDiscovery use the same filter. Key Default What it does IndexerFullTextBatchSize 250 (1 to 100000) Messages one backfill pass reads before the cursor is saved and the thread pauses IndexerFullTextMinTokenLength 3 (3 to 64) The shortest search string the index answers for IndexerFullTextMaxTokensPerMessage 2048 (64 to 1000000) Distinct terms before a message is marked always-scanned instead of indexed The Indexing tab's Index now, and show how far behind it is and Discard the index and rebuild it are POST /api/v1/settings/indexing/index and /clear over REST, and Index() and Clear() over COM, where TotalIndexedCount against TotalMessageCount shows a rebuild's progress. With mailboxes encrypted at rest, the indexed count stays short by their messages for good: those are never indexed. QRESYNC: how much expunge history is kept IMAPExpungeRetentionRecords keeps the newest 5000 expunge records per mailbox (0 keeps all), pruned every twelve hours and once at service start; on Windows it is the card Change history for synchronising clients. The newest is never removed. A client whose last sync is older than what is kept gets a full resync, never a wrong one: every UID in its range that the mailbox does not hold. An installation upgraded from a release older than 6.2.23-alpha1 prunes the table in one pass at its first start. README.md says 0 defers that prune, and an IMAPExpungeRetentionRecords line of 0 added to [Settings] before the upgrade is taken: 6.3.4's first start adopts from the file a key the database has never held, and before 6.2.23-alpha1 the database held this one only if the file had the line. Restart the old service once after adding or changing it (Upgrading hMailServer wiki, 18.3, step 6): CHANGELOG.md's note that a line edited to prepare an upgrade is put back concerns a key the database already holds, and that restart settles it. Set it back to 5000 afterwards through an interface; Upgrading to 6.3.4 has the rest of the order. Connection limits and what guards them MaxSMTPConnections, MaxIMAPConnections and MaxPOP3Connections are on the Protocols page (REST max_smtp_connections, max_imap_connections, max_pop3_connections). None has a limit by default; SMTP and IMAP ship at 0, no cap. The Services tab turns a protocol off altogether (ServiceSMTP, ServiceIMAP, ServicePOP3). Watch hmailserver_sessions{protocol} on /metrics (off until MetricsServerPort is set, which waits for a reinitialise) or sessions in GET /api/v1/status. Around the limits: auto-ban counts failed sign-ins per IP across SMTP, IMAP, POP3, ManageSieve and REST (on: 3 in 30 minutes, a 60-minute ban); LogonTarpitSeconds (off) delays each failed sign-in's refusal; a second POP3 session on one mailbox gets -ERR [IN-USE]; and since 6.3.4 an IMAP LIST or LSUB pattern over 8,192 characters, or more than 32 patterns in one LIST, is answered BAD. The submission rate limit and the tarpits are in IP ranges, auto-ban, lockout and tarpits; refusing a connection by country is in allow and block lists and country blocking. Opening a mailbox to a colleague Shared and delegated mailboxes are IMAP ACL (RFC 4314). The owner opens a folder to another account, and it appears to that account under the Other Users namespace as #[email protected]. No administrator step is needed: any account whose client speaks SETACL can do it, and the audit trail records no row for it. Leave ACL on. With it off, #Users stops existing (NAMESPACE advertises NIL), public folders are open to everyone, and the send-as grant is not honoured. The owner grants rights from an IMAP client with ACL support. An administrator can do it over REST: GET on /api/v1/accounts/{address}/folders gives the folder's id, and /api/v1/accounts/{address}/folders/{id}/permissions lists, adds, changes and removes entries (since 6.3.3; writes need a full key). On Windows, COM has IMAPFolderPermissions.Add. The grantee is an account, a group (/api/v1/groups, since 6.3.4), or Anyone, which is every authenticated account on the server. Grant l, or the folder does not exist for the colleague. r reads, s changes read state, i, t and e file and delete, and p on the INBOX is the send-as grant. Grant on each top-level folder needed. A right is decided at the nearest folder up the owner's tree that has any entries, and entries are not merged: a grant on INBOX reaches its sub-folders that have none of their own, not the Sent folder, and one entry on a sub-folder cuts it off from the parent's. Over SMTP the p grant counts only while SmtpAuthenticatedSenderCheck is on, and it ships off: until then nothing constrains an authenticated MAIL FROM, so any account can send as the mailbox, or as anyone else. Turned on, it applies to every authenticated session on the server; the addresses it then allows are in who may send as whom. In the colleague's client, a pane of subscribed folders shows only the ones the owner subscribed to; the webmail shows them under Shared by <owner>. What follows from sharing: \Seen is stored on the message, not per person: with s, a delegate marks a message read for everyone; with lr alone they leave no trace, and their client cannot track what they have read. Since 6.3.4 an APPEND into a #Users folder is charged to the owner's quota, as COPY and MOVE already were. A delegate reads a folder of a mailbox encrypted at rest only while the owner's key is open; otherwise a FETCH that reads the message answers NO [UNAVAILABLE], and copying out is refused. In the webmail a read-only share refuses a flag or a delete with 403, a message cannot move between one's own mailbox and a shared one, and the owner cannot grant access from the page. Since 6.3.4 a restore puts each folder's access grants back. Every LIST runs one more query while ACL is on. What 6.3.4 fixed that a client may notice Where Dovecot's imaptest found IMAP answering other than the RFCs say (\Seen without .PEEK, APPEND byte for byte, SORT and THREAD order, among others), Connecting a mail client has the list. The rest: What a client did What it gets since 6.3.4 Sent STARTTLS after LOGIN, or inside TLS BAD, and no STARTTLS in CAPABILITY Sent COMPRESS DEFLATE before signing in NO Appended over a compressed connection, data inflating past the 250,000-octet receive buffer The whole message, where it was stored cut short or never finished Read ENVELOPE with a backslash in Date, or BODYSTRUCTURE with a quote or backslash in a part's parameters A structure that parses: the date's backslashes left out; quotes and backslashes escaped, a control character sent as a space Fetched a message the server cannot read NO [UNAVAILABLE] for a closed key; a damaged one (HM7350) left out, the rest fetched and the command ending NO - not OK with an empty ENVELOPE Changed one message's flags from two sessions at once Both changes kept; STORE ... (UNCHANGEDSINCE n) tested in the same step Went on sending a MULTIAPPEND's later literals after a refusal, one over the maximum message size (and 1 MB) The connection closed with BAD [TOOBIG], where it was read whatever its size Fetched a message only an unreachable object store holds IMAP NO [UNAVAILABLE], POP3 -ERR [SYS/TEMP] (object storage is new in 6.3.4, and off) Two heap-corruption aborts are gone as well (two sessions on one message's keywords; LIST beside folder changes). Most of these were in 6.3.3 too. What it does not do No APOP or CRAM-MD5 on POP3, and THREADID is answered NIL. Delegation is not measured in Thunderbird or Outlook, and an Outlook IMAP profile never acts on the Autodiscover answer's delegated mailboxes; the #Users namespace is what works. Where to read more Wiki: Capabilities and Configuration, Settings Reference (9.12), Connecting a Mail Client, Aliases, Lists and Public Folders (13.3), Control Panel Reference (7.5), Security Hardening (17.5, 17.7), Changes Since 6.2.10 (4.3, 4.4). At the 6.3.4 tag: SettingsApply.md, SendAs.md, EncryptionAtRest.md, DeckParity.md, README.md, CHANGELOG.md (6.3.4, IMAP, POP3 and the webmail), Roadmap.md. The public documentation: 34.3 IMAP details worth knowing, 30.3 IMAP folders and permissions. On this forum: Upgrading to 6.3.4, The Control Deck, The REST API: turning it on, Message storage: object storage and encryption at rest, What runs out first: pools, connections, large folders and Monitoring and health. A client that does not get on with these answers is a bug: a GitLab issue or, without a GitLab account, an email to contact-project+hmailserver-hmailserver-86730559-issue-@incoming.gitlab.com, filed as a confidential issue (SUPPORT.md). A vulnerability goes only by SECURITY.md's routes.
  • 0 Votes
    1 Posts
    132 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) adds reasons a message stays where it is. A forward, a redirect or a rule's forward of mail from outside whose From names the forwarding domain's own organisation, and that did not pass DMARC for it when it arrived, keeps the envelope sender it came with, with no SRS address or rewritten sender; where the copy would still carry an identity of that organisation (an empty envelope sender under this server's host name, or an envelope sender at this server's host name or at a hosted domain in that organisation) it is not sent to an outside address at all: the message stays in the mailbox, and the application log names each recipient. RuleLoopLimit 0 now stops out-of-office replies, as it stopped every other generated message. A delivery whose mail servers' DNS lookup failed tells the sender that the DNS query failed (4.4.4) and retries, where a failed lookup was read as a name with no addresses. On MySQL and MariaDB a lost database connection is reconnected and the statement run again, and an error log that was quiet may now name database failures that were hidden (HM5032). See the 6.3.7 announcement. This topic covers the case where mail is not moving and the server looks healthy: no crash, no error, the service running, and a log that seems to stop mid-transaction. The server used to be silent about it; it is no longer, and the answer is usually in one line of the log. The one thing to establish first is which half is stuck: the server accepting a message from a sender, or delivering one it has already accepted. The two fail differently, for different causes, under different settings. Which half is stuck? Accepting. The sending server connects, sends the message, and then waits. From its side there is a timeout after the message body was transmitted; Postfix reports it as timed out while sending end of data. The message never appears in your queue. Delivering. The message was accepted (the sender got a 250), is visible in the delivery queue, and never leaves. Monitoring & troubleshooting → Delivery queue in the Control Panel tells the two apart in one look, and so does hmailserver_delivery_queue_messages on the metrics endpoint once it has a port (see Commands). The Control Panel is Windows-only; on Linux the queue is GET /api/v1/queue, hmctl queue list (since 6.3.4) and the Control Deck's queue page. Turn on debug logging first Control Panel → Logging → tick Debug messages, and make sure logging is enabled; then reproduce the problem once. The stage lines below are written at debug level. The slow ones (ten seconds or more, for a whole stage or for any single spam test) are also written at application level, so a server with only application logging still records the important half. Turn debug logging off again afterwards on a busy server. Reading the logs covers where the logging keys live on each platform. Accepting: the sender times out after sending the message Between the 354 and the 250 the server runs the accept pipeline: spam tests, message modifications, archiving, the OnAcceptMessage script and the database save. All of it runs on a bounded pool of threads, and the reply is sent only when it finishes. The log times each stage: SMTPConnection - accept: start spam-protection. Spam test: SpamTestDNSBlackLists, Score: 0, Time: 12 ms Spam test: SpamTestSpamAssassin, Score: 0, Time: 120000 ms SMTPConnection - accept: done spam-protection in 120047 ms (session 5). SMTPConnection - accept: done message-modifications in 0 ms (session 5). SMTPConnection - accept: start script/save. SMTPConnection - accept: done script/save in 3 ms (session 5). How to read it. Read it as a sequence and look for the last line written; the stage that is stuck is the one after it. Two stages announce themselves with a start line (spam-protection and script/save), so for those a start with no matching done is the whole diagnosis. The middle stage, message modifications, has no start line, so a stall there shows as done spam-protection followed by silence. If every stage completes but one took a long time, the Time: or done ... in value names the culprit. What the log shows Cause SpamTestSpamAssassin with a large Time: spamd is unreachable, overloaded, or accepting connections without answering SpamTestDNSBlackLists, SpamTestSURBL or SpamTestSPF slow The resolver is not answering. Look in the TCP/IP log for DNS - Query timed out and DNS - Query failure. Do not reach for DNSServer as the fix (below) done spam-protection, then silence Message modifications: the spam headers, the signature, the List-* headers, or the write of the modified message back to disk script/save slow An OnAcceptMessage event script, or the database Nothing between 354 and silence A version older than 6.2.17, which added these lines. Upgrade The DNSServer setting. It was broken in 6.2.17 and 6.2.18 (CHANGELOG's 6.2.19 section names 6.2.16 as well) and fixed in 6.2.19: on the affected versions every lookup through a configured custom server failed, and DNSBL, SURBL, SPF, DKIM and MX lookups failed in silence, so such a server had quietly stopped most of its spam filtering while still accepting mail. One caveat survives the fix: a custom server list is only honoured together with DNS_QUERY_BYPASS_CACHE, so every lookup pays full price. Leave DNSServer empty unless you need a specific resolver. SPF, and SPF on Linux. In 6.3.3 and earlier the SPF evaluator resolved names through the Windows DNS client rather than through the server's resolver. On Windows an SPF check therefore went to the system resolvers whatever DNSServer said; on Linux every SPF check returned a temporary error, reported once per process as HM6406, and because SpamTestDMARC evaluates SPF, DMARC there lost its SPF half. The RFC 7208 evaluator that replaced it in 6.3.4 resolves through the same resolver as every other lookup. If you run 6.3.3 or earlier on Linux, an SPF or DMARC verdict from it is not a verdict. The deadline. Since 6.2.17 acceptance is bounded: if the pipeline exceeds FinalizationTimeout (240 seconds by default) the server answers 451 4.3.1 Server temporarily overloaded while accepting the message; please retry. and the sender retries rather than waiting for a reply that never comes. The accompanying error entry names the deadline that was hit, and the stage timings above it name what consumed the time. If every message stalls, not just one Look for these two lines: Task SMTP-accept session=42 ip=203.0.113.9 waited 8 seconds for a thread in work queue Asynchronous task queue. 14 task(s) are still queued. All 15 threads in work queue Asynchronous task queue have been busy for at least 120 seconds, so no further task on this queue can start. 14 task(s) are queued. Running: SMTP-accept session=42 ip=203.0.113.9 (thread 4120, 168s), ... Every worker is occupied, and one slow dependency does this to the whole server. The task names say which sessions are stuck; the stage timings say what they are stuck on. AsyncQueueStallThreshold (120 seconds by default) is how long every worker may be busy before the second report is written; 0 turns the report off. Delivering: how the queue works There is no in-memory queue to inspect. The delivery queue is the set of rows in hm_messages whose type is delivering, and the delivery manager is a loop that polls for them. Three properties of that loop explain most of what a stuck queue looks like: The selection is where messagetype = 1 and messagelocked = 0 and messagenexttrytime <= now, ordered by size, then try count, then id. Small and new goes first. The delivery pool is separate and smaller than the accept pool: maxdelivertythreads, 10 by default, on the work queue named SMTP delivery queue. It is not the Asynchronous task queue that finalises inbound mail, so a saturated delivery pool does not stop mail being accepted; it stops it leaving. The manager wakes on demand, or once a minute otherwise. A message whose next-try time has just passed is picked up within a minute, not instantly. At startup every message in the queue is unlocked (update hm_messages set messagelocked = 0), because locks held by a server that crashed would otherwise strand those messages for ever. That is also why exactly one hMailServer service may run against one database. The retry schedule, and when a message gives up Setting Where Default Effect Number of retries Mail flow & delivery → Delivery of e-mail (smtpnoofretries) 4 How many further attempts after the first Minutes between every retry The same page (smtpminutesbetweenretries) 60 The gap between them QuickRetries [Settings] 0 (off) The first N retries use the short gap below instead, for greylisting QuickRetriesMinutes [Settings] 6 That short gap QueueRandomnessMinutes [Settings] 0 (off) Adds a random 1..N minutes to every gap, so a burst does not retry in lockstep Route number of tries and minutes between tries Mail flow & delivery → Routes — Overrides both, per matching route; where several routes match, the largest of each wins With the shipped defaults that is five attempts an hour apart: a destination that is refusing temporarily holds a message for roughly four hours and then bounces it. The try count in the queue is the same messagecurnooftries the arithmetic uses. Two things do not advance that counter: Deliver now resets the next-try time without touching the count, and a message held because the virus scanner could not examine it is held on its own budget (below). Reading a stuck queue What the queue looks like What it means Where to look next Depth climbing, oldest message seconds old A busy server draining normally. Not a fault — Depth flat, oldest message hours old, try counts climbing The far end is deferring you The SMTP log for that destination; your IP's reputation and PTR Every stuck message shares one recipient domain That destination, not your server The route settings for it, and its MX Try counts stuck at 0, next-try times in the past, nothing moving Delivery threads are not running the tasks hmailserver_workqueue_depth{queue="SMTP delivery queue"}, and the saturation report above Messages appear and vanish without arriving Delivered to the wrong place, or discarded by a rule Mail flow & delivery → Rules; enable MessageTraceEnabled and read the trace Try count 1 and a next-try time of 1901-01-01 An ETRN hold: the one route these recipients match is an ETRN route Mail flow & delivery → Routes; the log line is SMTPDeliverer - Route Message: HOLD for later delivery.. Queue empty, but the sender got a 250 and nobody received it Delivered locally into a folder you are not looking at, or the row was removed The account's folders; the message trace Message N could not be delivered since the data file does not exist The row and the store disagree: a restore that missed files, or a store on a share that vanished Turn on MessageStoreConsistencyCheck and read the report When the virus scanner cannot examine a message. With AVFailAction=1 such a message is not delivered. It is held on its own schedule rather than on the SMTP retry budget: AVFailRetryMinutes apart (15 by default), at most AVFailMaxHolds times (16), and then returned to the sender. The log says where in that budget each hold is (... this was hold 3 of 16 before it is returned to the sender.). Each hold counts as a deferral in hmailserver_messages_deferred_total, so a scanner that has stopped answering shows as a deferral rate with no matching delivery rate. The usual causes of a genuinely stuck delivery A virus scanner that stops responding. ClamAV is contacted after the message is accepted, so a wedged clamd shows up as accepted but never delivered. Each socket operation is bounded by ClamMinTimeout / ClamMaxTimeout, but that is a deadline on one read or write, armed fresh each time, and the message is streamed in chunks, so one large message can take considerably longer than ClamMaxTimeout in total. A remote server that answers extremely slowly. The idle timeout is re-armed on every byte received, so a host that sends one byte occasionally used to hold a delivery thread indefinitely. Since 6.2.18 there is an absolute ceiling, ClientSessionCeiling (30 minutes by default), armed once and never re-armed. A custom virus scanner or external tool that hangs, bounded by ExternalProcessTimeout. A message held for a sandbox's verdict (since 6.3.4). With SandboxEnabled on, a message none of your own signed-in users submitted, whose attachment is of a submitted type, waits for the sandbox you named to open the file, up to SandboxTimeoutMinutes (15 by default). While it waits it is in no mailbox, not in the quarantine and not the queue's to retry: its Retry and Delete answer 409. It is listed by GET /api/v1/sandbox/held, by GET /api/v1/queue marked held: sandbox (the held column of hmctl queue list), and in the Deck's queue page under Held for a sandbox, where it can be delivered without the verdict or discarded. The database. Check for errors mentioning the connection pool. Since 6.3.4 a message can also wait in other places, each on purpose: held for review by compromised-account detection in enforce (/api/v1/review); in a destination's back-off window, with the last error Delivery deferred by this server's own shaping, not by a remote server. (GET /api/v1/queue/shaping; such a deferral does not spend the message's retries); under an IP warm-up ceiling; or behind a drain, when /readyz says draining. Section 19.11a of the Troubleshooting page lists each with what to do. Settings that bound each stage All are in seconds. On 6.3.3 and earlier they are [Settings] keys in hMailServer.ini. Since 6.3.4 they are stored in the database and changed in the Control Panel, over the REST API at /api/v1/settings/ini/<name> or with hmctl, and the save applies them. They still appear in the file's [Settings] section, but the database's value is the one used: where the service can write the file, an edit made there is reported (HM5804) and put back at the next start. Setting Default Bounds 0 means FinalizationTimeout 240 The whole accept pipeline, after which the sender gets a 451 no bound SAMaxTimeout 90 SpamAssassin: idle timeout, and a session ceiling of this plus 30s not "no bound" ClamMaxTimeout 90 ClamAV: idle timeout per socket operation not "no bound" DNSQueryTimeout 10 A single DNS query no bound ScriptTimeout 60 One event script invocation no bound ExternalProcessTimeout 300 An external scanner process no bound ClientSessionCeiling 1800 An entire outbound delivery session no bound AsyncQueueStallThreshold 120 How long every worker may be busy before the saturation report reporting off DBConnectionAcquireTimeout 60 Waiting for a pooled database connection no bound The two exceptions matter, because 0 tightens them instead of removing them. SAMaxTimeout and ClamMaxTimeout are not passed straight through: they go to a calculator that returns the minimum whenever the maximum is lower than it. So SAMaxTimeout=0 gives a 30-second idle timeout (SAMinTimeout) and a 30-second session ceiling, and ClamMaxTimeout=0 gives 15 seconds (ClamMinTimeout). To lengthen either, raise the value; to lengthen it a long way, raise the matching ...MinTimeout too. There is no way to make either unbounded. When the pool deadline expires, a recipient lookup answers 451 4.3.2 rather than 550: the server can tell "the database did not answer" from "no such address", so a database locked by a backup defers the mail instead of bouncing it. Commands The COM examples below are Windows-only. The metrics endpoint and the REST routes work on both platforms. Is it accepting, or is it delivering? The metrics listener is off until MetricsServerPort is set (9100 here; since 6.3.4 the port, the bind address and the listener's other settings, its credentials included, wait for a reinitialise). Once it is on, the endpoint needs no credential on a loopback bind: (Invoke-RestMethod http://127.0.0.1:9100/metrics) -split "`n" | Select-String '^hmailserver_delivery_queue_(messages|oldest_message_age_seconds) |^hmailserver_workqueue_depth|^hmailserver_messages_(delivered|deferred|bounced)_total ' A rising oldest_message_age_seconds with a flat delivered_total is the delivering half. A rising hmailserver_workqueue_depth{queue="Asynchronous task queue"} is the accepting half. The COM examples start from a signed-in $app: $app = New-Object -ComObject hMailServer.Application $app.Authenticate("Administrator", "<password>") | Out-Null Push one message to the front of the queue (resets its next-try time, leaves the try count alone, and releases an ETRN hold): $q = $app.GlobalObjects.DeliveryQueue $q.ResetDeliveryTime(4711) $q.StartDelivery() Wake the delivery manager without touching any message: $app.GlobalObjects.DeliveryQueue.StartDelivery() Remove one message from the queue (it is locked first, so a message a thread already has is not pulled from under it): $app.GlobalObjects.DeliveryQueue.Remove(4711) Follow the log while you reproduce it by tailing the day's file and filtering on accept: |Spam test:|waited .* for a thread|have been busy|SMTPDeliverer; Reading the logs has the tail command for each platform. Turn the message trace on (MessageTraceEnabled, off by default) to see what was done with one message. It has no acceptance, spam or virus event, so an empty trace does not show that the server never had the message; Troubleshooting by symptom says how to turn it on and what a row says. What to include in a report If none of the above identifies it, post in General support or open an issue with: the log from the 354 (or from the delivery attempt) onwards, including the stage timing lines; the ERROR_hmailserver_<date>.log for the same window; which scanners and event scripts are enabled; and whether the message eventually arrives, arrives twice, or never arrives. Say which half you have. Read this before you post covers the rest. Without a GitLab account, email the report to the project's Service Desk address, contact-project+hmailserver-hmailserver-86730559-issue-@incoming.gitlab.com, which GitLab files as a confidential issue; the maintainers answer by email. Where to read more Wiki: Diagnosing Stalled Mail and Troubleshooting, sections 19.7 and 19.11a. Repository: DiagnosingStalledMail.md; SettingsApply.md; Sandbox.md; SUPPORT.md. The public documentation: The delivery queue and the retry schedule and Troubleshooting. On this forum: Being trusted by the servers you send to, for when the far end is deferring you; Outbound shaping and IP warm-up, for when your own server is holding mail back; Monitoring and health, for turning the metrics listener on.
  • 0 Votes
    1 Posts
    69 Views
    ProgressiverobotP
    Every message over 60,000 bytes sent to a server advertising CHUNKING has stalled since outbound BDAT arrived in 6.2.25. Every release from 6.2.25 to 6.3.2 is affected. If your smart host or the destination offers CHUNKING, large mail has been failing, and OutboundChunking=0 is the workaround on any of them. 6.3.3 fixes it (GitHub issue 261). The SMTP client armed the read for the reply as soon as the BDAT command, or with PIPELINING the envelope, had been queued, and the chunk's second 60,000-byte buffer was queued behind that read in the operation queue, which starts only the operation at its head. The read could not complete while the remote waited for the bytes behind it, so nothing sent them, and the remote gave up on its own timeout. iCloud returned 421 after five minutes in the report. DATA never met it, because its body streams only after the 354. The queue now runs once more after a read has started. If you cannot upgrade yet, OutboundChunking=0 in hMailServer.ini is the workaround. What the upgrade involved Two schema changes, both upgrade in place. Schema 6039 widens a domain's relay-password column: it held 255 characters and a DPAPI envelope is 314, so a relay password could not be saved on any Windows installation. Schema 6040 adds the CardDAV tables. On PostgreSQL the escaper doubled backslashes unconditionally, which is correct only while standard_conforming_strings is off, and that has been on by default since PostgreSQL 9.1. DKIM key-file paths, signatures, vacation messages and rules were stored doubled. Values already stored that way stay as they are. Edit and save them once. Also in this release The four [Settings] ini routes over REST answered to any api key. A read-only key could read the OAuth2 HMAC secret, the password pepper and the service account password, and a write key could set AutoBanCommand, which the firewall reconciliation runs as the service. They take the administrator password only now. A domain-scoped key is refused the eleven administrator-only fields on PUT /api/v1/domains/{domain} with 403. CardDAV (RFC 6352) for the account's address book, one book named Contacts, vCard 3.0 and 4.0, HTTP Basic over HTTPS only. The web services HTTPS listener has to be on (WebServicesHttpsPort). The webmail is rebuilt: inbox tabs, mute, pinning as $Pinned, follow-up dates as $FollowUp and $Due-YYYY-MM-DD, fourteen more search operators, twenty languages. The full-text indexer no longer reports an error for a message deleted before its terms were saved. Full release notes, checksums and signatures
  • 0 Votes
    1 Posts
    193 Views
    ProgressiverobotP
    This was an alpha. Everything in it shipped in 6.2.24. Nothing here should be installed today. Thunderbird 128 and later could not save a single Sent copy. Every message went out over SMTP and nothing was ever stored, with only a per-machine client setting as a workaround (GitHub issue 53). Two ways a sender could steer the anti-spam tests are also closed here. What the upgrade involved This is an alpha because one of the fixes changes what a non-conforming client sees. Relayed and fetched mail is now tested against a different address. The Received parser takes the last observed address before by, ignores values marked as HELO-supplied, and keeps a header whose host name is not a valid domain name. If you sit behind an incoming relay and have tuned scores around the old behaviour, expect verdicts to move. MAIL FROM straight after a STARTTLS handshake with no fresh EHLO now gets 503 Bad sequence of commands, as RFC 3207 section 4.2 requires. If you have a home-grown submission script that skips the second EHLO, this is the release that tells you. Changes IMAP APPEND: once a client enables UTF8=ACCEPT, RFC 6855 lets it wrap the message as UTF8 (~{n} plus a closing ). The parser counted parentheses across the whole line and refused with BAD APPEND Command requires at least 2 parameter before reading a byte. The wrapper is now stripped ahead of the parser, including the non-synchronizing ~{n+} form and later messages of a MULTIAPPEND. Anti-spam bypass: the parser took the first bracketed value after from, which is the HELO literal the client chose, and skipped any header whose host name was not a valid domain (my_pc). DNSBL, SPF and the HELO-host test could be skipped entirely. Two assert(0) on sender-reachable input are gone with it. RSET before EHLO no longer opens a transaction on its own. That had let MAIL FROM through with an empty HELO host, skipping the OnHELO/OnEHLO events. A PTR answer carrying the trailing dot (1.0.0.127.in-addr.arpa.) no longer empties the result. That had been failing open in the HELO and reverse-DNS spam tests. hMailServer.exe is built with /guard:cf in both configurations (GitHub issue 45). An unattended install whose database step fails now fails, rather than showing a suppressible message box and exiting 0. Known limitation: the weekly C++ CodeQL analysis has not run since late August. It targets a self-hosted runner that is no longer registered. Full release notes, checksums and signatures
  • 0 Votes
    1 Posts
    51 Views
    ProgressiverobotP
    Inbound mail from a Postfix-based relay could hang forever. The connection logs 354 OK, send., stops dead, and the sender eventually gives up with "timed out while sending end of data", leaving a zero-byte file in the Data folder. Anyone taking mail from Postfix or a Proxmox Mail Gateway was affected, and a same-host relay hits it almost every time. Three previous releases claimed to fix it and did not. This one was settled by running a real Postfix 3.10 against the server and reading the bytes on the wire. What the upgrade involved Two known issues stand. Equal-preference MX records are not randomised (RFC 5321 5.1); the server always tries them in the order the resolver returned, and a comment and COM help string that claimed otherwise have been corrected. One-click unsubscribe (RFC 8058) is not implemented. RFC 2369 List-* headers are still emitted for distribution-list postings. Changes Postfix pipelines the terminating dot and QUIT into one segment, so the terminator sits mid-buffer and every tail-only end-of-data check missed it. The receive path now searches for <CRLF>.<CRLF> anywhere in what arrived and returns the remainder to the command parser. Bare-LF terminators are still recognised only at the very end of the buffer, with anything behind them discarded, which is the CVE-2023-51764 rule. SMTP dot transparency (RFC 5321 4.5.2) now carries line-start context across buffer boundaries in both directions. Previously a line-leading dot landing a byte or two into a chunk went unstuffed, silently truncating messages. Sieve scripts were left behind by domain and account renames and deletes. Recreating an address could reactivate the previous holder's filter, redirects included. Renames now move the Sieve tree, deletes remove it, and the Control Panel writes the script only after the save succeeds. Sieve body test (RFC 5173) implemented and advertised over ManageSieve, with :text, :content and :raw. SORT () US-ASCII ALL spun a connection thread at 100% of a core forever. Empty sort criteria are now rejected per RFC 5256. The 512 MB message-list cache never evicted and only counted upwards. Eviction now measures entries as they are. Malformed DNS answers are retried once over TCP. The Control Panel MX tool (GitHub issue 29) now uses the server's resolver via Utilities.ResolveMXRecords. Control Panel: seven Administrator functions restored, plus Welcome page icon alignment (GitHub issue 30). Everything in the 6.2.20 notes is in this release too. Full release notes, checksums and signatures
  • 0 Votes
    1 Posts
    58 Views
    ProgressiverobotP
    A recipient lookup that failed because the database did not answer returned 550, exactly as a lookup that found nothing did. The two cases were indistinguishable inside the server, so a database briefly locked by a backup told the sending server that a valid mailbox did not exist and the mail was bounced instead of retried. That is mail loss with a delivery receipt. Anyone running against a database that stalls under backup, maintenance or load was affected. Those lookups now return 451. What the upgrade involved DBConnectionAcquireTimeout now defaults to 60 seconds rather than 0. It shipped disabled in 6.2.17 precisely because timing out an acquisition turned a slow database into a bounce. The 451 fix is what made it safe to turn on, but the pool deadline is live after this upgrade, so check the value if you set it explicitly. Bounded waits (GitHub issue 23, #24) Outbound delivery sessions get an absolute ceiling, ClientSessionCeiling, 30 minutes, separate from the idle timeout. The idle timeout re-arms on every byte received, so a peer dribbling one byte at a time held a delivery thread indefinitely. ClamAV on the delivery path (GitHub issue 23) is bounded and reports a timeout rather than holding the thread. DNS queries, event scripts and external scanner processes (GitHub issue 24) are bounded by DNSQueryTimeout (10s), ScriptTimeout (60s) and ExternalProcessTimeout (300s). Work queue saturation is reported on a schedule and names the task holding each thread, with its session and peer IP. Pre-authentication IMAP command buffering is capped at 11 MB. An unauthenticated peer could previously buffer without limit. Backup restore validates the source archive before deleting the target. It deleted first, so a corrupt archive destroyed the data it was restoring over. Other A first static analysis pass fixed a buffer overrun on long paths in GetExecutableName and two MySQL path helpers, and a shadowed fileExists in Logger::WriteLogFile that made rotation test an uninitialised value. A correction to the roadmap: ARC sealing is narrower than previously described. Arc::Seal sits after every early return in DKIMSigner::Sign, so relayed third-party mail is never sealed. Discussion #18 is fixed and bounded, but not yet confirmed against the original reporter's Postfix/PMG setup. If you are affected, the per-stage timings in the troubleshooting guide will name the culprit in one log line. Please post it on the discussion. Full release notes, checksums and signatures
  • 0 Votes
    1 Posts
    51 Views
    ProgressiverobotP
    If your server accepts inbound mail from a trusted relay, a Proxmox/Postfix front end for example, and you run SpamAssassin, messages could stall after end-of-data and never get a 250. Reception was never the problem. The stall was in the accept/save work that runs after the terminating dot and before the reply. What the upgrade involved No database change. Schema version stays at 6005. The same audit found other unbounded waits, not fixed here: synchronous DNS lookups in the spam tests have no application-level timeout, the database connection pool has no acquisition deadline, event scripts have no execution limit, and the ClamAV read/write timeout on delivery is ineffective. Why it only ever hit relayed mail For a trusted incoming relay, hMailServer defers the whole spam battery to after end-of-data. For a direct or authenticated sender those tests run earlier, during MAIL FROM/RCPT TO, on the connection thread. The post-DATA work runs on a bounded pool, 15 threads by default, and holds the thread that sends the 250. SpamAssassin's wait had no overall ceiling: the connection's idle timeout is re-armed on every byte received, so a scanner that stalls or dribbles holds the thread indefinitely. Against a blackholed SpamAssassin endpoint, one message was acknowledged after 120 s. Eighteen concurrent messages produced zero acknowledgements: 15 workers blocked, the rest with no worker at all. The fixes SpamAssassin's wait is bounded. Hard ceiling of SAMaxTimeout + 30 s, after which the message is accepted without a verdict, the same outcome as spamd being down. New FinalizationTimeout, default 240 s, inside Postfix's 600 s data-done timeout, 0 disables it. Acceptance past that answers 451 4.3.1 and the sender retries. The check runs on the accepting thread and only before anything is saved, so it cannot duplicate mail. Acceptance is timed per stage and each spam test timed individually. A slow stage is logged at APPLICATION level, not only under debug: Spam test: SpamTestSpamAssassin, Score: 0, Time: 120031 ms. Upgrading is enough to turn silent stalls into either a completed delivery or a clean 451 retry. Enable debug logging and the per-stage timings name the scanner, DNS lookup or event script responsible. Also in this release: regression coverage for clients that vanish mid-operation (aborted DATA, truncated BDAT, IMAP APPEND literals cut short, POP3 disconnects during RETR), plus an installer smoke test on a clean machine. Full release notes, checksums and signatures