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

Guides & tutorials

10 Topics 10 Posts

Walkthroughs that worked on a real server — give the version, platform and database. The official manual stays at progressiverobot.com/hmailserver-documentation/.

  • 0 Votes
    1 Posts
    178 Views
    ProgressiverobotP
    Every documentation topic on this forum, by the category it sits in. Each one is written from the project's wiki and the operator documents at the release tag, for hMailServer 6.3.4, and links the pages it was written from. The long tail is in the wiki and the operator documents; the public documentation is the same material as one long page. A question that none of these answers belongs in the category it is about, as a new topic; Read this before you post says what a support request needs. Running hMailServer General support Read this before you post: what a support request needs Where to post what: a map of the forum categories Diagnosing stalled mail: a sender that times out, and a message that never leaves Reading the logs: levels, files, the Logs page, following a connection, and fail2ban Backup and restore: what is in the archive, what is not, and restoring one mailbox Settings: what is not stock, when a save takes effect, and the three levels The Control Panel on Windows: every page, what it writes, and what applies when IMAP and POP3 on the server: capabilities, switches, shared mailboxes, search limits Troubleshooting by symptom: no mail in, no mail out, a service that will not start Every port hMailServer listens on or connects to, and the firewall rules to match Installation, migration & upgrades Upgrading to 6.3.4: any 5.3 or later goes straight there Moving to new hardware, and why Windows to Linux is a rebuild Installing on Windows: wizard pages, service account, profile and the first hour Choosing a database, creating it, encrypting the link and leaving the built-in one Your first domain and mailbox, and the DNS records your domain needs Bringing mailboxes in: IMAP mirror, Google, Microsoft 365, mbox, Maildir and PST Unattended installs, winget and Chocolatey, and configuration from the environment Warm standby and high availability: what a second machine can and cannot do Message storage: compression, de-duplication, object storage and encryption at rest The update check and the live update: feed, verification, window and rollback Upgrading from 5.x, 6.2 or 6.3: the 24 changes without a switch, and rolling back Linux hMailServer on Linux: what you get and what you do not First install on Linux: the order of operations, Ubuntu to Enterprise Linux The hmailserver command line on Linux: every verb, who runs it, the files, the unit In a container or on Kubernetes: the image, Compose, Helm and the environment Databases on Linux: PostgreSQL or MariaDB, the client, TLS and the upgrade floor Webmail, portal & Control Deck The webmail portal at /portal The Control Deck: browser administration at / Calendars, contacts, the directory and shared address books over CalDAV and CardDAV Signing in: passwords, second factor, passkeys, single sign-on, recovery, devices The webmail in depth: search, offline mail, expiring messages, send-as and recovery Connecting a mail client: IMAP, POP3, SMTP submission, app passwords and the ports TLS, certificates & deliverability Let's Encrypt certificates are automatic: the built-in ACME v2 client Being trusted by the servers you send to TLS on the listeners: your own certificate, versions, ciphers and client certificates Requiring TLS: MTA-STS and TLS-RPT, DANE with ACME, and remote domain policies DKIM rotation, DMARC and TLS reports, complaints, bounces and the reputation watch Outbound shaping and IP warm-up: one slow destination must not stall the queue End-to-end encryption: S/MIME authority, OpenPGP and WKD, zero access, sealed mail Anti-spam & message filtering The anti-spam stack end to end: score, greylisting, SPF, DKIM, DMARC, DNSBL, SURBL Allow and block lists, digests, spam traps, country blocking and impersonation Viruses and dangerous attachments: ClamAV, custom scanners, the sandbox, sealed mail Mail flow rules, the outbound content policy and what a domain does to a message Guides & tutorials Quotas, retention and the archive: how much mail the server keeps, and for how long Administrator roles end to end: the 27 permissions, giving a role, where each works How the service is built and what runs out first: pools, connections, large folders IP ranges, auto-ban, lockout, tarpits, sending limits, send-as and password storage Behind a gateway, load balancer or reverse proxy: relays, PROXY, XCLIENT and HTTPS Hardening a public mail server: secure defaults, checklist, sealed secrets and roles Compliance: eDiscovery, legal holds, personal data, erasure and the audit trail How email works, and what hMailServer is and is not Relocating an installation to another drive, and moving or renaming a mailbox Automation & integration Scripting & the COM API Event scripts: every event and its arguments, the limits, and the 6.3.4 security fix The COM API on Windows: object model, Save() since 6.3.4, roles and the Import Tool REST API & integrations The REST API: turning it on, credentials, roles, one-time codes, OpenAPI and examples Active Directory and LDAP sign-in, directory synchronisation and OAuth2 bearer tokens hmctl and the PowerShell module: shell administration and accounts from a CSV file Integrating applications: the send API, mail in to a webhook, and outbound webhooks SCIM provisioning from Entra ID and Okta, and single sign-on for the organisation Rules, Sieve & routing Rules and Sieve: what runs when, vacation replies and the Run function action Routes, relays and smart hosts, OAuth2 relaying, restricted delivery and recall Aliases, distribution lists, membership queries, mailing lists and public folders Journaling and dual delivery: a compliance copy, and what a journal rule does not see Monitoring, metrics & logs Monitoring and health: Prometheus metrics, health probes, Grafana, OTLP and SNMPv3 Alerts, the audit trail, syslog and the journal, and per-domain reports Releases The current release, its announcement and the release history are in Announcements & releases.
  • 0 Votes
    1 Posts
    120 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) bears on relocating a Windows installation to another drive. hMailServer.exe /ProtectFolders, which setup runs at every installation and upgrade, now hardens the program folder, Bin and ControlPanel: where hMailServer was installed in a folder that took its permissions from the root of a drive, any account that could sign in to the computer could replace the programs the service runs (CVE-2026-107571); the default folder under Program Files was never affected. The procedure below copies the tree and re-registers it without running setup, and a folder below a drive's root takes the permissions the root hands down, so an installation relocated there is hardened by the upgrade to 6.3.7, or on 6.3.7 by running hMailServer.exe /ProtectFolders as an administrator, not by the move (on 6.3.6 or earlier follow the icacls steps in ServiceAccount.md, since those versions' /ProtectFolders leaves the three program folders as they are); before upgrading an installation at a drive's root, follow the steps "Before upgrading an installation at a drive's root" in ServiceAccount.md. Where accounts that are not administrators could sign in to the computer while the installation was in such a folder, the release says to reinstall the programs into a new folder that only the administrators may change, not over the installation, with setup given the new folder by /DIR=: over an installation setup installs into the folder it finds, and a 32-bit installation's uninstall entry overrides /DIR= until it is taken away, which the "re-run the installer" repair below meets. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026) has setup make or harden the program folder, Bin and ControlPanel before it copies a file or runs a program of the installation. One of them that another account could change, as a folder below a drive's root can be, setup hardens and then refuses, with exit code 7, until Windows has restarted; and a folder or a link on the way to them, or a program, library, script or settings file in them, that another account than SYSTEM, the Administrators and TrustedInstaller controls, it refuses with exit code 7, naming the icacls that deals with it and a restart of Windows before setup runs again. On Windows 6.3.8 may also refuse, with HM7534 or HM7536, TlsCaFile, PostgreSQLSslRootCert, a TLS certificate or private key file, OAuth2PublicKeyFile or AcmeCertificateDirectory where its path holds characters outside ASCII, so give the new root, and the folders above it, an ASCII name. See the 6.3.8 announcement. This guide covers two different moves. The first is relocating a configured installation to another directory or drive on the same machine, for example from C:\Program Files (x86)\hMailServer to C:\Program Files\hMailServer. The second is renaming a mailbox or moving it to another domain, which is an operation of its own since 6.3.4. Moving to new hardware is a third thing, and Moving to new hardware, and why Windows to Linux is a rebuild covers it. The one thing to know first: relocating is not one change. The installation path is recorded in the registry, in the service, in the COM registration, in hMailServer.ini and in a handful of database columns, and since 6.3.4 a [Settings] path edited in the file is ignored in favour of the value stored in the database. The relocation runbook is written in Windows terms: the registry, the service and the COM registration. The documents used here give no separate procedure for a Linux installation. Installing somewhere new is a different, solved problem: /DIR="<path>" on the installer puts a fresh installation wherever you want. The good news first The mail store itself is already relocatable. Message rows hold a bare {guid}.eml with no directory, and the path is rebuilt at every read against the data directory. Moving the data directory and pointing DataFolder at its new home moves every message with it. The exception is a database that predates hMailServer 5.4, where the column can hold a full path. Before you touch anything Take a backup and verify it restores (Backup and restore). Everything below is reversible only while the old tree and a good backup both exist. Ask the server whether its message rows are relative. In the Control Panel, Monitoring & troubleshooting → Diagnostics → Test message file locations. If it reports ERROR: Full paths are stored in the database, the move is still possible, but converting the paths becomes mandatory rather than optional. Write down what you are about to strand. The same Diagnostics page has an Installation paths section that lists the registry root, every [Directories] key and every path-shaped setting the running server holds, with the ones that do not exist marked. It changes nothing. The inventory Where What Repaired by Bin\hMailServer.ini, [Directories] ProgramFolder, DataFolder, DatabaseFolder, LogFolder, TempFolder, EventFolder Editing the file Bin\hMailServer.ini, [Database] PostgreSQLSslRootCert and TlsCaFile Editing the file. Leave the connection keys alone: they name a server, not a path The settings store (hm_inisettings) ArchiveDir, AcmeCertificateDirectory, OAuth2PublicKeyFile, UpdateTrustRootsFile, UpdateLogPublicKeyFile, and the certificate and private key files of the REST API, metrics and web services listeners Storing the new value, not editing the file (since 6.3.4) Registry, HKLM\SOFTWARE\hMailServer in the 32-bit view InstallLocation By hand Registry: the service's ImagePath, LocalServer32 (79 keys), InprocServer32 (15), the TypeLib values hMailServer.exe /Register Registry, HKLM\...\Uninstall\hMailServer_is1 InstallLocation, UninstallString, DisplayIcon Nothing; re-run the installer The database Certificate and key files, DKIM key files, the client-certificate CA bundle, backupdestination, the ClamWin and custom scanner paths, rule actions Each on its own page What breaks when a key is left behind. With a stale DataFolder the store appears empty. With a stale LogFolder logs are written to the old tree, silently. With a stale EventFolder event scripts are not found and events stop running. The 32-bit view is not optional. The server reads InstallLocation through KEY_WOW64_32KEY, and the installer writes it there. Writing the 64-bit view changes nothing and looks like it worked. The procedure Stop the service, and confirm it is stopped. Nothing here may run against a live server. Copy the tree. Copy, do not move: the old tree is the rollback. Include Bin\hMailServerApiKeys.ini if it exists, since the REST API key store lives there and no installer manifest mentions it. Grant the service account access to the new tree as 6.3.8's RelocatingAnInstallation.md does: a copy takes the permissions of the folder it lands in (under Program Files every user's Read on the new Data, Logs, Temp, Database, Events and hMailServer.INI) and not the old tree's entries for NT SERVICE\hMailServer, so the document makes the new root with the permissions setup gives a program folder before the copy, runs hMailServer.exe /ProtectFolders from the new Bin after step 5's /Register, and grants a service that runs as anything but LocalSystem its folders again with /GrantServiceAccess. A server that cannot write its log directory starts and then says nothing. Rewrite the file keys in <new root>\Bin\hMailServer.ini whose value was under the old root: the [Directories] folders, and [Database] PostgreSQLSslRootCert and TlsCaFile, the second named beside the first by 6.3.8's document, since with TlsMode=verify-full a CA file left in the old tree stops the server reaching its database once that tree is deleted (HM7531). Nothing else, and not a folder that was outside the old tree and has not moved: a DataFolder pointed at an empty directory makes the store appear empty. Repoint the registry root, from an elevated prompt: reg add "HKLM\SOFTWARE\hMailServer" /v InstallLocation /t REG_SZ /d "<new root>" /reg:32 /f. /reg:32 is the whole of the point. Re-register the server, from an elevated prompt, from the new Bin: hMailServer.exe /Register. It registers the path of the executable that runs it. Start it, and check what it read. Read the application log, and since 6.3.4 the ERROR log too. Confirm that the Diagnostics page's Installation paths shows the new paths (6.3.8's document names it; the Control Panel has no Settings → Directories page), that your domains are listed, and that the log file you are reading is under the new log directory. Convert full message paths, only if the diagnostic found any: run the Data Directory Synchronizer, in its import mode, on the old installation before step 2 copies anything, as step 0 of 6.3.8's RelocatingAnInstallation.md has it, not against the new data directory after the move: it converts only a row whose full path is where the file is found, so after the move it would import every such message a second time, and its other mode would delete the copies. A hand-written UPDATE hm_messages SET ... is not a supported converter. Store the [Settings] paths and fix the database-borne paths (both below). Verify before deleting anything: send and receive a message; complete a TLS handshake on every port that has a certificate; run a backup to completion; run an event script, if you use them; check the log directory is being written. Keep the old tree, unreachable by the service, for at least one full backup cycle. Do not make it unreachable until the [Settings] paths are stored and a reinitialise has picked up the new certificate and ACME paths. The failure this prevents. A server whose anchors are stale reports Running and serves an empty configuration. That failure is silent. The [Settings] paths: 6.3.3 and earlier, and since 6.3.4 On 6.3.3 and earlier these are [Settings] keys of hMailServer.ini like any other, and editing the file moves them. Since 6.3.4 it does not: the stored value is used, the edit is named in the error log as HM5804, and, where the service can write the file, the line is put back to the old path. RelocatingAnInstallation.md and the wiki page between them give three ways to store the new values. The first is the document's own, and the one to use: When How Where it is described After the moved server is running In the Control Panel under Settings, with Settings.SetIniSetting over COM, PUT /api/v1/settings/ini/{name}, or hmctl ini set <name> <value> RelocatingAnInstallation.md, step 9, and the wiki Before stopping the old server The same four ways, as the last thing before the service is stopped. A save applies ArchiveDir, OAuth2PublicKeyFile, UpdateTrustRootsFile and UpdateLogPublicKeyFile at once, not at the next start as the wiki page has it, so the running old server turns to a tree not yet copied: archiving stops, and token validation and update verification refuse. The database then holds the new paths, so going back to the old tree means storing the old values again The wiki, corrected by SettingsApply.md At the first start, if the service is already stopped A [SettingsOverride] section in the new hMailServer.ini, named in the error log as HM5807; then store the same values and remove the section before the next restart The wiki A save applies every one of these paths at once except the listeners' certificate paths and the ACME certificate directory, which are bound when the servers start and wait for a reinitialise (Apply now, or POST /api/v1/server/reinitialize). Until then the server runs on the old paths, which still resolve because the tree was copied rather than moved. PasswordPepper is the exception to all of this: it lives in the file only, is not a path, and comes across with the copied file. The database-borne paths SSL certificates. Since 6.3.7 the Control Panel's SSL certificates page has Edit…, which changes a certificate's name and files and keeps its id, so every port that presents it keeps it and reads the new files at the next reinitialise; deleting a certificate and adding it again, as the document had it until 6.3.8, leaves each port that presented it with none, and such a port does not start (HM5113). It can also be set over COM. DKIM keys. Domain → DKIM, the primary and, if used, the secondary. A domain whose key rotation manages its keys refuses that edit. Client-certificate CA bundle, per port. A port set to require a client certificate refuses every client when its bundle cannot be loaded. Archive index. hm_archiveindex.archivepath is matched on the exact string by retention, legal hold and the address eraser. Do not bulk-rewrite it. If the archive directory moved, move the archive back, or accept that the rows written before the move point at the old location; there is no supported converter for this column. What stays broken, and why Add/Remove Programs still points at the old unins000.exe. Do not edit unins000.dat. The supported repair is to re-run the installer at the new root. Start-menu shortcuts hold absolute targets and nothing repairs them. Message content. The old path appears inside messages the server itself wrote, such as a delivery report. Never rewrite message content. Renaming a mailbox, or moving it to another domain New in 6.3.4 (schema 6056, whose table hm_mailboxmoves is the move's journal). new_address carries the domain, so renaming and moving are the same operation: [email protected] to [email protected] is a rename, [email protected] to [email protected] is a move. A move keeps the account's row, so everything keyed on the account id follows without being touched: rules, folders with their subscriptions and access-control entries, application passwords, the second factor, contacts, calendars and the role assignment. Copying the mail into a second account loses all of it. Interface How Control Deck The Move or rename card in the account editor hmctl hmctl mailbox status [email protected], then hmctl mailbox move [email protected] [email protected] REST POST /api/v1/accounts/ann%40example.com/move with {"new_address": "[email protected]", "keep_alias": true}; GET on the same path says whether anything holds the mailbox PowerShell Move-HmMailbox -Address [email protected] -NewAddress [email protected] Control Panel (Windows) Move or rename… on the account's General tab COM (Windows) Account.MoveTo(NewAddress, KeepOldAddressAsAlias) or Utilities.MoveMailbox(Address, NewAddress, KeepOldAddressAsAlias) The old address is kept as an alias by default, pointing at the new address. Turn it off (keep_alias false, --drop-alias) only when the address is to stop working: mail to it is then refused at RCPT TO. What moves: the message store directory <data>/<domain>/<local part>; the Sieve tree; aliases in any domain that point at the account; distribution list memberships; recipients of mail still in the delivery queue; and quarantined mail, up to a ceiling of 200,000 rows. What does not move, on purpose. The archive and the message trace stay under the old address, as a record of the past. Erasure works by address, so erasing the new address will not reach the archive written under the old one: erase both (Compliance: personal data and erasure). The domains a role reaches are not rewritten either, so check a role after a cross-domain move (Administrator roles end to end). What is refused, before the first byte is moved: a domain this server does not hold, or one that is not active; a destination domain at its maximum number of accounts, or that would exceed its maximum total size; an account, an alias or a distribution list already at the new address; a mailbox encrypted at rest moving to another domain (409); a rename inside the domain is not refused; an installation whose message file names are still full paths; a credential that may not administer accounts in both domains; a mailbox in use. In use means a POP3 session, or an IMAP session with one of the account's folders selected. What is not counted: an IMAP session that has selected nothing, a webmail request in flight, a ManageSieve session and an SMTP delivery to the mailbox in flight. A message that arrives during the copy is written under the old directory, and the old directory is swept once afterwards; anything that could not be carried is HM6948. That is why a move of a large busy mailbox belongs out of hours. When a move fails or is interrupted There is no transaction across a filesystem and four database backends, so the order is chosen to make each step undoable: the store, then the account row, then the references, then the alias. A journal row is opened before the first step and closed after the last, and an interrupted move is said once per start as HM6947 in the application log, naming both addresses and the stage it stopped at. Stage What it means started Nothing has been changed moving the store The copy was under way; look at both directories. The database has not been touched store moved The directories are at the new address. While the account row says the old one, the account authenticates and its mail is unreadable account written The store and the account row agree; the references have not all been rewritten references rewritten Everything but the alias complete The move finished rolled back A step failed and everything before it was put back rollback failed Something could not be put back; HM6940 is in the application log To finish a move interrupted at moving the store or store moved, ask for the move again, naming the address the directories now have. Read the journal with hmctl mailbox status or GET /api/v1/accounts/<address>/move. The journal is not in the backup. Code Meaning HM6941 The move succeeded but the alias for the old address could not be created HM6942 Aliases pointing at the account could not be re-pointed HM6943 Distribution list memberships could not be moved HM6944 Messages in the delivery queue still name the old address HM6945 Quarantined messages still name the old address HM6946 The move could not be journalled. It still happened, and is on the audit trail Who may. manage_accounts, in both domains. The server administrator and a domain administrator of both domains have it; the help-desk role and the auditor do not. view_accounts is enough to read the journal. A move is one entry on the audit trail: object type account, action moved. What it does not do The relocation runbook does not repair the uninstall entry or the shortcuts, and it has no converter for the archive index. A mailbox move does not wait for a delivery in flight; nothing in the server records one. Setting Account.Address and saving goes through the same implementation, but leaves no alias, does not refuse while the mailbox is in use, and cannot cross domains. A domain rename (Domain.Name and Save()) is a different thing again, and keeps the path it has always taken. Where to read more Wiki: Relocating an Installation. Documents: RelocatingAnInstallation.md and MailboxMove.md. The public documentation: 33.1, the installation on disk. On this forum: Moving to new hardware, and why Windows to Linux is a rebuild, Upgrading to 6.3.4, and how the settings store works and when a save takes effect in Settings: what is not stock, when a save takes effect, and the three levels.
  • 0 Votes
    1 Posts
    123 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
    128 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) regroups the Control Panel's sidebar: the audit trail, journaling and eDiscovery are a Compliance group of their own rather than the last rows of Monitoring & troubleshooting, as the table below has it, and Ctrl+K takes every old title and route to its page. See the 6.3.7 announcement. This guide covers what an operator is asked for when a lawyer, a regulator or a person with a data request arrives: eDiscovery (cases, searches, legal holds and exports), a person's own data (the export and the erasure), and the audit trail that records who did what. eDiscovery, the person's data as one archive, the erasure of log lines, the erasure's reach into the stores beside the mailbox, and the audit trail are new since 6.3.4. The one thing to know first: cases, their files and their holds are not in the backup. A server rebuilt from a backup has no cases, no exports and no holds, so place the holds again before its mail can be deleted. Where it is Where What The Control Deck, Discovery view The whole of it: cases, the search form, holds with Lift the hold, exports with Download, Open and Close REST, /api/v1/discovery/cases The same ten routes the Deck calls, in the OpenAPI document The Control Panel (Windows only), Monitoring & troubleshooting → Discovery (eDiscovery, in the Compliance group, since 6.3.7) Read-only: the cases and their lines of the audit trail, read over the REST listener, which must be on (RestApiPort). It changes nothing COM, hmctl Neither On Linux, then, discovery is worked from the Deck or over REST. Discovery and the erasure do nothing until somebody opens a case or asks for an erasure; the audit trail is on from the first start. Two neighbouring tools are not this one. The archive has had a legal hold of its own since before 6.3.4 (/api/v1/archive/{id}/hold), which keeps one archived copy from the archive's retention sweep (Quotas, retention and the archive). Journaling, a copy of a domain's or an account's mail sent elsewhere as it passes, has its own topic: Journaling and dual delivery. Cases A case has a name (up to 255 characters), a description (up to 1,000), who opened it and when, and a state: open or closed. Its searches, holds and exports belong to it. Closing a case releases every hold of it that is still live, each release on the audit trail; a closed case runs no search, places no hold and makes no export, and what it made stays readable and downloadable. A case is never deleted, and an address's erasure does not reach it. The files a case makes are under the data directory, in Discovery\case-<id>\: search-<id>.json for each search and export-<id>.zip for each export. Nothing removes an export; delete its zip by hand when it is no longer needed. Searching POST /api/v1/discovery/cases/{id}/searches takes: Part What it means custodians (required, at most 100) An address, a domain, or * for every account on the server and the whole archive from, to (optional, YYYY-MM-DD) The day a message is dated by its Date field, read as UTC; else the day the server stored it words (optional, at most 50) Every word must be found, a quoted phrase as one: a case-insensitive substring of what IMAP SEARCH TEXT reads attachment (optional) name, type and min_kb; {} is any attachment at all scope mailboxes, archive or both, the default There is no sender or recipient criterion: a word is looked for in the whole decoded header, and a person is named as a custodian. A query the server cannot take is refused with 400 and the reason; 202 answers with the search's id, and the search is queued. Where it looks. For each custodian account: its folders, its deleted items (what a hold kept, and what RecoverDeletedDays keeps), and the archive. A search copies nothing; it lists each hit, and lists separately what it could not read, with the reason. What it cannot see. 100,000 hits. A search stops there and says truncated. Narrow it and run it again. Text inside a PDF, a Word document or a spreadsheet. Only text/* attachments are read, up to 1 MB of characters a message. An attachment criterion still finds the PDF by name, type and size. More than 80 MB a message. It is listed as not searched when the query asks about its body, and never dropped as a non-match. Zero-access mailboxes and messages encrypted end to end are matched on their headers alone and marked headers_only. The stores beside the mailbox, <data directory>\Confidential and \Protected, and archived copies with no index row (a bounce, a delivery report). The full-text index is a filter, never the answer. It is off by default (IndexerFullText). When it is on, a message the index proves cannot contain a word is ruled out; every other message is read. One at a time. Searches and exports are queued and run by one runner, the server's own. Only one hMailServer service may run against a database at a time (Warm standby and high availability), so a standby started after a failover takes over the queue rather than sharing it. Holds POST /api/v1/discovery/cases/{id}/holds takes one of: Body Holds {"scope":"account","target":"[email protected]"} Every message of the account, and its archived copies {"scope":"domain","target":"example.com"} Every message of every account in the domain, and their archived copies {"scope":"archive","search":12} The archived copies search 12 of this case matched, at most 10,000 at once A hold is live until it is released or its case is closed, and what it keeps is kept by any live hold of any case. Path Under a hold The person's delete (IMAP EXPUNGE, POP3 DELE, the webmail's delete, a folder deleted) and an administrator's delete of a message The message leaves the folder, and a copy is kept among the account's deleted items whatever RecoverDeletedDays says (0 by default). If the copy cannot be made, the delete is refused (HM7333) An IMAP MOVE out of the account The original is kept among the held account's deleted items Every message of the account deleted at once Refused, 409 over REST The account's own filters at delivery: a rule's Delete or Reject, a Sieve discard, reject or redirect, a forward that keeps no copy The copy is kept among the deleted items instead of dropped; if it cannot be kept, the message is delivered Retention, the recover-deleted sweep, deleting a deleted item for good Nothing of the held account is deleted A mailbox restore with mode replace, a full restore with the domains option Refused (the full restore as HM5014) Moving the account to another domain, removing its mailbox key, deleting the account or its domain Refused, over REST, SCIM and COM alike The archive's retention sweep and an address's erasure A held copy keeps its file and its index row What a hold does not reach: the server's and a domain's rules, which decide before a message is split between its recipients; spam and viruses refused or deleted before a message is the account's, and what the quarantine discards; the delivery queue; the confidential and password-protected stores; archived copies with no index row. If ArchiveDir was put inside the data directory, a full restore with the messages option replaces held archived copies too, so keep the archive outside it. The hold is not hidden from the custodian. The account's own GET /api/v1/me/recoverable answers held: true and lists what the hold kept, and the webmail's Recover deleted items page offers no delete for good. It fails closed. When the holds cannot be read, every question is answered held, and the error log says so once a minute (HM7332). A hold is a row in the database. The server reads the holds again as soon as one is placed or released, and no more than five seconds after a change made elsewhere. Exports POST .../searches/{search}/export queues an export of a finished search's hits. The zip holds: Each hit as EML, the message's own bytes as the server stores them, under <custodian>/<folder>/<n>-<message id>.eml, and an mbox per custodian folder beside them, in parts of at most 256 MB. SHA256SUMS, so that sha256sum -c SHA256SUMS checks the unpacked export. manifest.json: the case, the search as it was run, who ran it, who asked for the export, every item with its path, SHA-256 and size, and every item withheld, with the reason. The zip's own SHA-256 is kept with the export, and the case answers it (exports[].sha256). Withheld: a zero-access mailbox's messages, and anything deleted, moved or swept since the search ran. Search again to find where a message is now. Limits. The zip written is the 32-bit format, so no export passes 4 GB: narrow the search and export each part. A download past 500 MB answers 413 with where the file is on the server's disk and its SHA-256. An export that would leave the data directory's volume with less than a gigabyte free fails. A discovery export is never a PST; an examiner who needs one converts the EML files with a third-party converter. Who may Permission Allows Held by view_discovery Reading the cases, their searches and hits, holds and exports The server administrator and the auditor role manage_discovery Opening and closing a case, running a search, placing and releasing a hold, making an export The server administrator; a custom role may be given it Downloading an export needs view_mailbox as well, since it hands over the mail itself, the custodians' deleted items among it. A search that reads the archive needs view_archive as well. A credential confined to named domains names no custodian outside them, never *, and is answered 404 for a case that reaches outside them. A person's data: the export One .zip of everything the server holds for an account. The person downloads it from the webmail's Your data card (GET /api/v1/me/export.zip), with no administrator involved; that download is refused (403) to a request authenticated with an app password. The administrator has the Deck's "Everything (.zip)" on the account row, GET /api/v1/accounts/{address}/export.zip (the administrator password's alone), POST /api/v1/accounts/{address}/export with archive, hmctl privacy export <address> --archive <file> and, on Windows, COM Account.ExportArchive(file). Entry What it is manifest.json The address, the moment, and the counts of what follows mail/<folder path>.mbox Every folder with mail, one mbox each account.xml What the backup carries for the account, less the credentials contacts.vcf The address book as vCard 3.0 calendars/<calendar>/<object> Every calendar object as the client sent it preferences.json, filters.sieve The webmail's preferences, and the active Sieve script logs.txt The lines of the server's own log files that name the address, up to 50 MB A download past 500 MB is refused with 413, and the operator writes the archive to disk instead; the disk form has no ceiling but the format's 4 GB. A person's data: the erasure POST /api/v1/addresses/erase, hmctl privacy erase, the Deck's "Erase traces" and, on Windows, the Control Panel's "Erase traces..." remove what deleting the account deliberately leaves: quarantine entries, message-trace rows, greylisting triplets, distribution-list memberships, aliases on either side, the bounce and complaint records, and the rows naming the address in other people's address books and first-contact memory. With the archive included, the person's archive tree is swept and every index row naming the address goes. The log lines are asked for by name (include_logs on the REST body, --logs every occurrence becomes [erased], and a log past 256 MB is reported instead of rewritten. Since 6.3.4 it also reaches the stores beside the mailbox. A message held for review that the address sent or would receive is discarded. The address is taken out of the recipients of a message that expires, and one it sent is ended. A password-protected message to it is ended. The recipients an account remembers writing to, the out-of-office memory, booking policies and correspondents' S/MIME certificates and OpenPGP keys lose it. Every list of addresses is matched whole. Erasing [email protected] leaves [email protected] and [email protected] as they are. A legal hold outranks it. An erasure never deletes a message from a mailbox, so a hold meets it only in the stores it deletes from, and there the hold wins. The REST answer's held counts what a hold kept; run the erasure again once the case is closed. When the holds themselves cannot be read, the erasure fails (HM6250; 500 over REST) and says nothing about a legal hold. What it leaves, by design, among others: mail in mailboxes, the person's own and everybody else's, and a mailbox's deleted items; a list's archive of its posts; other people's calendars; configuration somebody made that names the address (a forward, a rule, a list's moderator); the audit trail, and the alert events, which nothing prunes yet; the spam trap record; the delivery queue and the webhook queue; the contacts in DMARC and TLS reports; the archive's Inbound folder; and backups, which are outside the running server's reach. PersonalData.md has the full list, with each reason. The audit trail, for a compliance reader The audit trail is on by default. It keeps one row in hm_audit for each administrative change made over COM, REST or the Deck, a mailbox's move included, with a few exceptions such as an API key made or revoked or a quarantined message released or deleted; it records changes, never reads. A password, key, token or other secret is recorded as (changed), never as a value. Each row carries the SHA-256 of the row before it and its own, so GET /api/v1/audit/verify tells an edited row from a removed one: tampering is detectable, not impossible, since anyone who can write the table can recompute the whole chain. audit_retention_days is 0 by default, which keeps everything. Three things on it matter here. Every discovery search is recorded with its whole query; a download of a discovery export is recorded on the trail alone, so with the trail off a download is recorded nowhere; and an erasure is recorded with the address it erased wherever it deletes from recorded tables (aliases, list recipients, the archive index): those rows name the person until audit_retention_days ages them out. Alerts, the audit trail, syslog and the journal has the columns, reading and putting a change back, who may read it, and what it does not record. Regulatory scope, in one paragraph The project's own determination, reviewed 12 August 2026, is that the EU Cyber Resilience Act and the revised Product Liability Directive do not place a manufacturer's obligations on it, because it is supplied outside a commercial activity. The document calls itself a self-assessment and not legal advice, and lists the triggers that would re-open it. Under GDPR and UK GDPR the project is neither controller nor processor: the operator of an installation is. The export, the erasure and the log retention limits exist so that operators can meet their own obligations. What it does not do No PST export, and no text read from attachments but text/*. No silent hold: the custodian can see what a hold kept. No export past 4 GB and no download past 500 MB. No saved queries, review tags, redaction or de-duplication across custodians: a message sent to two custodians is two hits. No search of backups, and no case carried by a backup. Where to read more Wiki: Compliance and eDiscovery. Documents: Discovery.md, PersonalData.md, AlertsAndAuditTrail.md and RegulatoryScope.md. On this forum: The Control Deck, Journaling and dual delivery, and what a backup carries and what it leaves in Backup and restore.
  • 0 Votes
    1 Posts
    125 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) is a security release that bears on this guide. On Windows, hMailServer.exe /ProtectFolders, which setup runs at every installation and upgrade, now hardens the program folder, Bin and ControlPanel: where hMailServer was installed in a folder that took its permissions from the root of a drive, any account that could sign in to the computer could replace the programs the service runs (CVE-2026-107571); the default folder under Program Files was never affected, and an installation at a drive's root is hardened by the upgrade itself. The serveradmin role given with a list of domains is confined to those domains over COM as over REST, a credential confined to named domains is refused the changes that reached beyond its domains over REST and COM, and a state-changing REST request that a browser reports as cross-site is refused. SMTP, POP3 and ManageSieve refuse a SASL PLAIN sign-in that asks to act as another account, and the stored-secrets key is no longer left in memory freed without being written over. An upgraded Windows server keeps the .NET 10 Desktop Runtime it had, 10.0.12 where 6.3.4 to 6.3.6 installed it, against which Microsoft published advisories on 13 October 2026: bring it to 10.0.13 through Microsoft Update or Microsoft's installer. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026) is a security release too: upgrade every server running 6.3.7 or earlier. On Windows setup now makes or hardens the program folder, Bin and ControlPanel before it copies a file or runs a program of the installation (the four narrower cases of work item 78 that 6.3.7's notes left open), and a program folder, Bin or ControlPanel, a folder or a link on the way to them, or a program, library, script or settings file in them, that another account than SYSTEM, the Administrators and TrustedInstaller controls ends the installation with exit code 7, naming the icacls that deals with it and, where it says so, a restart of Windows before setup runs again. The auto-ban saves the IP range it adds under a name of its own, so that it bans the address; the web services listener (CardDAV, CalDAV, Autodiscover) refuses an address an auto-ban or an IP range bans; and a sign-in's name is matched to its account as the per-name lockout and the compromised-account hold count 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: correct the name in the client. See the 6.3.8 announcement. This guide covers what stands between a mail server on the public internet and the people who guess passwords at it. All of it is new in 6.3.4 except auto-ban, account lockout, the administrator's second factor and the IP range option Require TLS for auth, which are older; 6.3.4 sets that option on a new installation and judges it in the checklist. The one thing to know first: the secure defaults are applied only to a database the installation itself created, and an upgraded server is never changed. After an upgrade your listeners, IP ranges and service account are as they were and nothing is sealed until you make a key; the checklist says what is left. What a new installation starts with, and what an upgrade keeps In 6.3.3 and earlier a new installation listened on ports 25, 587, 110 and 143 with no TLS, and neither shipped IP range required TLS for authentication. The create scripts still make that database; the Windows installer, and the Linux packages' first start, now change it before the server first listens. A new installation since 6.3.4 An upgraded server Ports 25 and 587 (SMTP), 110 (POP3), 143 (IMAP) STARTTLS offered, optional as it was IP range Internet (every IPv4 address) TLS required for authentication as it was; the checklist flags it IP range My computer (127.0.0.1) unchanged as it was Certificates Self-signed (made at installation) as they were Windows service account NT SERVICE\hMailServer the account it had (LocalSystem, if it began before 6.3.4) What a client sees. From another machine, AUTH on SMTP is not offered before STARTTLS and is refused with 530 A SSL/TLS-connection is required for authentication. if it is sent; IMAP LOGIN and AUTHENTICATE answer BAD A SSL/TLS-connection is required for authentication.; POP3 USER and AUTH answer -ERR. After STARTTLS each works as before. From the server itself, on 127.0.0.1, nothing has changed, and mail from other servers on port 25 is taken with or without TLS. Why STARTTLS is optional. A port's requirement applies to every address: on port 25 it would refuse every server that cannot start TLS, on the others this computer's own clients. The range's rule protects the password. The certificate. self-signed-certificate.pem and its key self-signed-key.pem are in <data directory>/Certificates: C:\Program Files\hMailServer\Data\Certificates on a default Windows installation, /var/lib/hmailserver/Certificates from the Linux packages, where the key is mode 0600 and owned by the service's user. It is RSA 2048, runs 825 days, names the host and gives the organisation hMailServer, self-signed at installation. It encrypts, but nothing vouches for it, so mail clients warn. Replace it with ACME (set AcmeEnabled, AcmeContactEmail and AcmeDomains; the first certificate goes to every listener that holds the self-signed one) or with your own, set on each port. See Let's Encrypt certificates are automatic. Where the defaults are applied The create step marks its database with the hm_settings row NewInstallDefaultsPending; the verb --apply-new-install-defaults requires the mark and removes it once it has decided. Platform When it runs What to know Windows The installer, after the database step of a new installation, with the service stopped A database DBSetup attached, rather than created, carries no mark: exit 5, logged, nothing changed Linux (.deb, .rpm) The unit's first start after sudo -u hmailserver hmailserver --create-database, which marks the database and leaves a one-line file, .new-installation, in the DataFolder The unit runs ExecStartPre=-/usr/bin/hmailserver --apply-new-install-defaults --if-marked: nothing unless that file is there, then one try at the database, given room by TimeoutStartSec=300 (the leading - forgives a failure, not a timeout). It ignores a mark made more than a day from its clock; a run by hand has no such limit A container (since 6.3.8) The image's entrypoint, before whatever command the container runs; the Compose file and the Helm chart run the same step before the server hmailserver --apply-new-install-defaults --if-marked secures a database the entrypoint's --create-database made a moment before and opens nothing at any other start; a failure (run as root, a database not reached) is said and forgiven, and the server starts all the same By hand A container from an image before 6.3.8, a server started by hand rather than by the unit, or defaults left for later With the service stopped; on Linux as the service's user, because the verb refuses to run as root By hand, on Windows and then on Linux: Stop-Service hMailServer & "C:\Program Files\hMailServer\Bin\hMailServer.exe" --apply-new-install-defaults Start-Service hMailServer sudo systemctl stop hmailserver sudo -u hmailserver hmailserver --apply-new-install-defaults sudo systemctl start hmailserver Exit codes: 0 applied (with --if-marked, also nothing to do); 1 something could not be made or written (HM7510, HM7511 in the error log); 2 no database named, or not readable enough to judge, or on Linux run as root; 4 the Windows service is running; 5 not a database this installation created, or not as the create script left it: nothing changed. On Linux, mind the data directory. A DataFolder outside the unit's ReadWritePaths (/var/lib/hmailserver and /var/log/hmailserver) is read-only to the service, so the first start can neither write the certificate nor store mail there. Add the directory with a drop-in (sudo systemctl edit hmailserver, then ReadWritePaths=/srv/mail under [Service]) before that first start. Not yet proved on a clean machine. SecureDefaults.md's checks that the installer applies the defaults are not yet written, and those for the .deb ran only on GitHub Actions, so nothing runs them today. The setup checklist The checklist judges every server, new or upgraded, in twelve items. Key Done when profile a setting profile has been applied; asked for on a server with fewer than ten accounts domain an active domain exists, and none has a name the server reserves for its own directories account at least one account exists submission every SMTP listener other than port 25 has STARTTLS or implicit TLS, and a certificate other than the self-signed one sign_in_tls every IP range that lets a client connect from another address requires TLS for authentication, or reaches only listeners that start TLS before a password spf, dkim, dmarc every active domain has a TXT record beginning v=spf1, signs with a key whose record is published at <selector>._domainkey.<domain>, and has a TXT record beginning v=DMARC1 at _dmarc.<domain> backup the destination is set and the folder exists second_factor the administrator password is enrolled with a one-time code stored_secrets_key this server holds the key the database records, and an export of it has been recorded service_account it is not LocalSystem on Windows, not root on Linux Each item is done, action_needed, cannot_tell or not_needed. Port 25 is never the submission port, and a plain submission port beside a secured one still needs action. Where it is. GET /api/v1/setup/checklist (reading needs view_configuration; ?dns=0 skips the lookups), the Deck's Checklist view and, on Windows, COM's Utilities.GetSetupChecklist(LookUpDns) and the Control Panel's External setup page and dashboard. Passwords only over TLS, on a server you upgraded "Require TLS for auth" is an option of an IP range, not of a port, and covers SMTP, POP3, IMAP and ManageSieve alike. Read the sign_in_tls item: it names each range, and the listeners where a password from there would cross the network in the clear. If the finding is sign_in_no_tls, a listener has no TLS to start, and requiring TLS would refuse every client on it: give those listeners a certificate and STARTTLS first. This finding carries no fix. Set every mail client that signs in from another machine to STARTTLS first. Once the range requires TLS, a client that signs in without TLS is refused; that is why an upgrade is never changed by itself. When the finding is sign_in_plain, apply its fix: the Deck's button Require TLS for sign-in from this range, or PUT /api/v1/ipranges/{id} with {"require_tls_for_auth": true}, which needs manage_settings. On Windows the Control Panel opens its IP ranges page, where the setting is Require TLS for auth. Replace the self-signed certificate if you have one, so that submission_self_signed clears. Secrets at rest and the stored-secrets key Until 6.3.4 the administrator password (unsalted MD5, from the Windows installer), each account's second factor and the [Settings] secrets were kept as written. The administrator password is now written as PBKDF2. An older MD5 or SHA-256 value is rewritten as PBKDF2 at the first successful administrator sign-in where the service can write the file. Where it cannot (NT SERVICE\hMailServer, or the Linux package's root-owned file) the old value stays, HM7494 says so once, and the verb rewrites it: on Windows hMailServer.exe --set-admin-password from an elevated prompt, with the password in HM_ADMIN_PASSWORD, or on one line of standard input from a pipe or a file for a password in plain ASCII (Windows PowerShell 5.1 turns every other character into ?), never as an argument; on Linux sudo hmailserver --set-admin-password, which reads standard input. Second factors and ten [Settings] secrets (SRSSecret, BATVSecret, OAuth2HmacSecret and the rest) are sealed with AES-256-GCM under the installation's stored-secrets key, in the form HMK1:<key id>:<base64>, once the installation has a key. The key is kept in each server's hMailServer.ini as [Security] StoredSecretsKey, and never in the database or a backup archive. Who gets a key without asking. The Windows installer makes one on a new installation. On Linux, hmailserver --create-database makes one when it creates the schema and can write the configuration: in a container, whose /etc/hmailserver the service owns, it does (no image of 6.3.4 is published); run by the package as the service's user, it cannot. An upgraded installation, and a package installation on Linux, seal nothing until an administrator makes the key: Make the key on one node. Windows, from an elevated prompt beside hMailServer.exe: .\hMailServer.exe --create-stored-secrets-key. Linux: sudo hmailserver --create-stored-secrets-key. Export it at once: .\hMailServer.exe --export-stored-secrets-key D:\Escrow\hmailserver-stored-secrets.key, or sudo hmailserver --export-stored-secrets-key FILE. The export writes a new file, never over one that exists, readable only by SYSTEM and the administrators on Windows and mode 0600 on Linux, and prints the key id, never the key. Restart the service on that node (Restart-Service hMailServer, or sudo systemctl restart hmailserver). Its next start seals what is stored as written; until then nothing is sealed. On every other node that shares the database, --import-stored-secrets-key FILE, then restart each. An import is refused while the node holds another key that opens: a standby or new node whose installer made a key of its own removes its [Security] StoredSecretsKey line first, and until then the shared database refuses it (HM7495). Keep the exported file with the database backups, stored apart from them. Why the export matters. The product's own backup archive carries these secrets opened, and a restore seals them under the restoring server's key, so set a backup passphrase. A database-level backup (a dump, a copy of the .sdf) restored without the key brings back every second factor and [Settings] secret unopenable. A copy of hMailServer.ini is no copy of the key on Windows, since its line is sealed under that machine's DPAPI; never copy that line from one machine to another. When a node lacks the key it says HM7495 at every start and makes no key of its own; until the key is imported, a [Settings] secret sealed under it runs as though unset (HM7492) and a second factor refuses every code (HM7493). A key lost for good is recovered from with --forget-sealed-secrets <key id>, with the service stopped (exit 4 otherwise), which clears only what that key sealed: every second factor is enrolled again, every [Settings] secret entered again. ProtectStoredSecretsWithDPAPI=0 (default 1) has a cost. The next start opens what was sealed and stores it as written, readable by anyone who can read the file or the database, and the machine-bound secrets are written with the legacy Blowfish scheme, whose key is in the source code: the way back, not a setting for a new server. Two things to do after an upgrade. Since 6.3.4 PasswordPepper stays in hMailServer.ini only and is left out of every backup, so keep a copy of that value with the backups. And a password hash stored before 6.3.4 from a password with a character above U+00FF is refused, to its own password too: an administrator sets such passwords again, and the application log names each account as its user tries to sign in. Checking new passwords against published breaches Off by default, because it sends something off the server. When on, a new password found in the published corpus of breached passwords is refused. Setting (REST key) Default What it means breach_check_enabled off The whole feature breach_check_url https://api.pwnedpasswords.com/range/ Where the five characters are sent breach_check_minimum_count 1 Refuse a password seen this many times or more breach_check_unreachable_action allow allow lets a change through unchecked; refuse refuses it What leaves the server is the first five characters of the password's SHA-1 and nothing else. The default endpoint is a third-party service, Have I Been Pwned's Pwned Passwords, and an organisation that cannot send it those five characters, its address and the time runs its own mirror. When it runs. When a password is chosen, by the person or by an administrator, and never at logon. An endpoint that gives no complete answer within eight seconds counts as unusable, and the error log records HM6600 (let through) or HM6601 (refused). Under refuse, an endpoint that is down refuses every password change on the server. Bulk imports make one request per account, so run them against a mirror or with the check off. Where it is set. The four settings are hm_settings rows, not hMailServer.ini lines, and a change applies from the next password chosen. They are on the Control Panel's Administrative access → Password page (the Passwords found in published breaches card), in the Deck's Settings view, over REST and in hmctl settings set: hmctl settings set --set breach_check_enabled=true Compromised-account detection This judges every authenticated submission (SMTP, the webmail's send, a scheduled draft) against the account's own history of the last 30 days, without reading the message. It starts in watch on every installation, new or upgraded: the baseline is built, the account.compromised alert says what enforcing would have done, and nothing else happens. Four signals score: volume (2 points), the last hour's messages reaching compromise_volume_floor (30) and exceeding compromise_volume_factor (4) times the busiest baseline hour; recipients (2), the last hour's distinct recipients reaching compromise_recipient_floor (20), at least 80 per cent of them new to the account; hour (1), an hour of the day (UTC) the account has not submitted at in the baseline; address (1), a client network (the /24 of an IPv4 address, the /64 of an IPv6 one) not seen for this account. compromise_score_to_lock (3) is a takeover. In enforce, the message is held, the account is locked until an administrator unlocks it, and the alert and an account.locked webhook are raised. The client is still answered 250 Queued. Held mail is reviewed on the Deck's Held for review page and under /api/v1/review; a held message nobody has looked at in compromise_held_days days (7) is discarded. Watch it before enforcing: the false positive is a person locked out of their own mail. There is no Control Panel page for it. Roles instead of the administrator password Rather than share the administrator password, give each administrator a role in the domains they look after, on the Deck's Roles view, over REST, or on Windows in the Control Panel or over COM. Nothing changes on the day you upgrade: an account with no assignment holds the authority its admin level has always given it, and a narrow role cannot take over a wider one, so a help desk is refused a domain administrator's password reset. Administrator roles end to end has the permissions, how to give a role, and the two stores only the administrator password opens. The rest of the list Auto-ban is on by default: 3 failures within 30 minutes, a 60-minute ban. MaxInvalidLogonAttempts at 0 disables it, and AutoBanMinutes at 0 disconnects the client but bans nothing. Account lockout (AccountLockoutThreshold) is off until set, and counts by the name being guessed at. IP ranges, auto-ban, lockout and the rest has both in full. The server-enforced administrator second factor refuses the password alone everywhere, the REST API included. On Windows, move a service still on LocalSystem with the installer, the Control Panel's button, or from an elevated Command Prompt in Bin: hMailServer.exe --confirm /GrantServiceAccess, then, only if that ended in 0, hMailServer.exe --service-account virtual /Register, then net stop hMailServer && net start hMailServer. Check first what the virtual account cannot do, listed in Installing on Windows. The event-script flaw fixed in 6.3.4 (CVE-2026-100741; its record was not yet public when this was written) is in every release from 6.0.0 to 6.3.3: upgrade, and until you can, switch event scripting off. See Event scripts and the 6.3.4 security fix and the 6.3.4 security announcement. What it does not do Passkeys, single sign-on and the session policies do not reach IMAP, POP3 or SMTP, which still take the password or an app password. The breach check does not cache, and does not expire a password that enters the corpus later. Compromised-account detection does not drop a session that is already up. The stored-secrets key protects against a copy of the database that leaves without the file, not against a local account on the server. Where to read more Wiki: Security Hardening. Documents: SecureDefaults.md, SetupChecklist.md, SecretsAtRest.md, PasswordBreachCheck.md, CompromisedAccounts.md and Roles.md. The public documentation: chapter 17, Security hardening. On this forum: Upgrading to 6.3.4, The Control Deck and hMailServer on Linux. A suspected vulnerability is reported privately, never in a forum post: 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 (SECURITY.md).
  • 0 Votes
    1 Posts
    117 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
    119 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
    119 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) changes three things this guide touches. On MySQL and MariaDB a lost database connection is reconnected and the statement run again, so the statement after a database restart, a failover or wait_timeout no longer fails, and an insert that forgives a duplicate key forgives that alone, so an error log that was quiet may now name failures that were hidden. On Windows a stop or a reinitialise no longer waits past a work queue's ten seconds for a thread held by a host or a database that has stopped answering, and setup, the live update's silent install and the uninstaller wait ShutdownDrainSeconds plus 90 seconds for the stop, where they waited plus 30. 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. This topic describes a running hMailServer 6.3.4 service: its parts and pools, the connection ceilings in front of them, the scheduled work behind them, what the machine needs, and what a folder of a hundred thousand messages costs the webmail. The one thing to know first: nothing in the server is unbounded. There is no thread per connection and no pool that grows. Every piece of work runs on a pool of a set size, and a dependency that stops answering uses up a pool long before it uses up the processor, the memory or the disk. What it cannot give you is a throughput figure: none has been measured yet. The parts of the running service Everything runs in one process: the Windows service hMailServer, or the systemd unit hmailserver on Linux. Part What it is Notes Mail listeners SMTP, POP3 and IMAP on IOService + TCPServer Boost.Asio HttpServer The engine under two listeners: REST (/api/v1, the webmail at /portal, the Control Deck) and web services (MTA-STS policy, autoconfig, ACME http-01) Its own io_context, so a request storm cannot starve SMTP accept. Since 6.2.28 Metrics listener /metrics and the health probes Raw sockets, one worker thread ManageSieve Sieve script management Raw sockets, a thread of its own SNMP agent Read-only, UDP 161 Since 6.3.4; off, and answering nobody until SnmpAllowedAddresses names a manager Background Scheduler, SMTPDeliveryManager, ExternalFetchManager Long-lived tasks on the main server queue State The database (accounts, domains, message metadata and settings, since 6.3.4 including the [Settings] keys), one file per message, and hMailServer.ini as the bootstrap The database stays small next to the store The handover that matters for capacity: an SMTP session never delivers anything. The final 250 is sent from a worker on the asynchronous task queue once the message is written, and delivery happens later on a delivery thread, so the accepting side and the delivering side run out separately. Diagnosing stalled mail takes each half in turn; How email works follows one message through both. The pools, their sizes, and what a full one looks like Seven work queues are created at startup, and the two HTTP listeners have workers of their own. The first column is the queue label of hmailserver_workqueue_depth{queue} on the metrics endpoint, which counts tasks accepted and not yet started; the last row is not a work queue and has no label. Queue Threads Sized by What runs on it When it is full IOCPQueue 15 tcpipthreads Every socket read, write and TLS handshake for SMTP, POP3 and IMAP New connections are accepted but nothing progresses; clients time out mid-command Asynchronous task queue 15 MaxNumberOfAsynchronousTasks Message finalization, which sends the final 250 Senders sit after CRLF.CRLF with no reply Name lookup queue 15 the same setting Blocking reverse-DNS lookups for live sessions Reverse names become "Unknown", so a dead reverse zone cannot stall acknowledgements SMTP delivery queue 10 maxdelivertythreads The delivery of one queued message The queue grows and mail is late; nothing is lost, since the sender already has its 250 External fetch queue 15 MaxNumberOfExternalFetchThreads One run per fetch account Fetch accounts fall behind their schedule Maintenance queue 5, fixed nothing One-shot scheduled tasks; a backup started over REST A slow sweep delays the other one-shot sweeps Server queue 4, fixed nothing Scheduler, IOService, SMTPDeliveryManager, ExternalFetchManager Cannot fill in normal operation: each task holds one slot for the life of the server HttpServer workers 4 per listener, fixed nothing One REST or web-services request each Requests queue behind the four; SMTP is unaffected Where the four sizes are set. On Windows, the Control Panel's Maintenance → Performance page, Threads tab, holds the COM Settings properties TCPIPThreads, MaxAsynchronousThreads and MaxDeliveryThreads, and the [Settings] key MaxNumberOfExternalFetchThreads. On either platform the first three are in the server settings group: the Control Deck's Settings view, PUT /api/v1/settings with tcpip_threads, max_asynchronous_threads and max_delivery_threads, or hmctl settings set. The fourth goes through /api/v1/settings/ini/{name} (administrator password only), hmctl ini set, or on Linux hmailserver --set-setting as root. Since 6.3.4 the database is the settings store, so do not edit the [Settings] line in hMailServer.ini: a line edited there is ignored in favour of the stored value. Settings: when a save takes effect covers the store, what becomes of the file's line (HM5804) and when each key applies. The tab also shows WorkerThreadPriority, read-only: the server stores that value and never reads it. When a new size takes effect. Since 6.3.4 a save applies most settings at once, but MaxNumberOfExternalFetchThreads waits for a reinitialise, because the queue's size is set when the fetch manager is built; GET /api/v1/server/settings/pending lists it until then. For the other three the documents leave a gap: the Control Panel reports a save of its COM settings as "applied immediately", while the Architecture page has the pools created when the servers start, and no document says when a changed count reaches a running pool. Until one does, treat all four alike: after saving, reinitialise (the Control Panel's Reinitialize or Apply now, POST /api/v1/server/reinitialize, or hmctl api POST /api/v1/server/reinitialize) or restart the service. A reinitialise stops and starts every service in place, ends the Control Deck's session, and ends a drain. # The REST listener on this machine: read the server settings group, # then give delivery 25 threads (or: hmctl settings set --set max_delivery_threads=25) curl -u Administrator:pw http://127.0.0.1:8443/api/v1/settings curl -u Administrator:pw -X PUT -H "Content-Type: application/json" \ -d '{"max_delivery_threads":25}' http://127.0.0.1:8443/api/v1/settings # The fetch pool is a [Settings] key; it waits for a reinitialise hmctl ini set MaxNumberOfExternalFetchThreads 20 hmctl api POST /api/v1/server/reinitialize Two asynchronous threads are kept back. Finalization runs the spam tests and event scripts, which wait on things outside the process, so AsyncQueueReservedThreads (2) keeps that many threads free of it: with the defaults at most 13 finalizations run at once. A task waiting for a slot holds no thread and is counted by hmailserver_workqueue_blocking_tasks_waiting{queue}; a count that stays above zero means something those tasks depend on is slow. On Windows the key is on Server limits & expert settings, card Timeouts and queue bounds; anywhere, hmctl ini set. Only two queues report a stall: the asynchronous and name lookup queues write the "every thread busy" report that Diagnosing stalled mail reads. The others are left out deliberately, since their tasks are long-lived by design, so a full IOCPQueue, SMTP delivery queue or External fetch queue shows only in hmailserver_workqueue_depth{queue} and in its symptoms. The database connections every pool shares The deliveries, the sign-ins and the rest of the server share one pool of database connections, NumberOfConnections in the [Database] section of hMailServer.ini, 5 as shipped. A wait for one longer than DBConnectionAcquireTimeout (60 seconds) fails. In hmailserver_db_connections{state}, available pinned at zero with busy at the pool size is saturation, and every mail path is then waiting out that timeout. Raise it with the delivery threads. The relay profile sets maxdelivertythreads to 25 and the sender profile to 50, and each tells you to raise NumberOfConnections to about the delivery thread count. The built-in database has one connection. On Windows, NumberOfConnections is forced to 1 for MSSQLCE. Before You Install calls the built-in engine adequate for anything up to a few dozen mailboxes, and its single connection the first thing to change if the server feels slow under concurrency. Linux has no built-in database; it uses PostgreSQL or MariaDB. Choosing a database covers the choice. It is a bootstrap key. [Database] stays in the file, never in the database, and its keys wait for a restart. On Windows the Performance page's Database tab edits it, on the server machine only. On Linux: sudoedit /etc/hmailserver/hMailServer.ini # [Database] NumberOfConnections=25 sudo systemctl restart hmailserver Connections and requests: the ceilings in front of the pools Where Limit As shipped Set on SMTP sessions MaxSMTPConnections 0, no cap Connections & protocols → Protocols, SMTP tab; max_smtp_connections IMAP sessions MaxIMAPConnections 0, no cap The IMAP tab; max_imap_connections POP3 sessions MaxPOP3Connections not given in the documents; GET /api/v1/settings shows it The POP3 tab; max_pop3_connections Recipients in one message 50,000 50,000 No setting is named; beyond it, 550 Too many recipients REST listener, with the webmail and the Deck 64 connections, closed at accept before any TLS handshake; 4 workers; 64 KB a request (16 MB, with 300 seconds, on the two attachment routes); 30 seconds a request; 300 seconds a connection fixed no key Web-services listener 128 connections; 4 workers; 64 KB a request; 15 seconds a request; 120 seconds a connection fixed no key The webmail shares the REST listener: its users, the Control Deck and every script that calls the API meet the same 64 connections and four workers. Its request rate is in the topic on the REST API. Outbound, since 6.3.4, MaxOutboundConnsPerDestination and MaxOutboundConnsPerTenant (both 0, off) cap sessions per destination and per sending domain; a tenant share larger than the delivery thread count is no share at all (Outbound shaping and IP warm-up). Scheduled work The Scheduler polls its recurring tasks once a minute; one-shot tasks go through the Maintenance queue at once. External fetch is not one of them: the ExternalFetchManager runs its own one-minute loop. Roadmap.md recorded in August 2026 that a due task runs on the scheduler's own thread, so one long task delays the others, and that the real period is the interval plus the run time. The tasks, grouped by cadence: Task Cadence Registered GreyListCleanerTask every GreylistingRecordExpirationInterval minutes (240 as shipped) when SMTP is on RemoveExpiredRecords, WorkQueueHealthTask every minute always; the second writes the stall report TlsRptReporterTask, DmarcRptReporterTask hourly always; each sends only when its from-address is set LogRetentionTask, MailboxRetentionTask start + 6 hours always ArchiveRetentionTask, IMAPExpungeRetentionTask start + 12 hours always MetricsHistoryTask start + every minute always UpdateCheckTask start + every 15 minutes always; does nothing until UpdateCheckEnabled=1 MessageStoreConsistencyTask, DiskSpaceMonitorTask start + hourly always BackupScheduleTask a one-minute tick against the wall clock only when a schedule is set DirectorySyncScheduleTask start + every [LDAP] SyncScheduleMinutes only when that is set AcmeRenewalTask start + hourly only when AcmeEnabled=1 Sizing the machine Memory. Before You Install asks for 2 GB as a minimum on Windows, more for a busy server. It is guidance; nothing enforces it, and memory per session has not been measured. Disk. Size it for how much mail you will keep, not how much you have today. The store is Data\ under the program folder on Windows and /var/lib/hmailserver on Linux, one file per message. For scale, the webmail bench's hundred thousand messages, 5 % of them with an attachment of 20 to 512 KB, are 1,323,987,827 bytes. Below MinimumFreeDiskSpaceMB (100) new mail is refused temporarily, 452 4.3.1 over SMTP and NO [UNAVAILABLE] to an IMAP APPEND, so senders retry; below DiskSpaceWarningThresholdMB (1024) the server logs a warning and keeps accepting. Antivirus. On Windows, exclude Data\, Database\ and Temp\ from real-time scanning. A scanner holding a message file open is a documented cause of delivery that silently does nothing; the server detects one shape of it and logs error 5760. Before You Install names no exclusions for Linux, where the store is /var/lib/hmailserver with its temp and database subdirectories. Durability. MessageStoreFsync (0 as shipped) flushes the spool file to disk before the 250, and a failed flush refuses the message. The published-numbers method runs with it on and labels the shipped default "not durable". Set it on Windows on Server limits & expert settings (card Message store durability), or anywhere with hmctl ini set. A folder of a hundred thousand messages in the webmail Every webmail path reads the folder's shared message collection, the one IMAP, POP3 and delivery use: one small object per message. Until 6.3.4 opening a folder copied the whole of it, under the lock IMAP sessions and delivery wait on, for every page of 200. Since 6.3.4: Operation What it does now What still grows with the folder Open, GET /api/v1/me/folders/{id}/messages One pass under the lock counts the folder and copies only the page of 200; each row's file head is read twice, not three times That pass; and the first open after a start, or after thirty idle minutes in the cache, reads every row of the folder from the database, as an IMAP SELECT does Scroll Older and Newer by before_uid; from three pages up, Oldest in one request (oldest_before_uid) Every page costs what the first costs, wherever it is in the folder: the one pass that counts the folder Search At most 2,000 message files and about two and a half seconds a request, then answers where to go on from; across every folder it walks the mailbox newest first and answers next_before_id; with IndexerFullText and message indexing on (off as shipped), a message the index rules out is passed without opening its file Performance.md does not say Select-all and one action The selection goes to POST /api/v1/me/folders/{id}/bulk: at most 1,000 messages a call for a flag, 500 for a move or a delete, nothing new taken on after about two and a half seconds A changed flag is a row written, a move is a file copied, and a selection narrowed to an inbox tab reads the head of every message Junk over a whole folder is a hundred thousand lessons. With SpamAssassin on, each message moved into or out of Junk queues one lesson, as the same move over IMAP does. In the Settings Reference the lesson on a move is SpamAssassinLearnOnMove (off as shipped), run as a task on the asynchronous work queue. A move holds the mail twice, briefly: original and copy, both counted against the quota, for no longer than one slice of the work. Not changed. The Storage, Subscriptions and Scheduled views still copy each folder they look at; emptying Junk or Trash copies the folder once per 500 messages removed; a quick step over a whole-folder selection still reads at most five thousand messages. For other callers of GET /api/v1/me/search, it now answers complete: false, more: true with next_before_id whenever it is not the end, and its hits come newest first across every folder, where they came folder by folder. Over IMAP, one SEARCH or SORT is bounded by IMAPSearchTimeout (60 seconds) and IMAPSearchMaxMegabytes (2048 MB examined). The webmail portal at /portal covers the rest of the client. What has not been measured The webmail at a hundred thousand. Performance.md has the method and a results table for the Windows bench (SQL Server Compact, 100,000 messages in the inbox, the full-text index off and on). Every cell reads to be measured on the bench; nothing was estimated. Its tools, build/perf/seed-folder.py and build/perf/webmail-timing.py, are in the repository and also run against a Linux server. Record the machine, database, message store, index state and build with any numbers. How much the server carries. PublishedNumbers.md defines messages in and out per second, latency percentiles, IMAP sessions per core with every operation's 95th percentile under 200 ms, memory per session and Dovecot's imaptest, measured against Postfix and Dovecot on the same machine under ten fairness rules. Its results say Not measured yet, and until they are filled the first line of Roadmap2.md is a goal, not a claim. The load generator, in the repository since 6.3.4, is build/perf/load/loadgen.py; a run in which the generator was busy for more than 85 per cent of it gives only a floor and is not published. The Windows bench gives hMailServer's own numbers only, not to be compared with the Linux table. python build/perf/load/loadgen.py --help python build/perf/load/loadgen.py self-test # proves the generator, says nothing about any server Size from the ceilings above, watch the queue depths and the database pool, and treat any throughput figure quoted elsewhere as unmeasured by the project. What it does not do No document gives a rule for sizing the pools to a machine; the only sized examples are the relay and sender profiles' 25 and 50 delivery threads. The main server queue, the maintenance queue and the HTTP listeners' workers, connection caps and deadlines are fixed; no key changes them. Where to read more Wiki: Architecture, Before You Install, Control Panel Reference section 7.8, Settings Reference, The REST API section 8, APIs Reference section 1 and Monitoring and Health. Documents: Performance.md, PublishedNumbers.md, SettingsApply.md, Profiles.md and OutboundShaping.md. The public documentation: Before you install, A database and Prometheus metrics and health probes. On this forum: Diagnosing stalled mail, Reading the logs, Monitoring and health, The webmail in depth, The Control Deck and hMailServer on Linux. Without a GitLab account, a bug or a question can go to the Service Desk address contact-project+hmailserver-hmailserver-86730559-issue-@incoming.gitlab.com, which GitLab files as a confidential issue; the replies arrive by email.
  • 0 Votes
    1 Posts
    154 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) is a security release that closes gaps in what this guide describes. Over COM an account given serveradmin with a list of domains was the server's administrator, its list aside, and one given domainadmin with a list that left out its own domain administered that domain's ordinary accounts; both are now confined as over REST, so before upgrading give serveradmin with no list to any account that is to administer the whole server over COM, a script's or a Control Panel's account among them. A credential confined to named domains is refused a domain's rename, a domain alias, a custom role's creation, change or deletion, a rule action that reaches the server's own addresses, a route's stored credentials or the event script, a relay host that is not public, and the server-wide metric history, over REST and COM alike; and a role is given or taken away only by a credential that takes in what the account holds now and afterwards, so manage_roles in named domains can no longer take a role from an account whose admin level is the server administrator's. A state-changing REST request that a browser reports as cross-site is refused. In the Control Panel, API & monitoring is Monitoring & alerts and REST API keys is under Sign-in & access; Ctrl+K takes every old title to its page. See the 6.3.7 announcement. This guide puts administrator roles in one place. Roles are new since 6.3.4 (schema 6047). The one thing to know first: an upgrade changes nobody's authority. The upgrade writes no assignment, and an account with no assignment holds exactly what its admin level has always given it. Nothing narrows until you give somebody a role. Permissions, roles and assignments A permission is one thing a credential may do, a role is a named set of permissions, and an assignment gives one account one role in named domains. An account holds at most one role. Role For Reaches when no domain is named The same as serveradmin Everything, including the secrets no other role reads Every domain The administrator password, admin level Server, an API key with Scope=full and no role domainadmin The accounts, aliases, lists and mailboxes of a domain The account's own domain Admin level Domain helpdesk Finding an account, resetting its password, unlocking it, and reading where its mail went, and nothing else The account's own domain Nothing before 6.3.4 auditor Reading everything the administration exposes, the audit trail included, and changing nothing Every domain An API key with Scope=readonly and no role The built-in roles cannot be edited or deleted. Their permissions are in the server's code, not the hm_roles table, so a change to the table cannot hand anybody a permission. The 27 permissions and who holds them A permission marked server-wide is reached only by a credential with no list of domains. The queue, the quarantine, the archive, the logs and the settings carry every domain's mail, so a role confined to named domains is refused them whatever it holds. Permission What it reaches serveradmin domainadmin helpdesk auditor administer_server Everything, secrets such as password hashes and private keys included; COM's "is this the server administrator?" yes administer_domain COM's "is this a domain administrator?" yes yes view_configuration Status, settings, listeners, certificates, rules, routes, anti-spam lists, the roles and who holds them, alert rules and events, bounce and complaint readings; never a secret yes yes yes view_accounts Domains, accounts, aliases, lists, groups (server-wide), fetch accounts yes yes yes yes view_mailbox Another account's folders and messages yes yes yes view_queue The delivery queue (server-wide) yes yes view_quarantine The quarantine (server-wide) yes yes yes view_archive The archive (server-wide) yes yes view_logs The logs (server-wide) yes yes view_message_trace The message trace; with view_configuration, the reports and a domain's suppression list yes yes yes yes view_audit The audit trail (server-wide) yes yes manage_accounts Creating, changing and deleting accounts, aliases, lists, groups, fetch accounts and app passwords; a domain's spam traps, DKIM rotation and suppression list; moving a mailbox (needs both domains) yes yes reset_password POST /api/v1/accounts/{address}/password, Application.ResetPassword yes yes yes unlock_account POST /api/v1/accounts/{address}/unlock, Application.UnlockAccount, Account.UnlockMailbox; clearing the logon-failure list (server-wide) yes yes yes manage_mailbox Another account's folders, messages and folder rights, a support session, a PST migration job (with manage_accounts) yes yes manage_queue Retrying or deleting a queued message (server-wide) yes manage_quarantine Releasing or deleting held mail (server-wide) yes yes manage_archive Holding and releasing archived messages (server-wide) yes manage_settings Every setting, listener, certificate, route, rule and anti-spam list, the alert rules (server-wide); the webmail's branding and file-link policy, a confined credential only its own domains' yes manage_domains Which domains exist (server-wide) yes manage_server Reinitialise, back up, reload scripts, clear caches, install an update, run a pass now (server-wide) yes manage_roles Roles and who holds them (server-wide) yes send_through_api Sending through POST /api/v1/send; only an API key reaches it yes provision SCIM 2.0 at /scim/v2, reads and writes; only an API key reaches it yes view_discovery eDiscovery's cases, searches, holds and exports yes yes manage_discovery Opening and closing a case, searching, legal holds, exports yes manage_inbound_webhooks Routing an address or a domain to a URL, as much as reading every message the route takes yes Four rules sit beside the table: The auditor reads mail. It holds view_mailbox; the help desk does not. The reports need two permissions. GET /api/v1/reports needs view_configuration and view_message_trace, because it says who mails whom. domainadmin and auditor read them; helpdesk is refused, and the Deck leaves the Reports view out for any role without both. The trace follows the role's reach. A confined help desk traces one whole address in a domain it reaches and sees only events naming that address exactly: [email protected] does not also show [email protected]. Clearing the logon-failure list is server-wide. hm_logon_failures records only the address a failure came from, with no account or domain, so a confined help desk is refused POST /api/v1/settings/logon-failures/clear, and Settings.ClearLogonFailureList answers the server administrator alone. The help desk unlocks one account with POST /api/v1/accounts/{address}/unlock instead; a password reset clears the lockout too. A ban already placed is an IP range under /api/v1/ipranges, which needs manage_settings. What only serveradmin has Reading a secret. No view_ permission covers a password hash or a private key: an auditor that could read one would be an offline attack away from being every role. Handing out authority. No custom role may hold administer_server, administer_domain or manage_roles, and a credential may never give a role holding a permission, or reaching a domain, that it does not hold itself. Taking over a wider account. A credential may not reset the password of, or change, an account whose whole authority it does not take in. A help desk is refused a domain administrator's or an auditor's password, even in its own domain, because otherwise it could sign in as them; a domain administrator is refused an auditor's. Where the server administrator's own mailbox is in a domain somebody else administers, that domain's administrator is refused all of these on it, over REST and COM. Over COM this is asked where the write is made, not where the object was reached: Account.Save, Account.Delete, Account.MoveTo, Application.ResetPassword, the app passwords and every write to what is in an account (FetchAccount.Save, Rule.Save, IMAPFolder.Save, Message.Save and the rest) ask that the credential reach the account and take in its authority. An event script's objects carry no credential and are left as they were. Roles.md lists every route and COM member that asks this. Which domains a role reaches Credential No domains named * A list An assignment of serveradmin or auditor Every domain Every domain Only those An assignment of any other role, custom ones included The account's own domain Every domain Only those An API key's Domains= line, whatever its role Every domain Matches no domain Only those A custom role holding a server-wide permission needs * before an account can use it at all. The listings answer every_domain over REST, and a fourth field over COM. A credential confined to named domains sees only the assignments of accounts in those domains. Giving a role, surface by surface Control Panel (Windows only): Accounts & domains → Administrator roles lists the roles and who holds them. Type an address, choose a role, optionally name the domains, and press Give the role; select a row and press Take the role away. It applies at once. Control Deck (Windows and Linux): the Roles view does the same. REST: giving or taking away a role needs manage_roles, which only serveradmin holds and which is server-wide: the administrator password, or a serveradmin credential reaching every domain. PUT /api/v1/role-assignments/[email protected] {"role":"helpdesk","domains":"example.test,example.org"} DELETE /api/v1/role-assignments/[email protected] GET /api/v1/role-assignments GET /api/v1/roles COM (Windows only): an empty role name takes the role away, and Account.RoleName, RoleDomains and RolePermissions read it back: $app = New-Object -ComObject 'hMailServer.Application' $account = $app.Authenticate('Administrator', 'your-admin-password') if (-not $account) { throw 'Authentication failed.' } $app.AssignRole('[email protected]', 'helpdesk', 'example.test') $helen = $app.Domains.ItemByName('example.test').Accounts.ItemByAddress('[email protected]') $helen.RoleName; $helen.RoleDomains; $helen.RolePermissions With a second factor on the administrator, Authenticate refuses and the script uses AuthenticateWithCode. Application.CurrentRole and CurrentPermissions say what a session holds. An API key: POST /api/v1/apikeys with "role":"helpdesk" writes a Role= line to hMailServerApiKeys.ini. The key store answers to the administrator password alone. The Deck's API keys view, signed in with that password, also makes a key with a role and its domains, showing the token once. curl -u Administrator:your-admin-password \ -H "Content-Type: application/json" \ -d '{"label":"help desk tool","role":"helpdesk","domains":"example.test"}' \ https://<host>:8045/api/v1/apikeys Use the address and port your listener answers on; bound to loopback with no certificate, it is http://127.0.0.1:8045 (https:// once a certificate, or an ACME one, is in place). A key's role decides what it reaches. The Control Panel's REST API keys page, as its reference describes it, names no role; give a key its role over REST or in the Deck. No API key signs in to the Deck. Custom roles are made over REST from a name, a description and a comma-separated permission list; both editors list them and give them out. A role that releases held mail holds server-wide permissions, so it is given with * and reaches every domain's held mail: POST /api/v1/roles {"name":"quarantine-desk","description":"releases held mail","permissions":"view_quarantine,manage_quarantine"} PUT /api/v1/role-assignments/[email protected] {"role":"quarantine-desk","domains":"*"} A permission the server does not know is dropped, not refused, so a custom role ends with less authority than it asked for, never more. A custom role somebody holds cannot be deleted: DELETE /api/v1/roles/{name} answers 409 saying how many accounts hold it, and the Deck's Delete shows that sentence. Give each holder another role or take this one away first; assignments are never removed for you, because an account with none falls back to its admin level. A built-in role answers 400, and COM cannot delete a role. An assignment decides even when its role holds nothing: holders of a custom role whose permissions name nothing this version knows are ordinary accounts, not their admin level. Where each role works Role Control Deck REST API Control Panel (COM) serveradmin Every view Every route Every page domainadmin Dashboard, DANE / TLSA, domains, fetch accounts, lists, roles and reports for its domains Its domains' accounts, aliases, lists and mailboxes, the status, who holds a role there Domains and Administrator roles helpdesk Domains, fetch accounts and lists, with Reset password and Unlock on every account The reset, the unlock, the account listings, the trace of an address in its domains Welcome and About only; Application.ResetPassword, UnlockAccount and GlobalObjects.MessageTrace answer a script auditor Every view that reads, the audit trail and reports included Every read, the audit trail and alerts included; every write refused Welcome, About and Administrator roles The help desk and the auditor work in the Deck. COM's settings objects answer only the server administrator, so an auditor reads the configuration through the Deck or REST. A domain administrator is not shown the settings, rules, routes, remote domains, certificates, ports, IP ranges, groups, backup, server messages, queue, logs or audit trail, which are server-wide. The quarantine is server-wide as well, so a domain administrator confined to its domain is refused it. Hiding a page is a courtesy, not the boundary. The server refuses the request whatever a page draws, and a Control Panel connected to a server that predates roles shows every page. A role holder signs in with its own address and password, and its second factor where it has one. An app password never carries the role: over REST it is an ordinary account's request, and COM's Application.Authenticate and AuthenticateWithCode treat it as a wrong password. A support session into a mailbox does not carry the person's role either. A role holder's own mail and settings under /api/v1/me stay its own. Setting up a help desk Turn the REST listener on if it is off. It needs RestApiPort set, RestApiBindAddress set to an address staff can reach (the default, 127.0.0.1, serves this machine alone), a certificate unless it is bound to loopback, and a name staff can reach. On Windows set them on the Control Panel's API & monitoring page (Monitoring & alerts since 6.3.7) and press Apply now, since they wait for a reinitialise. On Linux run sudo hmailserver --set-setting RestApiPort=8045 and the same for RestApiBindAddress and, for any address but loopback, RestApiCertificateFile and RestApiPrivateKeyFile (or let an ACME certificate stand in), then sudo systemctl restart hmailserver. The Control Deck topic has the details. Give each person a mailbox of their own; the role goes to that account. Decide the domains. With none named, helpdesk reaches only the account's own domain; name several, comma separated, for a desk serving more. Give the role on any surface above and check it with GET /api/v1/role-assignments or the Roles view. The person signs in to the Deck at https://<host>:8045/ as themselves, then uses Reset password or Unlock on an account. Tell them the limits: no mail reading, no reports, no domain administrator's or auditor's password, no removing a ban, and, when confined, no clearing of the logon-failure list. Each change the help desk makes is on the audit trail as its own, over COM and REST, with actor type account and a detail ending [role: helpdesk]. What a refusal says A permission the role lacks: 403 naming both, for example the role 'helpdesk' does not hold the permission 'manage_settings'. A domain it does not reach: the role 'helpdesk' does not reach that domain, whether or not the account exists, so nobody can list another domain's mailboxes one request at a time. Over COM, Application.ResetPassword and UnlockAccount answer No such account. for both. An account wider than the credential: that account holds authority this credential does not. Over COM, since 6.3.4 a refusal carries the server's sentence rather than a bare Exception from HRESULT: 0x800403E9. An API key on the key store, the [Settings] routes, the audit trail or the alerts: 401 with {"error":"authentication failed"}, the same as "not a key". The two stores only the administrator password opens The key store (/api/v1/apikeys) and the [Settings] section of hMailServer.ini (/api/v1/settings/ini) answer to the administrator password and nothing else: not an account holding serveradmin, not a key of any role or scope. A key that could mint keys could issue itself an unrestricted replacement, and the file holds the server's secrets. An account is refused 403 with the reason, a key 401. The audit trail (/api/v1/audit) and the alerts (/api/v1/alerts/...) are refused to every API key with the same 401, so a stolen key cannot read what it did or silence the alerting. An account whose role reaches every domain reaches them through view_audit, view_configuration, manage_settings or manage_server; a confined role is refused them. Upgrades, the audit trail and backups The upgrade. Schema step 6047 adds hm_roles, seeded with the four built-in rows, and hm_roleassignments, and writes no assignment. Roles are data, not settings: nothing goes into hm_settings or hMailServer.ini. The audit trail. Every role created, changed or deleted, and every role given or taken away, is a row naming the credential that did it; an assignment's row reads like role: helpdesk -> auditor; domains: the account's own domain -> every domain. The actor type follows the role: the administrator password and holders of serveradmin or domainadmin are administrator, every other account is account. Backup and restore. An account's assignment travels with it, by role name, and the custom roles travel whenever the domains or the settings do, restored first. A role the restoring server already has is kept as it is, with any difference named in the backup log. The admin level waits until the assignment is written, so a restore that stops part-way leaves no account wider than its role. An assignment naming a role neither side has is dropped, the level stays withheld, and HM6129 names the account. No archive carries the API key store. What it does not do No mapping from an identity provider. There is no claims mapping; an identity provider saying somebody is an administrator is not obeyed. The server's roles and oidc_administrators or saml_administrators decide. No role on an app password or a support session. No Control Panel or COM on Linux: roles are given there in the Deck and over REST. Where to read more Roles.md, the authority on all of this. Sign-in and Identity §7, The Control Deck, The REST API §2 and The COM API and Scripting §23.2. AlertsAndAuditTrail.md, Backup.md and Reports.md. On this forum: The Control Deck: browser administration at /, The REST API: turning it on, credentials and roles, The COM API on Windows, Hardening a public mail server, Alerts and the audit trail and IP ranges, auto-ban and lockout.
  • 0 Votes
    1 Posts
    117 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.