Repository navigation
Changes Since 6210
Every user-visible change from 6.2.11 to 6.2.28 by release and by area, the schema changes, the upgrading notes, what changes on upgrade without a switch, what was removed, the new INI keys with their defaults, and (sections 4a to 4e) what each release after 6.2.27 ships that no earlier one had - section 4e being 6.3.4 (27 September 2026, schema 6104), and the longest, since everything that landed after 6.3.3 is in it. File and line references point into the repository at the commit this page was written from (6.2.24, master of 4 September 2026); the 6.2.25, 6.2.26, 6.2.27 and 6.2.28 material was checked against master 40ae9491d on 8 September 2026 and its line numbers are from that tree, except for the last commits before the tag, which are cited from the tree v6.2.28 was itself cut from - there the HttpProxy line at IniFileSettings.cpp:492 moves everything below it down by one. Sections headed Unconfirmed or Contradictions record what could not be verified or where documents disagreed, and are left in on purpose. Brought up on 5 October 2026 to 6.3.6 (6 October 2026, schema 6108), for what 6.3.6 changes on this page, and on 18 October 2026 to 6.3.7, the latest release (17 October 2026, schema 6108), for what 6.3.7 changes on it: section 4h, the timeline and the schema. On 23 October 2026 the tables of what 6.3.6 and 6.3.7 fixed were given the CVE identifiers GitLab's CNA assigned after each release, sixteen of 6.3.6's (#62 to #77, published 8 October 2026) and twelve of 6.3.7's (#86 to #97, published 21 October 2026), in place of CVE requested. On 24 October 2026 the timeline's row for 6.3.7 was given the same identifiers for the fixes it names, as the CHANGELOG's 6.3.7 section gives them, with the cross-site request and the ACME challenge responder named as fixes of their own, and the Affected cells of both tables were given the versions hmailserver/docs/ForkAndOriginal.md gives with batch54 on master, 9336fdd59, where seven said earlier versions and CVE-2026-103010's did not say that Windows alone was affected.
The version-by-version summary is the repository's CHANGELOG.md: one section per release with its date, its schema and what it changed, 6.3.7's first, and an Unreleased section, empty when 6.3.7 was cut, for what comes after it. This page is the long form, with a source for each fact. What 6.3.4 added after master 540347500 - the schema steps 6097 to 6103, the batches 34 to 39 of section 4e and items 36 to 46 of section 5 - was read from 92b8f9f63 on 26 September 2026; what the tag v6.3.4 (51e2dbda0), the release, adds to that tree - step 6104, batch 40 in section 4e, and items 47 to 51 of section 5 - was read the same day from 8adce850a, the commit the tag was first made on, and again on 27 September 2026 from the tag itself, which adds the eleven fixes its release gate asked for (section 4e, The last batch).
Built 2026-09-04 from the GitHub release bodies (gh release view <tag>; the addresses of the project's former GitHub account, Progressiverobot, have answered 404 since GitHub suspended it on 18 September 2026, and the releases have been published again, notes and all, under their original dates at the project's GitLab releases) for every release after v6.2.10, Roadmap.md (the ✅ rows carrying a 2026 date), and the code at commit 4d5da3f (branch server-fixes-wave, tree identical to v6.2.24 for the files cited). Every setting name, default, port, keyword and command below was grepped in the tree; the (source: …) gives the file:line. Anything that could not be located in the code is under Unconfirmed at the end, not stated as fact.
Where an entry says a check runs in style.yml, in CI, or in another file under .github/workflows/, that is where it ran when the change landed. Those are GitHub Actions workflows: none has run for the project since GitHub suspended its account Progressiverobot on 18 September 2026, but sbom.yml and sign-release.yml, which, in the trees of the old tags, ran once on 18 October 2026 on the copies of the releases up to 6.2.22-pre6 in the project's repository on GitHub, GitLab's mirror since that day, and have been disabled there since (Assurance Case). Since 26 September 2026 GitLab CI runs their replacements: the checks, which a merge request's pipeline runs on GitLab's shared runners, and the builds and suites, which only the project's own runners run - bench150 and S1-WINBENCH, the two Windows benches, and linix, server2 and s1-linux, which share the tag linix (Contributing, The checkers; ContinuousIntegration.md).
Conventions: IniFileSettings.cpp = hmailserver/source/Server/Common/Application/IniFileSettings.cpp. IDL = hmailserver/source/Server/hMailServer/hMailServer.idl. DBScripts = hmailserver/source/DBScripts. "CP" = the WPF Control Panel at hmailserver/source/Tools/ControlPanel. "In Ctrl+K index" means the key appears in hmailserver/source/Tools/ControlPanel/Services/SettingsSearchIndex.g.cs.
| Tag | Published (UTC) | Kind | Schema after install | Regression gate | Headline |
|---|---|---|---|---|---|
| v6.2.11 | 2026-08-07 | stable | 6005 | 1026/1026 | Control Panel accessibility fix; suite runs in full for the first time (SpamAssassin + ClamAV installed) |
| v6.2.12 | 2026-08-10 | stable | 6005 | 1026/1026 | Setup tools to .NET 8 SDK-style; VB6 migration tools replaced by ImportTool; installer hardening |
| v6.2.13 | 2026-08-11 | stable | 6005 | 1026/1026 | Community bug fixes; SMTP/IMAP/SpamAssassin hardening; Win10 1607 floor |
| v6.2.14 | 2026-08-11 | stable | 6005 | 1026/1026 | 21 audit defects; Ctrl+K settings search; settings moved to the right pages |
| v6.2.15 | 2026-08-11 | stable | 6005 | 1040/1040 | IMAP sequence sets per RFC 3501; orphan-file fixes; restore no longer destroys data dir; four new CP pages |
| v6.2.16 | 2026-08-11 | stable | 6005 | 1040/1040 | One CP fix (GitHub issue 21, Ctrl+K palette error dialog) |
| v6.2.17 | 2026-08-12 | stable | 6005 | (not stated) | Root cause of GitHub discussion 18 (relayed mail stalling after 354): FinalizationTimeout, bounded SpamAssassin wait |
| v6.2.18 | 2026-08-12 | stable | 6005 (notes silent; 6.2.19 says "moves from 6005") | 1049 | DB-unavailable RCPT → 451; every pooled wait bounded; first /analyze pass; Roadmap rewritten |
| v6.2.19 | 2026-08-15 | stable, fresh installs broken | 6011 | (not stated) | Custom-DNS regression fix (GitHub issue 25); directory (AD) provisioning; Sieve imap4flags; INI settings into DB |
| v6.2.20 | 2026-08-15 | stable | 6011 | "1,490-test suite" mentioned | Fresh-install fix for 6.2.19; IMAP THREAD; installer never hangs under /SUPPRESSMSGBOXES |
| v6.2.21 | 2026-08-15 | stable (last stable before 6.2.24) | 6011 | (not stated) | Postfix QUIT-behind-dot hang fixed for real; dot transparency; Sieve body; Sieve trees follow renames; SORT hang |
| v6.2.22-pre1 | 2026-08-16 | pre-release | 6014 | "1,600-test" | ACME http-01 fix (GitHub issue 34); log flush (GitHub issue 33); M365 XOAUTH2 both directions; Sieve wave; eleven IMAP extensions |
| v6.2.22-pre2 | 2026-08-18 | pre-release (build 27) | 6015 | 1650/1650 | DMARC rua reports; per-account lockout; POP3 AUTH-RESP-CODE/PIPELINING; AVFailAction; IMAP BINARY, OBJECTID |
| v6.2.22-pre3 | 2026-08-19 | pre-release (build 28) | 6019 | 1700/1700 | App passwords; per-account TOTP; password policy/expiry/history; quarantine; POP3 EXPIRE/LOGIN-DELAY; ES256 |
| v6.2.22-pre4 | (withdrawn) | tag only, no release | 6020 | — | Installer could not create its own DB (SQL CE); tag v6.2.22-pre4 still exists, do not build from it |
| v6.2.22-pre5 | (superseded) | tag only, no release | 6022 | — | Same build as pre6 but published without SBOM/signature assets; re-cut as pre6 |
| v6.2.22-pre6 | 2026-08-20 | pre-release (build 30, "Pre-Alpha") | 6022 | 1721 | DMARCbis tree walk + np=; rsa-sha1 refused; SPF void limit; ACME ARI; RCPT-time over-quota; quota warnings; per-domain relay; message trace; archive retention; per-domain metrics |
| v6.2.23-alpha1 | 2026-08-21 | pre-release (build 32) | 6025 | 1831/1831 | Shared mailboxes; full-text index; RFC 3464 DSNs; BINARYMIME; PROXY/XCLIENT; filter hook; blocked senders; per-account spam; DL moderation; domain OOO; Event Log; OTLP metrics/logs; IPv6 listeners; disk floor; CP overhaul |
| v6.2.23-alpha2 | 2026-09-04 | pre-release | 6025 | 1838/1838 | Thunderbird UTF8 (~{n} APPEND (GitHub issue 53); two anti-spam bypasses ported from upstream; STARTTLS re-EHLO 503; CFG; signed tags |
| v6.2.24 | 2026-09-04 | stable | 6025 | 1838/1838 | Code of alpha2 plus NuGet lock files; first stable since 6.2.21 |
| v6.2.25 | 2026-09-06 | stable | 6030 | 2001 run, 1994 passed, 7 explicit skips (same on the assertion build) | ACME issuance/renewal crash (GitHub issue 93); external accounts over IMAP; message retention; archive index + legal hold; metric history; SASL EXTERNAL; scrypt + work factor; tarpits; server-enforced administrator 2FA; REST reaches COM-only objects; outbound PIPELINING/CHUNKING; seventeen foreign keys; OpenSSL 4.0.2 + Boost 1.92.0; C++ CodeQL runs on a hosted runner |
| v6.2.26 | 2026-09-06 | stable | 6030 | 2004 run, 1997 passed, 7 skips | One fix: SQL Server Compact upgrades through 6030 reported as failed after succeeding, and the probe crashed the provider (GitHub issue 114) |
| v6.2.27 | 2026-09-07 | stable | 6031 | 2024 run, 2017 passed, 7 skips | DNSSEC: a missing DS is proved missing (NSEC/NSEC3); FetchAccount.MirrorFolders; Import Tool reads Maildir; docs/Migration.md |
| v6.2.28 | 2026-09-08 | stable | 6031 (unchanged) | 2127 run, 2119 passed, 8 skips | Live update (check, Sigstore verify, apply with rollback) and HttpProxy; self-service portal /portal and /api/v1/me; REST API and web services on HttpServer; IMAP COMPRESS=DEFLATE; Control Panel in 17 languages; the Control Deck's four read-only views |
| v6.3.0 | 2026-09-10 | stable | 6031 (unchanged) | 2175 run, 2166 passed, 9 skips | Linux on x86-64 and AArch64: .deb, .rpm and AppImage packages, and the regression suite on a Linux server (756 passed, 649 skipped of 1,405); the /Debug console server; the webmail's first waves |
| v6.3.1 | 2026-09-11 | stable | 6031 (unchanged) | 2175 run, 2166 passed, 9 skips | The installer is Authenticode-signed (Progressive Robot Ltd, countersigned); nine release-path fixes found by executing the release workflows against the live asset list |
| v6.3.2 | 2026-09-13 | stable | 6038 | 2,219 tests, 2,211 passed, 0 failed, 8 skipped | The webmail's fifteen waves (address book, identities and signature, Archive/Junk/Trash, search, labels, Security/Away/Storage/Files pages, templates, receipts, one-click unsubscribe, send later and snooze, the editor and the app install, branding, files as links, twenty languages, S/MIME); SASL GSSAPI on Windows; an auto-ban that reaches the firewall; a SURBL expected result; static Boost on Linux and a container image; thirty-seven binaries out of git; the 6029-6030 sweep fix with an upgrade gate; the MySQL create-script fix; the refused-database COM guard |
| v6.3.3 | 2026-09-15 | stable | 6040 | 2,302 tests, 2,294 passed, 0 failed, 8 skipped | The webmail rebuilt to the shape of Gmail and Outlook.com (reading pane, tabs, mute, pin, block, sweep, drag-and-drop, docked compose, follow-up flags, nudges, quick steps, eighteen more search operators); CardDAV; the Linux regression suite's whole route backlog and the browser Control Deck at parity with the Control Panel; an outbound BDAT chunk larger than one buffer no longer stalls (GitHub issue 261); an upgrade from a schema older than 6038 works again (GitHub issue 263); PostgreSQL stores a backslash as one; the live update from an open Control Panel; the REST ini routes answer to the administrator password only |
| v6.3.4 | 2026-09-27 | stable; Authenticode-signed with no Sigstore bundle, so installed by hand; its SHA-256 sums file signed with the release tags' SSH key, and an SBOM beside each installer and package; Linux packages x86-64 only (.deb, .rpm) |
6104 | 3,811 tests ([Test] in hmailserver/test/RegressionTests at the tag, 51e2dbda0), all run by the release gate on the tag in two halves on two benches, 0 failed; batches 34 to 39 were each gated in two halves on two benches before they landed on master |
CalDAV and an iCalendar module; the webmail's next twelve and its three right-to-left languages; the Control Deck in twenty languages; the desktop Control Panel on one design system; every property the Control Panel writes writable over REST; winget and choco; hmctl and a PowerShell module; a Helm chart and a Compose file; nightly fuzzing; the first coverage measurement; the database as the settings store, with [Settings] in hMailServer.ini its copy; an audit trail and alerts; a domain's external-sender tag, first-contact note and disclaimer; remote-domain policies (TLS both ways, limits, a backup MX's recipient callout); a backup that carries an account's own stores; SPF per RFC 7208 through the server's own resolver; tutorial tours; hmctl config export, diff and apply; syslog; per-domain reports; the Control Deck redrawn on the webmail's design; then sender lists and digests, roles, DKIM rotation, bounces, webhooks, country blocking, sessions and devices, Web Push, complaints, mailbox move, the reputation watch, spam traps and an encrypted backup; and in batches 12 to 33 (section 4e): calendars, tasks, invitations and free-busy in the webmail, a quota policy, mail flow rules and data loss prevention, compromised-account detection, greylisting hardened and SMTP policy, journaling, account recovery, passkeys, OpenID Connect and SAML, the directory and shared address books, OpenPGP in the page and zero-access mailboxes, restricted delivery, message recall, impersonation protection, a settings store whose saves apply, three levels, the environment and profiles, outbound shaping and IP warm-up, a send API, migrations, a sandbox for attachments, a list manager, gateway encryption, an S/MIME authority and SCIM; in batches 34 to 39 (section 4e): eDiscovery, storage compression and attachment de-duplication, mailbox encryption at rest, mail in to a webhook, rooms and equipment, MySQL and MariaDB in utf8mb4, secrets at rest, the JScript escaping fix, event scripts on Linux in QuickJS, PST import, SNMP, secure defaults and a low-privilege service account for new Windows installations, database TLS, object storage, the update feed off GitHub, the IMAP compliance fixes, the webmail at 100,000 messages (built, not yet measured), and CI defined on the project's own GitLab runners; and in batch 40 (section 4e): the audit before the release and what it fixed, the fixes of GitLab's SAST, secrets refused before they are pushed and a release held by its scans, an SBOM for every compiled file, SHA-256 sums signed with the release tags' SSH key, the installer's HM_ADMIN_PASSWORD, stable download links, schema 6104, and the eleven fixes the release gate asked for |
| v6.3.5 | 2026-09-29 | stable; the first release made by its tag's GitLab pipeline, signed with Sigstore at the path the project began at, which a 6.3.4 server trusts; Linux packages x86-64 only (.deb, .rpm) |
6105 | the whole Windows suite on the project's Windows runner before the installer was built (section 4f) | Reviewing the quarantine (the HELO column, the grouping); 137 settings given an editor in the Control Panel; the sidebar in the order a new administrator needs (section 4f) |
| v6.3.6 | 2026-10-06 | stable; the first release signed at the group's path, with the project's id in the certificate, and the first that carries every file its pipeline makes - sixty-two, the ARM64 Windows installer (a preview) among them; the last release planned by date | 6108 | section 4g | The security fixes of the project's own reviews (section 4g), OpenSSL 4.0.3 in the Windows installers, the AArch64 packages and both AppImages again, each supported distribution release's own package |
| v6.3.7 | 2026-10-17 (23:52) |
stable, latest; the first security release since releases paused, cut from master with the finished work it held; signed at the group's path as 6.3.6 was; the same sixty-two files |
6108 (no step) | On the tag's pipeline: windows-gate on bench150, 2,794 tests, 2,732 passed, 0 failed, 62 skipped, and windows-gate-s1vm on S1-WINBENCH, 1,652 tests, 1,634 passed, 0 failed, 18 skipped, the two halves of the Windows suite, at once; linux-suite, 2,652 tests, 2,284 passed, 0 failed, 368 skipped; the signed installer's smoke test, an upgrade from 6.3.6 98 checks and a new installation 64, none failed | The fixes of section 4h: the program folder at a drive's root (CVE-2026-107571), mail sent, and signed, in the name of a hosted domain (CVE-2026-107567, CVE-2026-107568, CVE-2026-107566, CVE-2026-114478, CVE-2026-114474), the rule loop limit (CVE-2026-107569), what a credential confined to named domains may change (CVE-2026-114489, CVE-2026-114490, CVE-2026-114476, CVE-2026-114475, CVE-2026-114477), a REST request a browser made from another site (CVE-2026-114470), the ACME challenge responder (CVE-2026-114472), the anti-virus strip action (CVE-2026-114479), SRS (CVE-2026-114473) and the SMTP log (CVE-2026-114471); the .NET Desktop Runtime 10.0.13 in both Windows installers and MariaDB Connector/C 3.4.11 in the x64 one, the ARM64 installer carrying no MySQL or MariaDB client; the Control Panel's corrections and regrouped sidebar; IMAP and POP3 behind a PROXY header; a link's host read by UTS 46 |
- There is no stable 6.2.22 and no stable 6.2.23; both were pre-release-only lines (source:
gh release list). - Only pre1, pre2, pre3 and pre6 of the 6.2.22 line have releases; pre4 and pre5 exist only as tags (source:
gh release list;git tag -l "v6.2.2*"). - 6.3.7's stamp:
HMAILSERVER_VERSION "6.3.7", build 46 (6.3.6 was build 45), andREQUIRED_DB_VERSION 6108, as in 6.3.6 (source: hmailserver/source/Server/Common/Application/Version.h:6-8 and Constants.h:1291 at the tag v6.3.7,31d656c0c); the release is v6.3.7, published on 17 October 2026 at 23:52 UTC. - 6.3.4's stamp:
HMAILSERVER_VERSION "6.3.4", build 43 (6.3.3 was build 42) (source: hmailserver/source/Server/Common/Application/Version.h:6-8 at the tag,51e2dbda0); the installer ishMailServer-6.3.4-x64.exe(source: hmailserver/installation/section_setup_64.iss:2). The Linux packages of 6.3.4 arehmailserver_6.3.4_amd64.debandhmailserver-6.3.4-1.x86_64.rpmonly: there is no AArch64 package and no AppImage in 6.3.4, so those installations stayed on 6.3.3's or built from source until 6.3.6, which carries the AArch64 packages and both AppImages again (section 4g). -
REQUIRED_DB_VERSION 6040in 6.3.3, and 6104 in 6.3.4 - 6041 for CalDAV, 6042 to 6104 after it, sixty-four steps in all (source: hmailserver/source/Server/Common/Application/Constants.h:1224 at the tag,51e2dbda0; it was 6103 at92b8f9f63and 6096 at master540347500); a fresh install of 6.3.3 is stamped 6040 and one of 6.3.4 6104 (source: DBScripts/CreateTablesMSSQL.sql:3301, CreateTablesMySQL.sql:3166, CreateTablesPGSQL.sql:2968 at the tag); 6.2.28, 6.3.0 and 6.3.1 were at 6031 and 6.3.2 at 6038. Everything after v6.2.27 (47 commits,git log v6.2.27..v6.2.28) is what v6.2.28 ships — see section 4a. - Published (UTC): v6.2.25 2026-09-06T10:00Z, v6.2.26 2026-09-06T19:49Z, v6.2.27 2026-09-07T01:12Z, v6.2.28 2026-09-08 (source:
gh release view). - Tag dates (local, +0100) for placing dated Roadmap rows: v6.2.18 2026-08-12 06:24; v6.2.19 2026-08-15 08:46; v6.2.20 2026-08-15 14:10; v6.2.21 2026-08-16 01:27; pre1 2026-08-16 23:33; pre2 2026-08-18 02:48; pre3 2026-08-20 00:43; pre6 2026-08-21 00:02; alpha1 2026-08-21 20:26; alpha2 2026-09-04 18:44 (source:
git for-each-ref refs/tags).
Read left to right. The number under each release is the schema it leaves the database on; a release with no number changed nothing. Everything in a grey box shipped in one day — this project's cadence is deliberately many small releases rather than few large ones, and the reason is Release Process's first standing rule.
flowchart LR
subgraph aug1["7-12 August 2026 - schema 6005 throughout"]
direction LR
V11["6.2.11"] --> V12["6.2.12"] --> V13["6.2.13"] --> V14["6.2.14"] --> V15["6.2.15"] --> V16["6.2.16"] --> V17["6.2.17"] --> V18["6.2.18"]
end
subgraph aug2["15-16 August 2026"]
direction LR
V19["6.2.19<br/>6011<br/>fresh installs broken"] --> V20["6.2.20<br/>6011<br/>fresh-install fix"] --> V21["6.2.21<br/>6011"]
end
subgraph aug3["16-21 August 2026 - the 6.2.22 pre-release line"]
direction LR
P1["pre1<br/>6014"] --> P2["pre2<br/>6015"] --> P3["pre3<br/>6019"] --> P4["pre4<br/>6020<br/>WITHDRAWN"] --> P5["pre5<br/>6022<br/>superseded"] --> P6["pre6<br/>6022"]
end
subgraph aug4["21 August - 4 September 2026"]
direction LR
A1["6.2.23-alpha1<br/>6025"] --> A2["6.2.23-alpha2<br/>6025"] --> V24["6.2.24<br/>6025<br/>first stable since 6.2.21"]
end
subgraph sep["6-8 September 2026"]
direction LR
V25["6.2.25<br/>6030"] --> V26["6.2.26<br/>6030<br/>one fix"] --> V27["6.2.27<br/>6031"] --> V28["6.2.28<br/>6031"]
end
subgraph sep2["10-15 September 2026"]
direction LR
V30["6.3.0<br/>6031<br/>Linux"] --> V31["6.3.1<br/>6031<br/>signed"] --> V32["6.3.2<br/>6038"] --> V33["6.3.3<br/>6040"]
end
subgraph sep3["27 September 2026"]
V34["6.3.4<br/>6104<br/>installed by hand"]
end
subgraph oct["29 September - 17 October 2026"]
direction LR
V35["6.3.5<br/>6105<br/>first made by CI"] --> V36["6.3.6<br/>6108"] --> V37["6.3.7<br/>6108<br/>LATEST<br/>security release"]
end
V33 --> V34
V34 --> V35
V28 --> V30
V18 --> V19
V21 --> P1
P6 --> A1
V24 --> V25
There is no stable 6.2.22 and no stable 6.2.23 — both were pre-release-only lines.
v6.2.22-pre4 and -pre5 exist as tags with no release behind them; do not build from
either.
The gate is the full suite run on the exact binary being shipped, and its size is the single best proxy for how much of this server is now covered by a test that talks to it over a real socket.
| Release | Tests run | Result as published |
|---|---|---|
| 6.2.11 – 6.2.14 | 1,026 | all passing |
| 6.2.15 – 6.2.16 | 1,040 | all passing |
| 6.2.18 | 1,049 | all passing |
| 6.2.20 | ~1,490 | "1,490-test suite" |
| 6.2.22-pre2 | 1,650 | 1650/1650 |
| 6.2.22-pre3 | 1,700 | 1700/1700 |
| 6.2.22-pre6 | 1,721 | as published |
| 6.2.23-alpha1 | 1,831 | 1831/1831 |
| 6.2.23-alpha2 / 6.2.24 | 1,838 | 1838/1838 |
| 6.2.25 | 2,001 | 1,994 passed, 7 explicit skips — same on the assertion build |
| 6.2.26 | 2,004 | 1,997 passed, 7 skips |
| 6.2.27 | 2,024 | 2,017 passed, 7 skips |
| 6.2.28 | 2,127 | 2,119 passed, 0 failed, 8 skips |
| 6.3.0 – 6.3.1 | 2,175 | 2,166 passed, 9 skips |
| 6.3.2 | 2,219 | 2,211 passed, 0 failed, 8 skipped |
| 6.3.3 | 2,302 | 2,294 passed, 0 failed, 8 skipped |
(master at 3233b0cc4, 15 September) |
2,410 | 2,402 passed, 0 failed, 8 skipped; Linux on 00bec4412, the same server: 1,606 tests, 1,326 passed, 0 failed, 280 skipped |
(master at batch 11, a32bfd93b, 16 September) |
2,791 | split across two benches for the first time: 1,704 on this project's bench (1,703 passed, 1 skipped) and 1,087 on the second (1,085 passed; the two failures were in how a test read the server, corrected and re-run) |
| (master at batch 33, 24 September) | 3,390 | in two halves: 2,076 on this project's bench, 2,075 passed, 0 failed, 1 skipped; 1,314 on the second bench, 1,311 passed, 0 failed, 3 inconclusive. The Linux suite last ran on GitHub Actions on the tree of 17 September: 1,725 results, 1,439 passed, 0 failed, 286 skipped |
| 6.3.4 | 3,811 |
[Test] attributes at the tag, 51e2dbda0 (3,698 at 92b8f9f63), all run by the release gate on the tag in two halves: 2,349 on this project's bench, 2,252 passed, 0 failed, 97 skipped, and 1,462 on the second, 1,446 passed, 0 failed, 16 skipped, with the 71 live-update tests run again, elevated, on the second bench, all passed; the Linux suite on the tag's packaged binary, run by hand on the project's Linux machine: 2,013 tests, 1,562 passed, 0 failed, 451 skipped. The gate's first run, on 8adce850a (3,809 tests), failed 6 on this bench and 11 on the second, and the Linux suite in a cascade of 474 from its own test shim; eleven fixes followed before the tag (section 4e, batch 40). Each of batches 34 to 39 was gated in two halves on the two benches before it landed on master, and the Linux suite is the linux-suite job of the project's own .gitlab-ci.yml, whose first run, in the first pipeline of 26 September 2026 (master 40c7d3e0c), failed on faults of the tree's that 6.3.4 fixes; that job has not run on the fixed tree yet (the one pipeline to have run on it, the schedule of 27 September 2026 on master 6bc09509d, ran the update feed's pages and package-repos jobs alone), and the batches' Linux suites were run by hand on that machine |
The eighth skip at 6.2.28 is one ignored test where STARTTLS is not offered on the plain
IMAP port of this particular bench; the other seven are the explicit stress skips. The
eight skipped at 3233b0cc4 are seven explicit stress tests and one ignored test. The three inconclusive results at batch 33 were GnuPG proofs whose fixtures handed Git for Windows' gpg a home directory it could not read; the fixtures were fixed the same day (fd9d43e66). The Linux suite ran on GitHub Actions, where nothing has run it since 18 September 2026; in 6.3.4 it is defined as a job on the project's own Linux runner (ContinuousIntegration.md).
Schema was 6005 from 6.2.10 through 6.2.18 (source: 6.2.11–6.2.17 release notes each say "schema version 6005"). All steps below have MSSQL, MSSQLCE, MySQL and PGSQL variants in DBScripts/Upgrade<from>to<to><backend>.sql.
| Step | First shipped in | What it does (MSSQL script content) |
|---|---|---|
| 6005→6006 | 6.2.19 |
hm_imapfolders.folderspecialuse (source: DBScripts/Upgrade6005to6006MSSQL.sql) |
| 6006→6007 | 6.2.19 |
hm_domains.domaindkimsecondaryselector, domaindkimsecondaryprivatekeyfile (DKIM key rotation) (source: Upgrade6006to6007MSSQL.sql) |
| 6007→6008 | 6.2.19 |
hm_tcpipports.portclientcertificatepolicy, portclientcertificatecafile (source: Upgrade6007to6008MSSQL.sql) |
| 6008→6009 | 6.2.19 |
hm_sslcertificates.sslprivatekeypassword (source: Upgrade6008to6009MSSQL.sql) |
| 6009→6010 | 6.2.19 |
hm_settings rows ASArcFilteringEnabled, ASArcTrustedSealers (source: Upgrade6009to6010MSSQL.sql) |
| 6010→6011 | 6.2.19 | new table hm_inisettings (INI [Settings] mirrored into the DB) (source: Upgrade6010to6011MSSQL.sql) |
| 6011→6012 | 6.2.22-pre1 |
hm_rule_criterias.criteriamatchvalue widened to nvarchar(2000); hm_accounts.accountvacationbegindate (source: Upgrade6011to6012MSSQL.sql) |
| 6012→6013 | pre1 |
hm_messages.messagesavedate (IMAP SAVEDATE) (source: Upgrade6012to6013MSSQL.sql) |
| 6013→6014 | pre1 | new table hm_imap_metadata (IMAP METADATA) (source: Upgrade6013to6014MSSQL.sql) |
| 6014→6015 | pre2 |
hm_messages.messageemailid (IMAP OBJECTID EMAILID) (source: Upgrade6014to6015MSSQL.sql) |
| 6015→6016 | pre3 | new table hm_apppasswords (source: Upgrade6015to6016MSSQL.sql) |
| 6016→6017 | pre3 |
hm_accounts.accounttotpsecret (per-account TOTP) (source: Upgrade6016to6017MSSQL.sql) |
| 6017→6018 | pre3 | new table hm_quarantine (source: Upgrade6017to6018MSSQL.sql) |
| 6018→6019 | pre3 |
hm_accounts.accountpasswordchanged; new table hm_passwordhistory (source: Upgrade6018to6019MSSQL.sql) |
| 6019→6020 | pre4 (withdrawn) / pre6 | new table hm_messagetrace (source: Upgrade6019to6020MSSQL.sql) |
| 6020→6021 | pre6 | six hm_domains.domainrelay* columns (per-domain outbound relay) (source: Upgrade6020to6021MSSQL.sql) |
| 6021→6022 | pre6 |
hm_servermessages rows QUOTA_WARNING, QUOTA_WARNING_SUBJECT (source: Upgrade6021to6022MSSQL.sql) |
| 6022→6023 | 6.2.23-alpha1 | new tables hm_messageindexterms, hm_messageindexstate (full-text index; empty until enabled) (source: Upgrade6022to6023MSSQL.sql) |
| 6023→6024 | alpha1 | six hm_domains.domainvacation* columns; new table hm_blocked_senders (source: Upgrade6023to6024MSSQL.sql) |
| 6024→6025 | alpha1 |
hm_messages.messageflags tinyint → smallint (table rewrite on MSSQL/SQLCE/MySQL; PostgreSQL already smallint); hm_accounts.accountantispamenabled (default 1), accountspammarkthreshold (-1), accountspamdeletethreshold (-1); hm_distributionlists.distributionlistmoderatoraddress, distributionlistbounceaddress (source: Upgrade6024to6025MSSQL.sql) |
| 6025→6026 | 6.2.25 |
hm_settings row CreateDefaultSpecialUseFolders (0) (source: Upgrade6025to6026MSSQL.sql) |
| 6026→6027 | 6.2.25 |
hm_domains.domainmessageretentiondays, hm_accounts.accountmessageretentiondays, both default 0 = no policy (source: Upgrade6026to6027MSSQL.sql) |
| 6027→6028 | 6.2.25 | new table hm_metricsamples (one row per metric per minute; empty until the sampler runs) (source: Upgrade6027to6028MSSQL.sql) |
| 6028→6029 | 6.2.25 | new table hm_archiveindex (only copies made from now on are indexed) (source: Upgrade6028to6029MSSQL.sql) |
| 6029→6030 | 6.2.25 |
seventeen FOREIGN KEY ... ON DELETE CASCADE constraints (accounts/aliases/domain aliases/lists → domain, list recipients → list, route addresses → route, fetch accounts/app passwords/password history/index state → account, fetch UIDs → fetch account, rule criteria/actions → rule, group members → group, message recipients/metadata/index terms → message, expunged marks → folder); orphan rows deleted first, children before parents; MySQL gets ENGINE=InnoDB on every table involved. Reads every child table once — plan for it like an index build (source: Upgrade6029to6030MSSQL.sql; v6.2.25 body) |
| 6030→6031 | 6.2.27 |
hm_fetchaccounts.famirrorfolders tinyint default 0 (source: Upgrade6030to6031MSSQL.sql) |
| 6031→6032 | 6.3.2 | new table hm_contacts (the webmail's address book, one row per account and address, unique on both) (source: Upgrade6031to6032MSSQL.sql) |
| 6032→6033 | 6.3.2 | new table hm_accountprefs (the account's own preferences: theme, density, notifications, language, templates, label colours) (source: Upgrade6032to6033MSSQL.sql) |
| 6033→6034 | 6.3.2 | new table hm_scheduled (a draft to send at a time, a message snoozed until a time) (source: Upgrade6033to6034MSSQL.sql) |
| 6034→6035 | 6.3.2 | new table hm_files (files sent as links: the chunked store, its tokens, passwords and expiry) (source: Upgrade6034to6035MSSQL.sql) |
| 6035→6036 | 6.3.2 |
hm_messages.messagekeywords nvarchar(500) default '' (IMAP keywords, the webmail's labels) (source: Upgrade6035to6036MSSQL.sql) |
| 6036→6037 | 6.3.2 | new table hm_smimekeys (the account's own certificates with their chains and private keys wrapped in the browser, and the correspondents' certificates) (source: Upgrade6036to6037MSSQL.sql) |
| 6037→6038 | 6.3.2 |
hm_surblservers.surblresult nvarchar(255) default '' (a SURBL server's expected result) (source: Upgrade6037to6038MSSQL.sql) |
| 6038→6039 | 6.3.3 |
hm_domains.domainrelaypassword nvarchar(1024) (a DPAPI envelope is 314 characters; the column was 255, so a domain's relay password could not be saved) (source: Upgrade6038to6039MSSQL.sql) |
| 6039→6040 | 6.3.3 |
hm_contacts.contacturi, contactuid nvarchar(255) default '' and contactvcard ntext default '' (the card itself, as a CardDAV client sent it; mediumtext on MySQL) (source: Upgrade6039to6040MSSQL.sql) |
| 6040→6041 | 6.3.4 |
hm_calendars (calendar id, account, name, display name, sync token, created) and hm_calendarobjects (object id, account, calendar, URI, UID, component, the iCalendar data, ETag, sync token, the span it occupies, first and last instance, a deleted flag and a modified stamp), each with a foreign key cascading from its parent and a unique index on the resource name; CalDAV; both tables utf8mb4 on MySQL (source: Upgrade6040to6041MSSQL.sql; Upgrade6040to6041MySQL.sql:9, 34) |
| 6041→6042 | 6.3.4 |
No DDL. Each of the four dialect scripts is update hm_dbversion set value = 6042 and nothing else: the version is the change. From 6042 hm_inisettings, unchanged since 6011, is the store of the 238 [Settings] keys and the section in hMailServer.ini is its copy (section 4e); the step exists so that a server which would still let the file win refuses this database rather than running on it. No SchemaVerification probe, since no script carries [IGNORE-ERRORS] (source: Upgrade6041to6042MSSQL.sql, MSSQLCE, MySQL, PGSQL; Common/Application/IniSettingStore.cpp:272-496) |
| 6042→6043 | 6.3.4 | new tables hm_audit (time, actor, actor type, interface, peer address, object type, object name, action, detail, and the previous row's hash beside its own), hm_alertrules (one row per condition, unique on it: enabled, threshold, mail and/or webhook, address, webhook URL, secret, cool-down, digest) and hm_alertevents (condition, severity, state, time, summary, detail, notified, digested, and the webhook's state, attempts and next attempt); six rules seeded - backup.failed and certificate.expiring (threshold 7 days) on, disk.low, queue.stalled, autoban.storm (25) and minidump.written off; nine hm_settings rows AlertsEnabled 1, AlertRecipient '', AlertSenderAddress '', AlertDigestEnabled 1, AlertDigestHour 7, AlertMaxPerHour 20, AlertWebhookMaxAttempts 5, AuditTrailEnabled 1, AuditRetentionDays 0, each inserted only where the row is not already there; 29 probes (source: Upgrade6042to6043MSSQL.sql; Tools/DBUpdater/SchemaVerification.cs) |
| 6043→6044 | 6.3.4 | seven hm_domains columns - domainexternaltagsubject, domainexternaltagheader, domainfirstcontacttip, domaindisclaimerenabled (int, default 0), domainexternaltagtext nvarchar(100) default '', domaindisclaimerplaintext and domaindisclaimerhtml (ntext, default '') - and new table hm_knownsenders (account, sender address, count, first seen, last seen), unique on account and address, cascading from hm_accounts: the first-contact note's memory; the address column is utf8_bin on MySQL; 12 probes (source: Upgrade6043to6044MSSQL.sql; Upgrade6043to6044MySQL.sql:19) |
| 6044→6045 | 6.3.4 | new table hm_remotedomainpolicies (domain name or pattern, description, active, outbound TLS level, inbound TLS, largest message in KB, connections, messages a minute, replies allowed, forwarding allowed, and the recipient callout's switch, host, port, timeout, cache minutes and rate), indexed on the domain name, with no foreign key; 16 probes (source: Upgrade6044to6045MSSQL.sql) |
| 6045→6046 | 6.3.4 | new tables hm_senderlist (the reader's, the domain's and the server's allow and block list), hm_digeststate and hm_digesttokens (the quarantine digest's state and its links, cascading from hm_accounts); hm_domains.domainquarantinedigest (0) and domainquarantinedigesthour (7); hm_settings rows QuarantineDigestBaseUrl '' and QuarantineDigestLinkHours 72 (source: Upgrade6045to6046MSSQL.sql) |
| 6046→6047 | 6.3.4 | new tables hm_roles (the four built-in roles seeded) and hm_roleassignments (one role per account, cascading from hm_accounts) (source: Upgrade6046to6047MSSQL.sql) |
| 6047→6048 | 6.3.4 | five hm_domains.domaindkimrotation* columns (enabled 0, interval 180 days, propagation 72 hours, retention 7 days, key bits 2048) and new table hm_dkimrotations, cascading from hm_domains; the dkim.rotation.stalled alert rule, on (source: Upgrade6047to6048MSSQL.sql) |
| 6048→6049 | 6.3.4 | new tables hm_bouncesuppressions, hm_bouncecounts and hm_bouncereportkeys, each cascading from hm_domains (source: Upgrade6048to6049MSSQL.sql) |
| 6049→6050 | 6.3.4 | new table hm_pgpkeys (the OpenPGP public-key store, cascading from hm_accounts); hm_domains.domainopenpgppublish and domainopenpgpnewaccounts, hm_accounts.accountopenpgppublish, all 0; four hm_settings rows, OpenPGPWebKeyDirectoryEnabled 0 among them (source: Upgrade6049to6050MSSQL.sql) |
| 6050→6051 | 6.3.4 | new tables hm_webhooks and hm_webhookqueue; hm_domains.domainwebhooksenabled (1) and domainwebhooksubjects (0); four hm_settings rows, WebhooksEnabled 1 among them (source: Upgrade6050to6051MSSQL.sql) |
| 6051→6052 | 6.3.4 |
hm_domains.domaincountrypolicy ''; hm_settings rows GeoIpDatabaseFile, GeoIpDatabaseMaxAgeDays (45), CountryBlockingExemptRanges (loopback and the private ranges) and the four CountryPolicy* (source: Upgrade6051to6052MSSQL.sql) |
| 6052→6053 | 6.3.4 | new table hm_signins (sessions and devices, cascading from hm_accounts); four hm_domains.domainsession* columns and the four Session* hm_settings rows, all 0 (source: Upgrade6052to6053MSSQL.sql) |
| 6053→6054 | 6.3.4 | new table hm_pushsubscriptions (cascading from hm_accounts); seven WebPush* hm_settings rows, WebPushEnabled 0 (source: Upgrade6053to6054MSSQL.sql) |
| 6054→6055 | 6.3.4 | new tables hm_complaints and hm_complaintvolume (cascading from hm_domains); hm_bouncecounts.bccomplaints and bclastcomplaint; the complaints.rate.high alert rule, on (source: Upgrade6054to6055MSSQL.sql) |
| 6055→6056 | 6.3.4 | new table hm_mailboxmoves (the journal of a mailbox rename or move, cascading from hm_accounts) (source: Upgrade6055to6056MSSQL.sql) |
| 6056→6057 | 6.3.4 | new table hm_reputationchecks; hm_settings rows ReputationWatchEnabled 0, ReputationWatchLists (four block lists) and ReputationWatchAddresses; the reputation.listed alert rule, on (source: Upgrade6056to6057MSSQL.sql) |
| 6057→6058 | 6.3.4 | new tables hm_spamtraps and hm_spamtraphits (cascading from hm_domains); four SpamTrap* hm_settings rows (ban 1440 minutes, block 30 days, hits kept 90 days, SpamAssassin taught 0) (source: Upgrade6057to6058MSSQL.sql) |
| 6058→6059 | 6.3.4 | the hm_settings row backupencryptionpassphrase (source: Upgrade6058to6059MSSQL.sql) |
| 6059→6060 | 6.3.4 | new table hm_autoreplies (who the out-of-office reply has answered, cascading from hm_accounts); hm_accounts.accountvacationdays, accountvacationinternalsubject, accountvacationinternalmessage, accountvacationinternalonly; hm_domains.domainvacationdays (source: Upgrade6059to6060MSSQL.sql) |
| 6060→6061 | 6.3.4 | five hm_domains.domainquota* columns: warning percent, subject and text, grace percent and send-stop percent (-1 or '' taking the server's) (source: Upgrade6060to6061MSSQL.sql) |
| 6061→6062 | 6.3.4 | new table hm_domainsamples (one row per day and domain) (source: Upgrade6061to6062MSSQL.sql) |
| 6062→6063 | 6.3.4 |
hm_rules.ruledomainid int default 0 (a domain's own rules) (source: Upgrade6062to6063MSSQL.sql) |
| 6063→6064 | 6.3.4 |
hm_calendars.calendarcolor; new table hm_calendarshares, cascading from hm_calendars (source: Upgrade6063to6064MSSQL.sql) |
| 6064→6065 | 6.3.4 |
hm_fetchaccounts.faoauthprovider, faoauthtoken, famirrorskip, famirrorseen (source: Upgrade6064to6065MSSQL.sql) |
| 6065→6066 | 6.3.4 | new table hm_senderaddresses (addresses elsewhere an account may send as, cascading from hm_accounts) (source: Upgrade6065to6066MSSQL.sql) |
| 6066→6067 | 6.3.4 | new table hm_maskedaddresses (cascading from hm_accounts) (source: Upgrade6066to6067MSSQL.sql) |
| 6067→6068 | 6.3.4 | new table hm_recoverable (deleted items kept aside, cascading from hm_accounts) (source: Upgrade6067to6068MSSQL.sql) |
| 6068→6069 | 6.3.4 | new table hm_confidential (messages that expire, cascading from hm_accounts) (source: Upgrade6068to6069MSSQL.sql) |
| 6069→6070 | 6.3.4 |
hm_domains.domainreportmailbox; new tables hm_dmarcreports (cascading from hm_domains), hm_dmarcreportrecords (from hm_dmarcreports) and hm_tlsreports (from hm_domains) (source: Upgrade6069to6070MSSQL.sql) |
| 6070→6071 | 6.3.4 | new tables hm_accountactivity, hm_accountrecipients, hm_accountreview and hm_heldmessages, each cascading from hm_accounts; the account.compromised alert rule, on (source: Upgrade6070to6071MSSQL.sql) |
| 6071→6072 | 6.3.4 |
hm_greylisting_triplets addresses widened to 320 with glsenderhash and glrecipienthash beside them; new table hm_greylisting_bypass; hm_settings rows GreyListingSlideOnEarlyRetry 0, GreyListingAbuseThreshold 0, GreyListingAbuseLockMinutes 60 (source: Upgrade6071to6072MSSQL.sql) |
| 6072→6073 | 6.3.4 | new table hm_domainpolicylist (the domain white and black list) (source: Upgrade6072to6073MSSQL.sql) |
| 6073→6074 | 6.3.4 |
hm_distributionlists.distributionlistmembership nvarchar(200) default '' (source: Upgrade6073to6074MSSQL.sql) |
| 6074→6075 | 6.3.4 | new table hm_journalrules, cascading from hm_domains (source: Upgrade6074to6075MSSQL.sql) |
| 6075→6076 | 6.3.4 | new tables hm_accountrecovery, hm_recoverytokens and hm_recoverycodes, each cascading from hm_accounts; the account.password-reset alert rule, on (source: Upgrade6075to6076MSSQL.sql) |
| 6076→6077 | 6.3.4 | new table hm_passkeys (the account id nullable, for the administrator's passkeys) (source: Upgrade6076to6077MSSQL.sql) |
| 6077→6078 | 6.3.4 | new table hm_directory, cascading from hm_accounts (source: Upgrade6077to6078MSSQL.sql) |
| 6078→6079 | 6.3.4 | new tables hm_addressbooks (from hm_accounts) and hm_addressbookshares (from hm_addressbooks); hm_contacts.contactbookid default 0, which is the account's default book (source: Upgrade6078to6079MSSQL.sql) |
| 6079→6080 | 6.3.4 | new table hm_pgpring (the webmail's OpenPGP keys, cascading from hm_accounts) (source: Upgrade6079to6080MSSQL.sql) |
| 6080→6081 | 6.3.4 | five hm_pgpring columns: pgrverified, pgrverifiedat, pgrchangedfrom, pgrchangedat, pgraccepted (source: Upgrade6080to6081MSSQL.sql) |
| 6081→6082 | 6.3.4 | new table hm_protectedmessages (cascading from hm_accounts); hm_settings row ProtectedMessageDays 30 (source: Upgrade6081to6082MSSQL.sql) |
| 6082→6083 | 6.3.4 |
hm_accounts.accountzeroaccess 0; new table hm_zeroaccess (cascading from hm_accounts); hm_settings row ZeroAccessAllowed 0 (source: Upgrade6082to6083MSSQL.sql) |
| 6083→6084 | 6.3.4 |
hm_domains.domainrestricteddomains nvarchar(2000) '' and hm_accounts.accountrestricteddelivery 0 (source: Upgrade6083to6084MSSQL.sql) |
| 6084→6085 | 6.3.4 | new table hm_recall, cascading from the sender's hm_accounts row; hm_settings row RecallDays 14 (source: Upgrade6084to6085MSSQL.sql) |
| 6085→6086 | 6.3.4 |
hm_calendarshares.sharegroupid and sharedelegate, both 0 (source: Upgrade6085to6086MSSQL.sql) |
| 6086→6087 | 6.3.4 |
hm_domains.domainimpersonationaction 0, domainprotectedpersons and domainprotectedpartners nvarchar(2000) '' (source: Upgrade6086to6087MSSQL.sql) |
| 6087→6088 | 6.3.4 | new table hm_settingoverrides (a domain's or an account's own value of a setting with levels) (source: Upgrade6087to6088MSSQL.sql) |
| 6088→6089 | 6.3.4 | new table hm_apisends (what each API key sent through the send API) (source: Upgrade6088to6089MSSQL.sql) |
| 6089→6090 | 6.3.4 | new tables hm_migrations, hm_migrationjobs (cascading from hm_migrations) and hm_migrationfolders (from hm_migrationjobs); hm_settings row MigrationConnectionsPerHost 4 (source: Upgrade6089to6090MSSQL.sql) |
| 6090→6091 | 6.3.4 | new tables hm_sandboxverdicts and hm_sandboxholds; thirteen Sandbox* hm_settings rows, SandboxEnabled 0 among them (source: Upgrade6090to6091MSSQL.sql) |
| 6091→6092 | 6.3.4 | the list manager: fourteen hm_distributionlists.distributionlist* columns (managed, subscribe, archive and its days, digest, its hour and its marks, reply-to, prefix, footer, VERP, owner, secret), nine hm_distributionlistsrecipients columns (delivery, subscribed, confirmed - 1 for every existing member - token, hard and soft bounces, since, last bounce, reason), new table hm_listarchive, and fourteen LIST_* server messages (source: Upgrade6091to6092MSSQL.sql) |
| 6092→6093 | 6.3.4 | new tables hm_warmup and hm_warmupcounts (cascading from hm_warmup) (source: Upgrade6092to6093MSSQL.sql) |
| 6093→6094 | 6.3.4 |
hm_remotedomainpolicies.policyencrypt 0, policyencryptkey ntext '', policyencryptfallback 0 (source: Upgrade6093to6094MSSQL.sql) |
| 6094→6095 | 6.3.4 | new tables hm_smimeca (cascading from hm_domains) and hm_smimeissued (from hm_smimeca); hm_smimekeys.smimeauthority 0 (source: Upgrade6094to6095MSSQL.sql) |
| 6095→6096 | 6.3.4 |
hm_accounts.accountexternalid nvarchar(255) '' (the identity provider's id for an account SCIM made) (source: Upgrade6095to6096MSSQL.sql) |
| 6096→6097 | 6.3.4 | new tables hm_discoverycases, hm_discoverysearches (cascading from the case), hm_discoveryholds (from the case, indexed on scope and owner) and hm_discoveryexports (from the search) - eDiscovery; view_discovery and manage_discovery added to the built-in serveradmin role and view_discovery to auditor (source: Upgrade6096to6097MSSQL.sql) |
| 6097→6098 | 6.3.4 | new table hm_storeblobs (a de-duplicated part by its SHA-256, size and reference count, unique on the digest); hm_settings rows MessageStoreCompression 0 and MessageStoreDedupMinKB 256, each inserted only where absent (source: Upgrade6097to6098MSSQL.sql) |
| 6098→6099 | 6.3.4 | new tables hm_accountkeys (one per account, cascading from hm_accounts) and hm_domainrecovery (one per domain, cascading from hm_domains) - mailbox encryption at rest; hm_settings row MailboxKeyIdleMinutes 15 (source: Upgrade6098to6099MSSQL.sql) |
| 6099→6100 | 6.3.4 | new tables hm_inboundhooks (unique on the address) and hm_inboundhookqueue (cascading from the hook) - mail in to a webhook; manage_inbound_webhooks added to serveradmin (source: Upgrade6099to6100MSSQL.sql) |
| 6100→6101 | 6.3.4 |
hm_accounts.accountkind int default 0; new tables hm_bookingpolicies, hm_bookinganswers, hm_bookingqueue and hm_bookingdecisions, each cascading from hm_accounts - rooms and equipment; hm_messages.messagesubmitterid int default 0 and messageauthdomains nvarchar(255) default '' (the account an authenticated session submitted a message as, and the domains that passed DMARC and SPF when it arrived) (source: Upgrade6100to6101MSSQL.sql) |
| 6101→6102 | 6.3.4 |
No DDL on SQL Server, SQL Server Compact or PostgreSQL, which only move the version. On MySQL and MariaDB every table is converted to utf8mb4 under utf8mb4_general_ci, ENGINE=InnoDB, ROW_FORMAT=DYNAMIC, and the database's default with them (ALTER DATABASE, which needs the ALTER privilege on the database); hm_accounts first, so a server older than MySQL 5.7.9 or MariaDB 10.2.2 fails with error 1071 before anything has changed; MariaDB's statement time limit lifted for the connection; every statement can be run again (source: Upgrade6101to6102MySQL.sql; Upgrade6101to6102MSSQL.sql) |
| 6102→6103 | 6.3.4 |
hm_accounts.accounttotpsecret widened to nvarchar(1024) (varchar(1024) on MySQL and PostgreSQL), to hold a sealed second factor (source: Upgrade6102to6103MSSQL.sql, MySQL, PGSQL) |
| 6103→6104 | 6.3.4 |
hm_fetchaccounts.faorigin tinyint default 0: whose word a fetch account's server and user name are - 1 for one a person made in the webmail, or moved to another server or port there, which the fetcher connects only to the public addresses its name resolves to at that moment, unless ReaderFetchPrivateHosts is on; 2 for one whose server or user name somebody without the server's authority named; neither is ever given the server's own OAuth2 token, and every row the step finds gets 0, so an administrator's fetch account behaves as it did (source: Upgrade6103to6104MSSQL.sql, MSSQLCE, MySQL, PGSQL; Common/Application/Constants.h at the tag) |
| 6104→6105 | 6.3.5 |
hm_quarantine.quarantinehelo: the HELO name of a held message, shown and grouped by in the quarantine review (source: Upgrade6104to6105*.sql; CHANGELOG 6.3.5) |
| 6105→6106 | 6.3.6 | No column changes. Every IMAP external account's stored days to keep messages is translated so that the value means one thing for POP3 and IMAP - -1 deletes each message once collected, 0 leaves every message, N deletes it N days after collection - and no account starts deleting (source: Upgrade6105to6106*.sql; CHANGELOG 6.3.6, item 22) |
| 6106→6107 | 6.3.6 | The greylisting key finished: every triplet row has its key, the key is NOT NULL and unique, and the lookup asks for it alone - on MySQL and MariaDB over the stored generated column glipaddress2key, on PostgreSQL through two partial indexes. Rows in which an address had been cut to the old columns' 200 characters are deleted and one row kept per triplet, so such a sender is greylisted once more; on a large table the keying runs before the step's transaction, committing as it goes, so an upgrade stopped part-way continues where it stopped (source: Upgrade6106to6107*.sql; CHANGELOG 6.3.6, item 23) |
| 6107→6108 | 6.3.6 | New table hm_neverbannames (the never-ban list by reverse name: id, pattern, note, created); hm_tcpipports.portsmtpauth (0 On, 1 Off, 2 Honeypot), On for every listener an upgrade finds (source: Upgrade6107to6108*.sql; CHANGELOG 6.3.6, item 25) |
- 6.3.7 adds no step: it requires 6108, as 6.3.6 does, so an upgrade from 6.3.6 changes no table and the live update's rollback to 6.3.6 meets no newer schema (source: Constants.h:1291 at the tag v6.3.7; CHANGELOG 6.3.7, Back up first; no schema step).
- The 6.2.24 notes: "from 6.2.21 the schema moves from 6011 to 6025 in fourteen steps, one way" (source: v6.2.24 release body, "Upgrading from 6.2.21").
- Note: the pre1 body says "schema 6012 → 6014"; 6.2.21 was at 6011, so the real move for a pre1 upgrader was 6011 → 6014 (source: pre1 body vs 6.2.24 body).
- DBUpdater registers the steps through
new UpgradeScript(6030, 6031)and, since 6.3.2,new UpgradeScript(6031, 6032)to(6037, 6038), and since 6.3.3(6038, 6039)and(6039, 6040);(6040, 6041)to(6103, 6104)since 6.3.4 (source: Tools/DBUpdater/formMain.cs:251-314 at the tag,51e2dbda0), each with probes in SchemaVerification.cs that the Compact Edition check and the Linux upgrade gate execute - except 6042, which changes no table and has none. The 6043 step's setting rows first failed that gate on MySQL and PostgreSQL (a created database already had them); they are now inserted only where absent, andbuild/check-upgrade-inserts.py, run bystyle.ymlon GitHub and by hand since, refuses a setting row inserted with a plainVALUESin any upgrade script from 6030 on (source: build/check-upgrade-inserts.py; .github/workflows/style.yml:100). - 6.2.26 changed no schema; it fixed the SQL Server Compact verification of the 6030 step (GitHub issue 114): the four foreign-key probes used
case when exists (...), which faults the CE provider (the server reportedHM10045, the crash oracle then stopped the service); they now readupdate hm_dbversion set value = value / (value - value) where not exists (select 1 from information_schema.table_constraints ...), andbuild/check-db-scripts.ps1executes every probe against a freshly created database with a negative control (source: v6.2.26 body; build/check-db-scripts.ps1:126-156).
Since 6031, seven steps (all in 6.3.2): the tables hm_contacts, hm_accountprefs, hm_scheduled, hm_files and hm_smimekeys, each with a foreign key to hm_accounts and cascading deletes, hm_messages.messagekeywords, and hm_surblservers.surblresult; then two in 6.3.3: hm_domains.domainrelaypassword widened to 1024 characters, and hm_contacts given contacturi, contactuid and contactvcard for CardDAV. Five further steps, 6040→6045, came first in the work that became 6.3.4: the two calendar tables CalDAV needs (6041), no table at all (6042, the settings store), the audit trail's and the alerts' three tables (6043), the domain transforms' seven columns and hm_knownsenders (6044), and hm_remotedomainpolicies (6045); then fifty-nine more, 6046 to 6104, each a row of the table above. 6.3.4's create script has 131 hm_* tables (118 at master 540347500), against 51 in 6.3.3 and 46 at 6031 (grep -i "^create table" DBScripts/CreateTablesMSSQL.sql at each). What follows describes 6031 and stays true for everything it names.
Forty-six hm_* tables (grep -i "^create table" DBScripts/CreateTablesMSSQL.sql).
Seventeen of them are the child side of a foreign key, all added at 6029→6030, and that
is what makes deleting a domain or an account clean up after itself. Those seventeen
relationships are drawn below; the twenty-nine tables that are nobody's child are listed
underneath.
erDiagram
hm_domains ||--o{ hm_accounts : "accountdomainid"
hm_domains ||--o{ hm_aliases : "aliasdomainid"
hm_domains ||--o{ hm_domain_aliases : "dadomainid"
hm_domains ||--o{ hm_distributionlists : "distributionlistdomainid"
hm_distributionlists ||--o{ hm_distributionlistsrecipients : "distributionlistrecipientlistid"
hm_routes ||--o{ hm_routeaddresses : "routeaddressrouteid"
hm_accounts ||--o{ hm_fetchaccounts : "faaccountid"
hm_fetchaccounts ||--o{ hm_fetchaccounts_uids : "uidfaid"
hm_accounts ||--o{ hm_apppasswords : "apaccountid"
hm_accounts ||--o{ hm_passwordhistory : "phaccountid"
hm_rules ||--o{ hm_rule_criterias : "criteriaruleid"
hm_rules ||--o{ hm_rule_actions : "actionruleid"
hm_groups ||--o{ hm_group_members : "membergroupid"
hm_messages ||--o{ hm_messagerecipients : "recipientmessageid"
hm_messages ||--o{ hm_message_metadata : "metadata_messageid"
hm_messages ||--o{ hm_messageindexterms : "mitmessageid"
hm_imapfolders ||--o{ hm_imapexpunged : "expungedfolderid"
Every one of those is ON DELETE CASCADE. That is the point: before 6030, deleting a
domain left its accounts' app passwords, password history, fetch-account UID lists and
rule criteria behind as orphan rows, and nothing ever collected them.
The twenty-nine tables that are not the child of any foreign key, and what each is for. Five of them are parents in the diagram above; the rest stand alone:
| Table | Holds |
|---|---|
hm_domains |
The domains this server is authoritative for - the root of most of the cascade above |
hm_dbversion |
The single row the server compares against REQUIRED_DB_VERSION. A mismatch is error 5011 |
hm_settings |
Server settings the Control Panel and COM API edit |
hm_inisettings |
The [Settings] INI keys mirrored into the database (added at 6011); since 6.3.4, from 6042, their store, with the file's section the copy (section 4e) |
hm_tcpipports |
The listening ports. A fresh install seeds exactly four: 25, 587, 110, 143 |
hm_sslcertificates |
Certificates available to bind to a TLS port |
hm_securityranges |
IP ranges: who may connect, authenticate and relay |
hm_imapfolders |
The folder hierarchy; message bodies are on disk, not here |
hm_messages |
One row per queued or delivered message |
hm_imap_metadata |
RFC 5464 METADATA entries (added at 6014) |
hm_messageindexstate |
Full-text indexing progress (added at 6023; empty until enabled) |
hm_messagetrace |
Per-message trace records (added at 6020; empty until MessageTraceEnabled) |
hm_metricsamples |
One row per metric per minute (added at 6028) |
hm_archiveindex |
The archive's searchable index (added at 6029; only copies made from then on) |
hm_quarantine |
Held suspected spam (added at 6018) |
hm_greylisting_triplets |
The greylisting state machine's memory |
hm_greylisting_whiteaddresses |
Senders exempt from greylisting only |
hm_whitelist |
Senders exempt from spam checking |
hm_blocked_senders |
Claimed sender addresses and domains refused with a score (added at 6024) |
hm_blocked_attachments |
Attachment-name wildcards to strip |
hm_dnsbl |
Configured DNS blacklists |
hm_surblservers |
Configured URL blocklists |
hm_incoming_relays |
Upstream gateways whose IP is not the real client |
hm_logon_failures |
Failed-logon counters behind auto-ban and lockout |
hm_acl |
Public-folder permissions |
hm_groups |
Account groups (the parent side of hm_group_members) |
hm_routes |
Per-domain delivery overrides (the parent side of hm_routeaddresses) |
hm_rules |
Global and account rules (the parent side of criteria and actions) |
hm_servermessages |
The text of bounces and system messages |
flowchart LR
S6005["6005<br/>6.2.10 - 6.2.18"] --> S6011["6011<br/>6.2.19 - 6.2.21<br/>six steps"]
S6011 --> S6022["6022<br/>the 6.2.22 pre-releases<br/>eleven steps"]
S6022 --> S6025["6025<br/>alpha1, alpha2, 6.2.24<br/>three steps"]
S6025 --> S6030["6030<br/>6.2.25 and 6.2.26<br/>five steps, incl. the<br/>seventeen foreign keys"]
S6030 --> S6031["6031<br/>6.2.27 - 6.3.1<br/>one step"]
S6031 --> S6038["6038<br/>6.3.2<br/>seven steps"]
S6038 --> S6040["6040<br/>6.3.3<br/>two steps"]
S6040 --> S6104["6104<br/>6.3.4<br/>sixty-four steps"]
The chain is one way. The server refuses to start on a database older or newer than the binary requires (error 5011; 5010 when the version cannot be read), so downgrading a server without restoring the matching database backup does not work — see Upgrading Guide and Backup and Restore.
Four upgrade steps deserve planning rather than a click:
-
6024→6025 rewrites
hm_messageson MS SQL Server, SQL CE and MySQL, becausemessageflagswidens fromtinyinttosmallint. PostgreSQL was alreadysmallintand is untouched. - 6029→6030 adds the seventeen foreign keys, which means deleting orphan rows first and then reading every child table once. Plan for it like an index build.
-
6100→6101 (6.3.4) adds two columns to
hm_messages, the table with a row for every stored message; on a large store it is not instant on every backend. - 6101→6102 (6.3.4) rebuilds every table on MySQL and MariaDB, converting it to utf8mb4: it takes about as long as copying the database, needs MySQL 5.7.9 or MariaDB 10.2.2 and the ALTER privilege on the database, and stops with error 1062 where two rows the old collation told apart are one to the new - rename or remove one and run it again. Upgrading hMailServer 18.4c has the rest.
- 6.2.11: "Drop-in over 6.2.10. No database change (schema version 6005), no configuration change, no server-core change." (source: v6.2.11 body)
- 6.2.12: "No server-core changes; no database change (schema version 6005)." .NET 8 Desktop Runtime becomes a server-component prerequisite; supported-OS floor Windows 10 1607 (source: v6.2.12 body)
- 6.2.13 / 6.2.14: "in-place over any earlier hMailServer release; the database upgrades automatically (schema version 6005). Minimum OS is Windows 10 1607 / Server 2016, 64-bit." (source: v6.2.13, v6.2.14 bodies)
- 6.2.15–6.2.17: no database change (6005) (source: bodies)
- 6.2.19: "The database schema moves from 6005 to 6011. DBUpdater applies it; take a backup first." Fresh installs of 6.2.19 fail (SQL CE create script batching + silent-install hang) — use 6.2.20 (source: v6.2.19 body warning block)
- 6.2.20: fixes fresh install; "upgrades from existing installations were never affected" (source: v6.2.20 body)
- pre1: "Databases upgrade automatically (schema 6012 → 6014) via the bundled DBUpdater. Take a backup before installing a pre-release." (source: pre1 body)
- pre2: "Upgrading from pre1 requires DBUpdater — schema moves to 6015" (source: pre2 body)
- pre3: "schema moves from 6015 to 6019 in four steps" (source: pre3 body)
- pre6: "schema moves from 6019 to 6022 in three steps" (source: pre6 body)
- alpha1: "6022 to 6025 in three steps; from 6.2.21 (6011) it moves in fourteen"; the upgrade is one-way — an older server refuses a newer
hm_dbversion("The database is too new for this version of hMailServer. Please upgrade hMailServer.") (source: alpha1 body; hmailserver/docs/Upgrading.md:141-143) - alpha1 note 15: coming from 6.2.18 or earlier, the .NET runtime is load-bearing on upgrade — DBUpdater is a .NET app and the server refuses to start on an old schema; if the runtime install fails, install the .NET 10 Desktop Runtime by hand then run
Bin\DBUpdater.exe(source: alpha1 body) - alpha1 note 13: new COM interfaces → the installer runs
hMailServer.exe /RegisterTypeLib; a hand-copied install must re-register elevated (HKLMInterfacekeys), symptom otherwiseREGDB_E_IIDNOTREG(source: alpha1 body; hmailserver/installation/hMailServerInnoExtension.iss:724-744) - 6.2.24 "Behaviour that changes on upgrade, without a switch" (seven items) — reproduced in §5 below (source: v6.2.24 body)
- 6.2.24: early-bound COM clients compiled against a 6.2.22 pre-release interop must be recompiled; late-bound scripts and anything built against 6.2.21 are unaffected (source: v6.2.24 body; alpha1 note 4)
- 6.2.25: "The database schema moves from 6025 to 6030 in five steps, one way"; the 6029→6030 step reads every child table; eight behaviour changes without a switch (see §5); the type library gained members (
Utilities.SearchArchive,SetArchiveHold,GetMetricHistory,SampleMetricsNow,RunMessageRetention;Settings.EnrolAdministratorTOTP/DisableAdministratorTOTP;Application.AdministratorTOTPEnabled;COMAuthentication.AuthenticateWithCode;Domain/Account.MessageRetentionDays;Diagnostics.AssertionsEnabled) — clients compiled against 6.2.24 keep working, recompile to reach them;FetchAccount.ServerType1 now means IMAP (source: v6.2.25 body; hMailServer.idl:810-811, 854, 988, 1239-1243, 1720-1721, 1854, 3221) - 6.2.26: server unchanged; if the 6.2.25 installer reported the SQL Server Compact upgrade as failed, the database is at 6030 with its foreign keys and this installer finds nothing left to upgrade (source: v6.2.26 body)
- 6.2.27: "Schema 6030 → 6031: one column on
hm_fetchaccounts"; from 6.2.25 or 6.2.26 run the installer (source: v6.2.27 body) - 6.2.28: no schema step —
REQUIRED_DB_VERSIONstays 6031, so an upgrade from 6.2.27 is the installer and nothing else; from 6.2.25 or 6.2.26 the 6030 → 6031 step still runs on the way (source: Constants.h:173; Version.h:5-7) - Installer minimum OS:
MinVersion=10.0.14393(Windows 10 1607 / Server 2016) (source: hmailserver/installation/section_setup.iss:24-28; section_messages.iss:3) - Installer bundles the .NET 10 Desktop Runtime (
DOTNET_MAJOR "10") (source: hmailserver/installation/hMailServer64.iss:16-18); all tool projects targetnet10.0-windows(source: hmailserver/source/Tools/ControlPanel/.csproj, DBUpdater/.csproj<TargetFramework>) - .NET 10 migration commit
5409ae8d5is dated 2026-08-12 10:27 (after the v6.2.18 tag, before v6.2.19) → first shipped in 6.2.19, though no 6.2.19 note says so (source:git log; Roadmap.md:184 "done, 12 Aug 2026") - Silent install:
/VERYSILENT /SUPPRESSMSGBOXES— every installer dialog now takes its default (6.2.20); a failed DB step exits non-zero (alpha2); pass the admin password with/adminpassword=(min 5 chars) (source: v6.2.20, alpha2 bodies; hMailServerInnoExtension.iss:64-67, 649-653) - 6.3.4: the schema moves from 6040 to 6104 in sixty-four steps, one way, applied by DBUpdater on Windows and by
hmailserver --upgrade-databaseon Linux (the.deband.rpmrun it themselves) - or by the service itself as it starts, where[Database] UpgradeOnStart=1- so take a backup of the database and the data directory first. 6101 altershm_messages, and 6102 rebuilds every MySQL and MariaDB table; on a MySQL older than 5.7.9 or a MariaDB older than 10.2.2 the upgrade stops at 6041 with error 1071 and changes nothing. 6.3.4 carries no Sigstore bundle, so every server installs it by hand, and its download is checked against its SHA-256 sums file, signed with the release tags' SSH key (Security Policy, Verifying a release); a silent upgrade of a server with an administrator password takes it with/adminpassword=or, new in 6.3.4, inHM_ADMIN_PASSWORD; a server from 6.2.28 to 6.3.3 finds it only afterUpdateFeedUrl=https://updates.progressiverobot.com/releases.jsonis added under[Settings]and the service restarted. An upgrade keeps the service's account, gets none of the new installation's secure defaults and seals no secret until--create-stored-secrets-key. Section 2 lists every step and section 5, items 20 to 51, what changes without a switch (source: Common/Application/Constants.h:1224 at the tag; hmailserver/docs/Upgrading.md and UpdateFeed.md; Upgrading hMailServer 18.4c)
-
EHLO keywords now advertised:
SIZE,8BITMIME,PIPELINING,CHUNKING,BINARYMIME,SMTPUTF8,ENHANCEDSTATUSCODES,DSN,XCLIENT ADDR NAME PORT PROTO HELO LOGIN(trusted peers only),STARTTLS,AUTH LOGIN [PLAIN] SCRAM-SHA-256 [SCRAM-SHA-256-PLUS] [XOAUTH2 OAUTHBEARER],HELP(source: hmailserver/source/Server/SMTP/SMTPConnection.cpp:2447-2556) - 6.2.13: PTR lookup for the
Receivedheader moved off the I/O thread (fix for relayed-mail DATA stall);TCP_NODELAYon every connection; rejectedBDATdrains its payload;EHLO SIZEno longer overflows (source: v6.2.13 body) - 6.2.15: BDAT exact-length reads take only what arrived (a vanished sender no longer yields a NUL-padded delivered message) (source: v6.2.15 body)
- 6.2.17:
FinalizationTimeout= 240 s,0disables — acceptance after end-of-data is bounded; server answers451 4.3.1(source: IniFileSettings.cpp:281; v6.2.17 body). SpamAssassin wait hard ceiling =SAMaxTimeout(default 90) + 30 s (source: IniFileSettings.cpp:275; v6.2.17 body) - 6.2.17: per-stage acceptance timings logged at APPLICATION level ("SMTPConnection - accept: done spam-protection in N ms") (source: v6.2.17 body)
- 6.2.18: a recipient lookup that fails because the database is unavailable answers
451, not550(thread-local marker);DBConnectionAcquireTimeoutdefault 60 (was 0 in 6.2.17) (source: IniFileSettings.cpp:306; v6.2.18 body) - 6.2.21: end-of-data
<CRLF>.<CRLF>is found anywhere in the received buffer and what follows is handed to the command parser (Postfix sends.\r\nQUIT\r\nin one segment) — the real fix for GitHub discussion 18; bare-LF terminators still only matched at buffer end and only with "Allow incorrect line endings" (CVE-2023-51764 rule) (source: v6.2.21 body) - 6.2.21: dot-transparency (RFC 5321 4.5.2) carried across buffer boundaries in both directions (source: v6.2.21 body)
- alpha1: BINARYMIME (RFC 3030) advertised;
DATAunderBODY=BINARYMIMErefused503 5.5.1; binary mark persisted as flag bit 256 (why 6025 widenedmessageflags); relay of a binary message refused at RCPT with554 5.6.3(source: SMTPConnection.cpp:2475-2481, 3123; alpha1 body) - alpha1: PROXY protocol v1/v2 —
SMTPProxyProtocolEnabled=0,SMTPProxyProtocolTrustedIPs="" ; XCLIENT —SMTPXClientEnabled=0,SMTPXClientTrustedIPs="" ; trust decided against the real TCP peer; a listed proxy must send the header; unparseable list entry matches nothing (HM6281) (source: IniFileSettings.cpp:620-623; SMTPConnection.cpp:2494-2499, 2962-2974; alpha1 body). Both in CP (Views/FeatureSettingsView.xaml.cs) and README. - alpha1: disk-space floor —
MinimumFreeDiskSpaceMB=100 →452 4.3.1at MAIL FROM,NO [UNAVAILABLE]at IMAP APPEND, POP3 fetcher pauses;DiskSpaceWarningThresholdMB=1024 logs one line;0disables (source: IniFileSettings.cpp:417-420; alpha1 note 5) - alpha2:
RSETbeforeEHLO, orSTARTTLSwithout a freshEHLO, no longer opens a transaction —MAIL FROM/AUTHafter the TLS handshake without EHLO gets503 Bad sequence of commands(RFC 3207 §4.2) (source: SMTPConnection.cpp:527, 554; alpha2 body) - alpha2: Received-header parsing for relayed/fetched mail takes the last observed address before
by, ignores HELO-supplied values, keeps headers with non-domain host names — verdicts move toward the address that actually connected (source: alpha2 body; parser in hmailserver/source/Server/Common/Util/Utilities.cpp) - Rate limit knob (pre-existing, findable since 6.2.14):
MaxSubmissionsPerIPPerMinute=0 (off) (source: IniFileSettings.cpp:618)
- 6.2.14: multiple smart hosts —
|-separated hosts in the relayer field fail over ("always worked; nothing said so") (source: v6.2.14 body) - 6.2.14: MTA-STS enforcement / MX failover state no longer leaks between recipient batches (source: v6.2.14 body)
- 6.2.15: an unreadable message file fails the delivery attempt immediately (connection dropped, not
QUIT); only a genuinely missing file fails permanently (source: v6.2.15 body) - 6.2.18:
ClientSessionCeiling= 1800 s absolute ceiling per outbound session, separate from idle timeout; ClamAV on the delivery path bounded;DNSQueryTimeout=10,ScriptTimeout=60,ExternalProcessTimeout=300 (source: IniFileSettings.cpp:293-308; v6.2.18 body) - pre1: EHLO is the opener on every outbound delivery (previously only on routes needing TLS/auth), HELO fallback kept; outbound
SIZE(RFC 1870) declared on MAIL FROM and an oversize message refused locally before upload (source: hmailserver/source/Server/SMTP/SMTPClientConnection.cpp:588; pre1 body) - pre1: outbound XOAUTH2 for routes whose host is in
OutboundOAuth2Hosts(defaultsmtp.office365.com); client-credentials token fromOutboundOAuth2TokenUrl/ClientId/ClientSecret/Scope(defaulthttps://outlook.office365.com/.default),OutboundOAuth2FixedTokenfor a pre-issued token; token cached to 80 % lifetime, dropped on refusal (source: IniFileSettings.cpp:210-216; SMTPClientConnection.cpp:459-472; pre1 body). Settings in CPViews/ServerSettingsView.xaml.cs; not in README. - pre6: per-domain outbound relay (schema 6021):
Domain.RelayHostetc. (IDL id 44); matched by sending domain; order is route → sending domain's relay → server-wide relayer; empty host = no opinion (source: IDL:884-885; DBScripts/Upgrade6020to6021MSSQL.sql; pre6 body) - pre6: full mailbox refused at RCPT with
452 4.2.2—RejectFullMailboxAtRcpt=1 (set 0 for old accept-then-bounce) (source: IniFileSettings.cpp:641; pre6 body) - alpha1: RFC 3464 DSNs — every bounce is
multipart/report; report-type=delivery-status; codes: full mailbox5.2.2, DB write failure4.3.0, disk floor4.3.1, Sieve reject5.7.1;Arrival-Date/Original-Recipientomitted (source: alpha1 body) - alpha1: local delivery could silently lose a message when the account-level copy failed to write; the sender is now told (source: alpha1 body "Fixes")
- Known, unchanged: equal-preference MX records are not randomised (source: v6.2.21, pre1 bodies).
- 6.2.25: outbound PIPELINING and CHUNKING — the envelope is pipelined and the message sent as one BDAT chunk when the remote advertises both;
OutboundPipelining=1,OutboundChunking=1, each used only when the remote advertises the extension; a binary message is now relayed as-is to a remote that accepts BINARYMIME (the local-only rule of alpha1 still applies to remotes that do not) (source: IniFileSettings.cpp:732-733; v6.2.25 body) - Pre-existing knob surfaced in 6.2.14:
MaxOutboundPerDestinationPerMinute=0 (source: IniFileSettings.cpp:673)
-
Full CAPABILITY line today:
IMAP4 IMAP4rev1 IMAP4rev2 CHILDREN+IDLE(if enabled) +QUOTA QUOTA=RES-STORAGE(if enabled) +SORT(if enabled) +THREAD=ORDEREDSUBJECT THREAD=REFERENCES(unconditional) +ACL(if enabled) +STARTTLS+LOGINDISABLED(when auth refused on cleartext) +AUTH=PLAIN AUTH=SCRAM-SHA-256 [AUTH=SCRAM-SHA-256-PLUS](only ifEnableImapSASLPlain) +AUTH=XOAUTH2 AUTH=OAUTHBEARER(ifOAuth2Enabled) +SASL-IR+NAMESPACE RIGHTS=texk MOVE ID SPECIAL-USE CREATE-SPECIAL-USE UNSELECT UIDPLUS ENABLE STATUS=SIZE ESEARCH CONDSTORE QRESYNC LIST-EXTENDED SEARCHRES WITHIN UTF8=ACCEPT+LIST-STATUS PREVIEW MULTIAPPEND REPLACE SAVEDATE METADATA OBJECTID BINARY+UNAUTHENTICATE(authenticated state only) +LITERAL-+APPENDLIMIT/APPENDLIMIT=<bytes>(authenticated) (source: hmailserver/source/Server/IMAP/IMAPCommandCapability.cpp:24-190) -
Inert by default:
EnableImapSASLPlainships0in the DB and gates the wholeAUTHENTICATEcommand, soAUTH=SCRAM-SHA-256is unreachable over IMAP on a stock install (source: DBScripts/CreateTablesMSSQL.sql:1037; IMAPCommandCapability.cpp:80-95; alpha1 "Known limitations" 22) - 6.2.13: partial-fetch range crash/OOB read fixed;
OnClientLogonfires from everyAUTHENTICATEmechanism (source: v6.2.13 body) - 6.2.14: APPEND reports failure on disk-full; CLOSE notifies other sessions; RENAME cannot make a folder its own parent;
[UNSEEN]is a sequence number;\Recentset maintained;BODY.PEEKno longer cancels\Seenfrom another item; IMAP SASL credentials masked in logs (source: v6.2.14 body) - 6.2.15: sequence sets:
*resolves on either side of:, descending ranges normalised, in all four parsers (FETCH/STORE/COPY/MOVE, SEARCH, UID EXPUNGE, QRESYNC VANISHED) —UID STORE *:*no longer flags every message (source: v6.2.15 body);STATUS (RECENT)per folder (source: v6.2.15 body) - 6.2.18: pre-authentication command buffering capped at 11 MB (source: v6.2.18 body; IMAPConnection.cpp:238
iMaxCommandBuffer = 11 * 1024 * 1024) - 6.2.19:
GetUniqueMessageIDduplicate-UID fix (source: v6.2.19 body);LOGINDISABLEDadvertised (Roadmap "shipped 13 August 2026" → 6.2.19) (source: Roadmap.md:500; IMAPCommandCapability.cpp:73-74) - 6.2.20: THREAD (RFC 5256) both algorithms,
UID THREAD(source: IMAPCommandCapability.cpp:44-49; v6.2.20 body). (pre6 body's "correction" that THREAD "has been implemented all along" refers to a stale Roadmap row; it shipped in 6.2.20.) - 6.2.21:
SORT ()/ bareSORT (REVERSE)rejected instead of spinning a core (source: v6.2.21 body); message-list cache eviction fixed (512 MB ceiling now works) (source: v6.2.21 body) - pre1: APPENDLIMIT (RFC 7889,
TOOBIG), LITERAL- (RFC 7888), LIST-STATUS (RFC 5819), UNAUTHENTICATE (RFC 8437, failed-login counter survives), QUOTA=RES-STORAGE/OVERQUOTA/SETQUOTA refused (RFC 9208), PREVIEW (RFC 8970, 256-octet), MULTIAPPEND (RFC 3502, atomic), REPLACE (RFC 8508, stored before delete), SAVEDATE (RFC 8514, schema 6013), METADATA (RFC 5464, schema 6014) (source: IMAPCommandCapability.cpp:127-190; IMAPConnection.cpp:178-181, 772-781; pre1 body) - pre2: BINARY (RFC 3516:
BINARY[],BINARY.PEEK[],BINARY.SIZE[], literal8~{n}on APPEND) — composite sections empty in pre2, fixed pre3; OBJECTID (RFC 8474:EMAILIDsurvives COPY,MAILBOXIDsurvives RENAME,THREADIDNIL; schema 6015) (source: IMAPCommandCapability.cpp:137-146; pre2, pre3 bodies) - alpha1: shared/delegated mailboxes — Other Users namespace
#Users.owner@domain.folder, reachable only where the owner's folder carries an RFC 4314 ACL; no separate switch: it is theenableimapaclsetting (DB default1), and turning ACL off makes public folders open to everyone; no CP surface for grants (SETACL from a client);\Seenis shared state; everyLISTruns one extra query (source: DBScripts/CreateTablesMSSQL.sql:914; alpha1 note 9, limitations 8-10) - alpha1: full-text index —
IndexerFullText=0 and DB settingMessageIndexing(default 0, CP checkbox "Enable message indexing", COMSettings.MessageIndexingIDL id 89) must both be on; index only narrows the scan, results identical on/off; tuningIndexerFullTextBatchSize=250,IndexerFullTextMinTokenLength=3,IndexerFullTextMaxTokensPerMessage=2048; errorsHM6260(source: IniFileSettings.cpp:365-390; CreateTablesMSSQL.sql:1013; IDL:671; alpha1 body) - alpha1: QRESYNC tombstone pruning —
IMAPExpungeRetentionRecords=5000 per mailbox,0keeps all; one bulk delete at first start after upgrade; RFC 7162 §3.2.6 fallback built first;VANISHED (EARLIER)now sent before FETCH responses (source: IniFileSettings.cpp:646; alpha1 note 8, "Protocol correctness") - alpha2:
APPEND … UTF8 (~{n}wrapper (RFC 6855) accepted — Thunderbird 128+ Sent copies were refused withBAD APPEND Command requires at least 2 parameter(GitHub issue 53) (source: alpha2 body) - alpha2: COM
IMAPFolder.Deletereports a refused deletion instead of S_OK (source: alpha2 body) - 6.2.25: every session numbers its selected folder for itself (upstream PR 602) — another session's expunge no longer renumbers a client's messages; a message found gone answers
NO [EXPUNGEISSUED], QRESYNC sessions hearVANISHED; sessions stop sharing unguarded state (upstream PRs 580 and 566: one lock on the connection, serialised lazy collections, snapshots for cross-thread readers); one strand per connection, so a notification written to an idling IMAPS session cannot run inside the SSL engine mid-read; default special-use folders at account creation (Settings.CreateDefaultSpecialUseFoldersEnabled, off; schema 6026); one authorisation choke point — every folder-access decision isACLManager::CheckPermissionorCheckDelegatedRight, delivery and rules included, andbuild/check-authz-choke-point.pyinstyle.ymlfails any other caller (source: v6.2.25 body; DBScripts/Upgrade6025to6026MSSQL.sql; Common/Application/ACLManager.h:49,57; .github/workflows/style.yml:100) - 6.2.25: IMAP SEARCH BODY and TEXT read the text-bearing attachments (
MessageData::GetAttachmentText: parts that are attachments by the MIME layer's rule and whose media type is text, decoded, bounded at 1 MB) and the full-text index tokenises the same text, so an attached .txt/.csv/.html is searchable and the index never hides it (source: Common/BO/MessageData.h:63; v6.2.25 body; IMAP/AttachmentTextSearch.cs) - 6.2.28: COMPRESS=DEFLATE (RFC 4978) — both directions compressed on request, advertised while
IMAPCompressionEnabled=1 and the session is not yet compressed; zlib 1.3.1 vendored underServer/zlib(source: IMAPCommandCapability.cpp:61-62; IniFileSettings.cpp:497; IMAP/IMAPCommandCompress.cpp) -
IMAPSearchTimeout=60,IMAPSearchMaxMegabytes=2048 bound SEARCH/SORT/THREAD (source: IniFileSettings.cpp:347-349)
-
CAPA now includes
AUTH-RESP-CODE(pre2),IMPLEMENTATION hMailServer(pre2, no version),PIPELINING(pre2),EXPIRE NEVER(pre3),LOGIN-DELAY <n>(pre3, only whenPop3LoginDelaySeconds> 0) (source: hmailserver/source/Server/POP3/POP3Connection.cpp:493, 499, 508, 520, 532) - pre2: credential failures answer
-ERR [AUTH] …; inbox load failure-ERR [SYS/TEMP] …(RFC 3206) (source: POP3Connection.cpp:702-725, 795, 1027-1031) - pre3:
Pop3LoginDelaySeconds=0 (off) enforces a minimum login interval, early login refused-ERR [LOGIN-DELAY] …(source: IniFileSettings.cpp:619; POP3Connection.cpp:759). Not in README. - 6.2.14: refused-lock session no longer releases the owner's lock; RETR/TOP answer
-ERRon unreadable file;+ERRtypo fixed (source: v6.2.14 body) - 6.2.19 (Roadmap "13 August 2026"): seven POP3 conformance defects —
LIST n/UIDL nhide deleted messages,AUTH PLAIN =empty initial response, SCRAM failure firesOnClientLogon, CAPA no longer advertises USER/SASL on a connection that will refuse them (source: Roadmap.md:520 and the audit-defects table; commit ca788709b 2026-08-13) - APOP / CRAM-MD5 deliberately not implemented (need cleartext-equivalent stored secret) (source: pre2 body)
-
External POP3 fetcher: XOAUTH2 to hosts in
FetchOAuth2Hosts(defaultoutlook.office365.com), password is deliberately not a fallback (source: IniFileSettings.cpp:217; hmailserver/source/Server/ExternalFetcher/POP3ClientConnection.cpp:334; pre1 body). 6.2.15: rejected RETR no longer leaves an empty file; a downloaded message with no local recipients is removed and logged (source: v6.2.15 body) - 6.2.25:
SASL ... EXTERNALadvertised when the client certificate verified against the port's CA and names an address; the AUTH line and its SASL responses accept 12288 octets (was 500) (source: POP3Connection.cpp ProtocolCAPA_, :295-299) - 6.2.25: external accounts over IMAP —
FetchAccount.ServerType1 collects the remote INBOX once by UID (LOGIN or XOAUTH2, STARTTLS or TLS) and leaves it unlessDaysToKeepMessagessays otherwise; everything after the download is the POP3 fetcher's code (ExternalFetchClientBase) (source: hMailServer.idl:1854; Server/ExternalFetcher/IMAPClientConnection.h:32, ExternalFetchClientBase.h:27) - 6.2.27:
FetchAccount.MirrorFolders(schema 6031; CP Mirror every folder) — every remote folder collected into a local folder of the same name, byte for byte with\Seen \Flagged \Answered \Draft \Deleted, internal date and delimiter mapping, filed rather than delivered; per-folder collection record; days to keep messages -1 makes it a move (6.3.6; from 6.2.27 to 6.3.5 the IMAP fetcher read 0 that way) (source: hMailServer.idl:1889; DBScripts/Upgrade6030to6031MSSQL.sql; v6.2.27 body)
-
Advertised
SIEVEcapability today:fileinto copy relational subaddress vacation vacation-seconds imap4flags body mailbox regex ihave environment date index spamtest spamtestplus duplicate editheader variables reject ereject include enotify envelope(source: hmailserver/source/Server/Common/Sieve/ManageSieveServer.cpp:332) -
envelopeis implemented and, since 6.2.25, advertised: theSIEVEline ends... include enotify envelope(source: ManageSieveServer.cpp:331;SieveEnvelopeDeliveryfixture, 5 tests) - 6.2.15: ManageSieve disconnects after three failed authentications and registers with auto-ban; 6.2.19-era audit fix: the ban is now enforced in the accept loop (source: v6.2.15 body; Roadmap audit table "ManageSieve brute force was unbounded")
- 6.2.19:
imap4flags(setflag/addflag/removeflag/:flags) actually applied at delivery (only the five system flags), advertised (source: ManageSieveServer.cpp:198; hmailserver/source/Server/SMTP/LocalDelivery.cpp:416; v6.2.19 body) - 6.2.21:
bodytest (RFC 5173) with:text/:content/:raw; Sieve trees move/delete with domain/account rename/delete; CP writes the script only after a successful save; unimplemented constructs are refused at upload naming the construct (source: v6.2.21 body) - pre1:
variables(5229),editheader(5293),duplicate(7352),reject/ereject(5429, real DSNs),include(6609),enotify(5435 mailto only),date/currentdatewith:index/:zone(5260),spamtest/spamtestplus(5235, from the server's own verdict),ihave/environment(5463/5183),mailboxexists+fileinto :create(5490),:regexwith the runaway-pattern circuit breaker; ManageSieve response codes;RENAMESCRIPTimplemented (source: ManageSieveServer.cpp:332, 940; pre1 body; Roadmap.md:598-618) - ManageSieve listener is inert by default:
ManageSieveServerPort=0, bind127.0.0.1(source: IniFileSettings.cpp:453-454) -
virustestdeliberately not implemented (source: Roadmap.md:613)
- 6.2.13: OpenSSL stays on the 4.0 line;
_WIN32_WINNT 0x0A00(source: v6.2.13 body). Today, at 6.3.7: OpenSSL 4.0.3, Boost 1.92.0, PostgreSQL 18.6 libpq, as at 6.3.6, and on Windows x64 MariaDB Connector/C 3.4.11, where 6.3.6 carried 3.4.9 (source: build/Get-LibraryVersions.ps1:23-25 at the tags v6.3.6 and v6.3.7; section 4h; 6.3.4 and 6.3.5 carried OpenSSL 4.0.2 and PostgreSQL 18.3); alpha2 named OpenSSL 4.0.1, Boost 1.91, PostgreSQL 18.3, MariaDB Connector/C 3.4.9 (source: alpha2 body) — both bumped in 6.2.25 (OpenSSL 4.0.2 is a security patch release) - 6.2.14: DKIM
t=yfailure → neutral (was pass); all key records at a selector examined; SNI failure reports the SNI error (source: v6.2.14 body) - 6.2.15: DKIM header-name case under
simplecanonicalisation fixed (upstream PR 530) (source: v6.2.15 body) - 6.2.19: custom DNS server regression (GitHub issue 25) fixed — 6.2.16–6.2.18 could not resolve anything through
DNSServer; aDNS_ADDRentry must carry port 0, not 53 (source: v6.2.19 body; IniFileSettings.cpp:401DNSServer="") - 6.2.19 (Roadmap 12–13 Aug): post-quantum key exchange —
TlsKeyExchangeGroupsdefaultX25519MLKEM768:SecP256r1MLKEM768:X25519:secp384r1:secp256r1(source: IniFileSettings.cpp:572; Roadmap.md:424, 1398; commit 619ca858e); DANE validates the MX RRset as well as TLSA,DaneEnforcementEnabled=1 (source: IniFileSettings.cpp:403; Roadmap.md:319; commit ebc9305c9) - 6.2.21: DNS lookups whose answer Windows rejects as malformed retried once over TCP; CP MX tool uses the server resolver via
Utilities.ResolveMXRecords(IDL id 22) (source: IDL:1196; v6.2.21 body) - pre1: ACME http-01 challenge locator fixed (GitHub issue 34) — pretty-printed
"type": "http-01"never matched (source: pre1 body).AcmeEnabled=0,AcmeDirectoryUrlLet's Encrypt v2,AcmeHttpPort=80,AcmeReuseKey=1 (source: IniFileSettings.cpp:595-601) - pre2: TLS-RPT sending made verifiable —
Utilities.SendTlsRptReports(IncludeCurrentDay)(IDL id 23); refuses whenTlsRptFromAddress(default "") is unset (source: IDL:1205; IniFileSettings.cpp:483). Audit: reporter now logs at startup that statistics will never be sent while the address is empty (source: Roadmap audit table "TLS-RPT reporting is gated on an unset value") - pre2: DMARC aggregate (rua) reports — inert until
DmarcRptFromAddressset (default "");DmarcRptOrganizationName="hMailServer"; RFC 7489 §7.1 external-destination check; noruf;Utilities.SendDmarcReports(IDL id 24); data in memory (restart loses the day) (source: IniFileSettings.cpp:490-491; IDL:1209; pre2 body). CPViews/FeatureSettingsView.xaml.cs; not in README. - pre6: DMARCbis tree walk (RFC 9989 §4.10) replaces the PSL for organizational domain —
DmarcTreeWalkEnabled=1, PSL kept as fallback on transient failure; 8 queries/domain, 5-minute cache;np=tag honoured (NXDOMAIN vs NODATA); reports emit DKIM<selector>and SPF<scope>(source: IniFileSettings.cpp:639; pre6 body) - pre6: DKIM
rsa-sha1refused for signing and verifying (RFC 8301); signing domains re-signedrsa-sha256;DkimAcceptSha1=0 restores both (source: hmailserver/source/Server/Common/AntiSpam/DKIM/DKIM.cpp:974-976; IniFileSettings.cpp:642); RSA keys under 1024 bits treated as invalid (source: DKIM.cpp:268EVP_PKEY_bits(publicKey) < 1024) - pre6: SPF void-lookup limit
SpfVoidLookupLimit=2 (RFC 7208 §4.6.4) (source: IniFileSettings.cpp:640) - pre6: ACME renewal at two thirds of lifetime (one-day floor) and ARI (RFC 9773)
renewalInfoconsulted hourly (source: hmailserver/source/Server/Common/Util/AcmeClient.cpp:456-459, 934) - alpha1:
DmarcRptSchemaVersion=1 (RFC 7489);2emits RFC 9990 (dmarc-2.0namespace,versionelement stays1.0); invalid →HM6210and treated as 1 (source: IniFileSettings.cpp:506; README.md:440; alpha1 body) - ARC:
ArcSealingEnabled=0; 6.2.18 correction said relayed mail was never sealed; Roadmap says sealing is now reachable independently of author-domain signing, still gated on that setting (source: IniFileSettings.cpp:455; v6.2.18 body "Correction"; Roadmap.md "ARC sealing — corrected, then fixed"). DB settingASArcFilteringEnableddefault 0 (source: CreateTablesMSSQL.sql:995) - Inert-by-default pair stated at startup since the audit:
MtaStsHostingEnabled=1 butWebServicesHttpPort/HttpsPort=0, so MTA-STS/ACME hosting serve nothing until a port is set (source: IniFileSettings.cpp:602-607; Roadmap audit table "MTA-STS and ACME hosting were silently inert") -
SRSEnabled=0,BATVEnabled=0,AuthenticationResultsEnabled=0,ReceivedSpfHeaderEnabled=0 (all off) (source: IniFileSettings.cpp:480-481, 614, 616) - 6.2.15:
BOOST_USE_WINAPI_VERSIONraised to0x0A00(source: v6.2.15 body) - 6.2.25: ACME issuance and renewal ended the process (GitHub issue 93) — two calls handed the OpenSSL DLL a CRT
FILE*, which on Windows terminates the process for want ofOPENSSL_Applink; both now use OpenSSL BIOs, deployment runs before the TLSA line, and a certificate 6.2.24 issued but never deployed is deployed at the first check after start ("issued but never deployed" in the log) (source: AcmeClient.cpp:85,316,768BIO_new_file, :1581; v6.2.25 body) - 6.2.25:
/email.mobileconfigserved over HTTPS only — 301 to theWebServicesHttpsPortlistener, 403 when none, unlessX-Forwarded-Proto: https(source: WebServicesServer.cpp:653-659,758,807) - 6.2.27: a missing DS is proved missing (RFC 4035 §5.2, RFC 5155 §8) — the validating resolver requires the parent's NSEC/NSEC3/Opt-Out proof, signed by the parent's key, before calling a delegation unsigned; without one under a signed parent the chain is Bogus, not Insecure;
Diagnostics.DnssecChainStatusreports it (0 secure, 1 insecure, 2 bogus) (source: Server/Common/TCPIP/DnssecResolver.cpp; hMailServer.idl:3223)
- SMTP AUTH:
LOGINalways;PLAINonly if "allow plain text" is on;SCRAM-SHA-256always;-PLUSon TLS;XOAUTH2/OAUTHBEARERwhenOAuth2Enabled=1 (default 0,OAuth2RequireTLS=1) (source: SMTPConnection.cpp:2530-2552; IniFileSettings.cpp:208-209) - 6.2.14:
ES256OAuth2 tokens rejected with a clear message (source: v6.2.14 body). pre3: ES256 verifies (R||S → DER transcode), key type must match algorithm, and the algorithm must be inOAuth2AllowedAlgorithms(defaultRS256) (source: IniFileSettings.cpp:220; pre3 body) - 6.2.15 CP: Security ▸ Authentication page (OAuth2,
PreferredHashAlgorithm,MinimumAcceptedHashAlgorithm,PasswordPepper,DisableAUTHList= comma-separated local TCP ports) and Security ▸ Administrative access (admin password, 2FA setup) (source: v6.2.15 body; IniFileSettings.cpp:171, 198, 203, 674) - 6.2.19 (Roadmap 13 Aug): LDAP directory authentication incl. LDAPS with certificate validation, verified against a live Windows Server 2025 DC (source: Roadmap.md:627, 1117; commits ca788709b, d8976103b)
- 6.2.19: directory provisioning —
Settings.PreviewDirectorySync(DomainName, DisableMissing)/ApplyDirectorySync(source: IDL:785-787); domain opts in by having its AD domain name set; never deletes, at most clearsActivewhen asked; CP page + optional schedule (source: v6.2.19 body) - pre2: per-account lockout —
AccountLockoutThreshold=0 (off),AccountLockoutWindowMinutes=30,AccountLockoutMinutes=30; counts by the name guessed; locked-name refusals do not feed per-IP auto-ban (source: IniFileSettings.cpp:558-560; hmailserver/source/Server/Common/Util/AccountLockout.cpp; pre2 body). In CPViews/ServerSettingsView.xaml.cs; not in README. - pre3: app passwords (schema 6016) —
Account.AppPasswords(IDL id 42); 20 symbols from a 30-char alphabet (~98 bits); tried only after the account password fails (source: IDL:1012; DBScripts/Upgrade6015to6016MSSQL.sql; pre3 body) - pre3: per-account TOTP enforced by the server (schema 6017) — once
accounttotpsecretis set, the account password stops being a mailbox credential; app passwords remain (source: hmailserver/source/Server/Common/Util/PasswordValidator.cpp:174-189requiresSecondFactor = !pAccount->GetTotpSecret().IsEmpty(); pre3 body). Distinct from the administrator TOTP: until 6.2.24 the Control Panel checked that client-side; since 6.2.25 the server enforces it too — with[Security] AdministratorTotpSecretenrolled (Settings.EnrolAdministratorTOTP), COM needsCOMAuthentication.AuthenticateWithCodeand the REST API anX-hMailServer-OTPheader, so a stolen administrator password is no longer a complete credential (source: IniFileSettings.cpp:112; hMailServer.idl:810-811, 1720-1721; v6.2.25 body). - pre3: password policy —
PasswordPolicyMinimumLength,RequireMixedCase,RequireDigit,RequireNonAlphanumeric,RejectCommon(all 0 = off) plus the always-on "must not contain the account name"; enforced only where a password is chosen (source: IniFileSettings.cpp:666-670; hmailserver/source/Server/Common/Util/PasswordPolicy.cpp:170). Not in README. - pre3: password expiry and history (schema 6019) —
PasswordPolicyMaximumAgeDays=0,PasswordPolicyHistoryCount=0; until the self-service portal shipped in 6.2.28 (POST /api/v1/me/password) there was no self-service change, so expiry meant an admin reset; AD accounts exempt (source: IniFileSettings.cpp:671-672; pre3 body; RestApiServer.cpp:1720) - alpha1:
PreferredHashAlgorithmoutside 3, 4, 5 or 7 refused at read, PBKDF2 (4) substituted, reportedHM5528; default stays 4; 7 (scrypt, RFC 7914) added in 6.2.25 (source: IniFileSettings.cpp:190-209; alpha1 note 12) - Audit fixes (6.2.19 window): plaintext-stored passwords now upgrade on verify; POP3/IMAP/SMTP credential scrubbing in logs covers SASL initial responses and SCRAM continuations (source: Roadmap audit table)
- Not offered: SCRAM-SHA-1 (GitHub issue 49, closed not-planned — the stored PBKDF2-SHA256 output is the SCRAM-SHA-256 salted password) (source: alpha2 body)
- 6.2.25: SASL EXTERNAL on SMTP, IMAP and POP3 — a client certificate that verified against the port's CA logs on as the mailbox it names, through the addresses the certificate carries and nothing else (source: SMTPConnection.cpp:2665-2670 SendEHLOKeywords_; IMAPCommandCapability.cpp:122
AUTH=EXTERNAL; POP3Connection.cpp ProtocolCAPA_). The SMTP AUTH line limit rises from 510 to 12288 octets, RFC 5034's figure (SMTPConnection.cpp:476-483). - 6.2.25: scrypt as
PreferredHashAlgorithm=7, and schemes compared by strength rather than number (a preference for scrypt leaves Argon2id accounts alone; a minimum of either accepts both); work factorPasswordHashIterations,PasswordHashMemoryKB,PasswordHashTimeCost, a cheaper stored hash re-derived at the next logon, upward only (upstream PR 554) (source: Crypt.h:29ETScrypt = 7; IniFileSettings.cpp:206,233-235) - 6.2.25: OAuth2 JWKS and introspection —
OAuth2JwksUrl(cacheOAuth2JwksCacheSeconds=3600) as the signing-key source;OAuth2IntrospectionUrlwithOAuth2IntrospectionClientId/ClientSecret,OAuth2IntrospectionCacheSeconds=300,OAuth2IntrospectionFailOpen=0 for revocation; both off unless configured (source: IniFileSettings.cpp:265-275) - 6.2.25: tarpits —
LogonTarpitSeconds=0 on failed logons (SMTP/POP3/IMAP),SmtpTarpitCount=0/SmtpTarpitDelaySeconds=0 on stranger recipients (COMAntiSpam.TarpitCount/TarpitDelay, stubs for years); a pause on the connection's own timer, never a sleeping thread (source: IniFileSettings.cpp:675-679) - 6.2.25:
SmtpAuthenticatedSenderCheck=0 — when on, an authenticated session may only send as an address its account owns or has been granted; the post right on a mailbox's INBOX is the Send-As grant (source: IniFileSettings.cpp:734) - 6.2.25: event scripts can create only the COM classes
ScriptAllowedObjectsnames (default*); anything else fails inside the script with error 429 (source: IniFileSettings.cpp:389; Scripting/ScriptObjectPolicy.cpp)
- 6.2.11: EICAR test rewritten (ClamAV signatures match whole files) (source: v6.2.11 body)
- 6.2.13: SpamAssassin client parses responses defensively; original message preserved on any SA failure; scan ceiling clamped to the 80 MB MIME limit (source: v6.2.13 body)
- 6.2.19-era audit: spam pipeline no longer stops after one test when both thresholds are 0; a junk DKIM signature no longer erases a real failure;
b=located at a tag boundary (source: Roadmap audit table) - pre2:
AVFailAction—0(default) deliver with an error logged (historical behaviour),1hold, retry everyAVFailRetryMinutes=15 up toAVFailMaxHolds=16 then return to sender (source: IniFileSettings.cpp:537-551; pre2 body). CP yes; not in README. - pre3: quarantine (schema 6018) —
QuarantineEnabled=0,QuarantineRetentionDays=30; answers250and holds post-DATA verdicts; falls through to refusing if the store fails; release re-enters at the delivery queue; COMAntiSpam.Quarantine(IDL id 43); CP review page (source: IniFileSettings.cpp:624-625; IDL:2595; pre3 body). Not in README. - alpha1: external HTTP filter hook —
FilterHookUrl="" (empty = off),FilterHookTimeoutSeconds=10,FilterHookFailClosed=0,FilterHookRejectScore=100,FilterHookMaxMessageSizeKB=10240 (0= no ceiling); plain HTTP only; verdict is a score;HM5540non-http URL,HM5541engine down while fail-closed (source: IniFileSettings.cpp:658-662; alpha1 body). CPViews/ServerSettingsView.xaml.cs; not in README (see Contradictions). - alpha1: blocked senders (schema 6024) —
AntiSpam.BlockedSenders(IDL id 44);@= exact address, otherwise domain incl. subdomains at a label boundary; no wildcards; per-entry score default 100; null sender never blocked; matches the envelope sender; runs as spam check number one (log positions shift) (source: IDL:2596, 2815-2818; DBScripts/Upgrade6023to6024MSSQL.sql; alpha1 body) - alpha1: per-account spam settings (schema 6025) —
Account.AntiSpamEnabled(id 47),SpamMarkThreshold(id 48), delete threshold;-1= no override; act only on a score this server recorded (needs "add reason to header" on, which is the default); SMTP conversation unaffected (source: IDL:1018-1021; Upgrade6024to6025MSSQL.sql; alpha1 body) - alpha1: white-list cache actually caches, and deletes invalidate it (source: alpha1 body)
- alpha2: two bypasses closed — Received-header address selection (§4.1) and RSET/STARTTLS greeting state (source: alpha2 body)
- ClamAV timeouts:
ClamMinTimeout=15,ClamMaxTimeout=90 (source: IniFileSettings.cpp:282-283) - 6.2.25: spamd is told whose preferences to apply (
SpamAssassinUser="",SpamAssassinUserFromRecipient=0) and moving a message into the Junk folder teaches SpamAssassin spam, out of it ham (SpamAssassinLearnOnMove=0) through spamc's TELL into the local Bayes store, off the IMAP thread (source: IniFileSettings.cpp:341-354; SpamAssassinClient.cpp:69-73) - 6.2.25: the ClamAV connection test asks PING and VERSION first, names the daemon it reached, and streams its EICAR sample from memory — the button failed on every Windows with real-time protection because Defender removed the sample file first (source: ClamAVVirusScanner.cpp:136-157)
- 6.2.14: failed message copies no longer crash delivery; string SQL parameters on MSSQL/SQL CE bound from freed memory fixed; failed transaction start no longer leaks a pooled connection; message cache accounting fixed; account cache cleared on stop; domain size column fixed (source: v6.2.14 body)
- 6.2.15: restore confirms the replacement store exists before deleting; extracted copy kept on later failure;
.eml.tmporphans, bounce-file orphans, unbounded account cache fixed;BackupMessagesDBOnly(default 0) exposed on Backup & restore (source: v6.2.15 body; IniFileSettings.cpp:397) - 6.2.18: restore validates the archive before deleting the target (source: v6.2.18 body)
- 6.2.19: a colon in an account address is refused (local part becomes a directory name) (source: v6.2.19 body)
- 6.2.19:
[Settings]INI values mirrored into the database (hm_inisettings, schema 6011) so they can be administered remotely; the file still wins where both carry a value; falls back to the INI if the table cannot be read (source: hmailserver/source/Server/Common/Application/IniSettingStore.cpp:115-198; v6.2.19 body; Roadmap.md:925 "126 values, shipped 14 August 2026") - inverted in 6.3.4: from schema 6042 the row wins and the file is its copy (section 4e) - 6.2.19-era audit: deleting a group deletes its membership rows; IMAP folder-UID repair no longer aborts when the queue is non-empty; MIME parser nesting bounded (source: Roadmap.md:817, 827; audit table)
- pre1 (schema 6012): auto-reply begin date
Account.VacationMessageBeginDate(IDL id 41); rule criteria up to 2,000 chars (source: IDL:1032; Upgrade6011to6012MSSQL.sql) - pre6: quota warning
QuotaWarningPercent=90 (0disables), one notice per threshold crossing; texts inhm_servermessages(schema 6022) (source: IniFileSettings.cpp:643; Upgrade6021to6022MSSQL.sql) - pre6: archive retention
ArchiveRetentionDays=0 (never prunes; only.eml, no symlink following);ArchiveDir="" (source: IniFileSettings.cpp:262, 644) - alpha1: domain-wide out-of-office (schema 6024) —
Domain.VacationMessageOn/Subject/Message/InternalSubject/InternalMessage/VacationExternalOverride(IDL ids 51-55); "internal" = envelope sender resolves to a local account/alias (forgeable); RFC 3834 suppressions (Auto-Submitted, Precedence bulk/list/junk, List-*, X-Auto-Response-Suppress, mailer-daemon/postmaster) now apply to existing per-account replies too, with no switch (source: IDL:899-908; alpha1 note 10). Known: account vacation + Sievevacation= two replies (source: alpha1 limitation 5) - alpha1: distribution-list moderation —
DistributionList.ModeratorAddress(id 11): refused senders forwarded to the moderator withX-hMailServer-Moderation; approval = resend from an authenticated session;BounceAddress(id 12) = envelope sender for list copies (source: IDL:1365-1368; alpha1 body). Self-subscribe not included. Any credential holder can forge an approval (MAIL FROM is not bound to the account) (source: alpha1 limitation 1) - alpha1: privacy tooling, COM-only —
Account.ExportMessages(dir)(id 46, all-or-nothing) andUtilities.EraseAddressTraces(id 25;HM6250/HM6251) (source: IDL:1016, 1210; alpha1 body) - alpha1:
DatabaseStatementTimeout=30 s (0= none) — applied at connect on PostgreSQL/MySQL; reaches MSSQL/SQL CE only via maintenance-script connections; untested on MySQL/PostgreSQL (source: IniFileSettings.cpp:645; alpha1 note 7; v6.2.24 known limitation 1) - alpha1: delegated APPEND/COPY/MOVE filed under the wrong account (never shipped; fixed before alpha1) (source: alpha1 body)
- Scheduled backup knobs (pre-existing, findable):
ScheduledBackupTime,IntervalMinutes,KeepCount,MaxAgeDays(source: IniFileSettings.cpp:444-447) -
docs/WarmStandby.md(alpha1): exactly one service per database; DPAPI secrets do not travel; never rehearsed on two machines (source: alpha1 body; Roadmap.md:1437 still 🔄) - 6.2.25: message retention (schema 6027) —
Domain.MessageRetentionDaysandAccount.MessageRetentionDaysremove delivered mail older than the policy in every folder, by stored creation time, through the same deletion an EXPUNGE performs; off everywhere until a number is set;Utilities.RunMessageRetentionruns it now;MailboxRetentionTaskat start and every 6 h (source: DBScripts/Upgrade6026to6027MSSQL.sql; hMailServer.idl:854,988,1239; Application.cpp:673-680) - 6.2.25: archive index (schema 6029) — one
hm_archiveindexrow per copy, searched over COMUtilities.SearchArchiveandGET /api/v1/archive;SetArchiveHold//api/v1/archive/{id}/holdplaces a legal hold that survives the retention sweep andEraseAddressTraces;ArchiveDomains="" scopes the archive to named domains (source: Upgrade6028to6029MSSQL.sql; IniFileSettings.cpp:317; RestApiServer.cpp:2031; hMailServer.idl:1242-1243) - 6.2.25:
DeliveryHardLinks=0 — a message to several local recipients as one file with a name in each folder; every rewrite of a message file is a temporary file renamed into place (source: IniFileSettings.cpp:312) - 6.2.25: the schema enforces its parent-child relationships (schema 6030): seventeen
FOREIGN KEY ... ON DELETE CASCADE, orphans removed on the way in; columns where 0 is a value are deliberately without a key (source: Upgrade6029to6030MSSQL.sql) - 6.2.25:
BackupVerifyRestore=1 — every backup extracts its message store to a scratch directory through the restore code before it is called complete (source: IniFileSettings.cpp:480) - 6.2.25: TLS to the database is an INI line:
[Database] PostgreSQLSslMode,PostgreSQLSslRootCert,ConnectionStringOptions(MS SQL, appended verbatim),AllowUnencryptedConnection=0 (MySQL; upstream PR 559) — verified by reading, not on a live server (source: IniFileSettings.cpp:162-165; v6.2.25 known limitation 1) - 6.2.25: a Message-ID is added only for submissions (upstream PR 552); a stranded fetch account is unlocked by the task that owns it (upstream PR 603); the data-directory synchronizer walks the public folders (upstream PR 601) (source: SMTPMessageHeaderCreator.cpp:83-100; v6.2.25 body)
- 6.2.27: the Import Tool reads a Maildir (INBOX and every Maildir++ folder, flags from
:2,/;2,/!2,file names, line endings made CRLF); a message imported into an open folder appears at once;docs/Migration.mdcovers IMAP mirror, mbox, Maildir, Outlook via IMAP, upstream in place, bulk accounts (source: Tools/ImportTool/formChooser.Designer.cs:65; hmailserver/docs/Migration.md; v6.2.27 body)
- 6.2.11: alias list and live log announce content, not
+AliasRow/+LogLine, to screen readers (source: v6.2.11 body) - 6.2.12: DBSetup/DBSetupQuick/DBUpdater/DataDirectorySynchronizer/Shared → SDK-style .NET; COM consumed via checked-in tlbimp wrapper
source/Tools/Interop/; ImportTool (accounts from text file; mbox → IMAP folder viaUtilities.ImportMessageFromFileToIMAPFolder, IDL id 13) replaces the five VB6 wizards insource/Migration(folder no longer exists) (source:ls hmailserver/source/Tools; IDL:1181; v6.2.12 body) - 6.2.13: Diagnostics no longer reports every test FAILED; UI service restart is elevation-aware; DBUpdater labels 6002–6005 (source: v6.2.13 body)
- 6.2.14: Ctrl+K command palette searches settings by label and INI key (227 entries then; the generated index has 393 entries in 6.3.3 and 448 in 6.3.4); settings moved to Logging / scanner tabs / Performance ▸ Indexing / Delivery; "Advanced hardening" → "Advanced INI settings" under Maintenance (source: v6.2.14 body;
grep -con Services/SettingsSearchIndex.g.cs; MainWindow.xaml.cs, Services/NavigationMap.cs) - 6.2.15: new pages Security ▸ Authentication, Security ▸ Administrative access, Network ▸ DNS resolver, Network ▸ Web services & autoconfiguration, Advanced ▸ Copies of mail (archiving), Backup
BackupMessagesDBOnly, Diagnostics consistency-scan results; four labels corrected (UseDNSCache,DaemonAddressDomain,LogLevel,DisableAUTHList) (source: v6.2.15 body) - 6.2.16: Esc/selection in the palette no longer throws (GitHub issue 21) (source: v6.2.16 body)
- 6.2.19: directory-sync page; "pages show what is happening, including where a configuration looks enabled and is inert" (source: v6.2.19 body). Spam filtering overview page (Roadmap 13 Aug → 6.2.19) (source: Roadmap.md:1296; commit 8b5500c89)
- 6.2.21: restored from the retired Administrator: pause/resume engine, restore default ports, restore default IP ranges, delete domain, rename domain, rename account, inactive markers; MX tool via
Utilities.ResolveMXRecords; Welcome tiles centred (GitHub issue 30); DNS-cache and mailer-daemon descriptions corrected (UseDNSCachecontrols whether Windows' resolver cache may answer — there is no in-process cache) (source: v6.2.21 body; IniFileSettings.cpp:400) - pre1: Relayer page carries M365 OAuth settings; vacation begin date and criteria length editable (source: pre1 body)
- pre2: nine previously INI-only settings get fields (AV fail policy, lockout, DMARC report, POP3 OAuth host list); both OAuth host fields show the real default (a blank would have stopped M365 fetching) (source: pre2 body)
- alpha1: CP overhaul — Fluent type ramp, Segoe UI Variable, 168 MessageBox sites themed, 18 dialogs on a Fluent window, ~55 fields given accessible names; new Blocked senders page, Out of office domain tab, Windows Event Log card, PROXY protocol / XCLIENT card; sign-in no longer freezes on an unreachable host (narrowed, not closed); AccountDialog tabs reachable; duplicate tab names fixed; dark-theme chart cards; quarantine grid columns; three padded-text tables → DataGrids (source: alpha1 body)
- alpha1: COM vtable order repaired to match v6.2.21 exactly — five properties on
AntiSpam,Account,Application,GlobalObjectsmoved to the end of their interfaces (source: alpha1 note 4; Roadmap audit table) - New COM surface since 6.2.10 (all confirmed in IDL):
Settings.PreviewDirectorySync/ApplyDirectorySync(:785-787),Settings.MessageIndexing(:671),Domain.RelayHost…(:884),Domain.Vacation*(:899-908),Account.AppPasswords(:1012),Account.ExportMessages(:1016),Account.AntiSpamEnabled/SpamMarkThreshold/… (:1018-1021),Account.VacationMessageBeginDate(:1032),Utilities.ResolveMXRecords(:1196),SendTlsRptReports(:1205),SendDmarcReports(:1209),EraseAddressTraces(:1210),DistributionList.ModeratorAddress/BounceAddress(:1365-1368),Application.MessageTrace(:2362),AntiSpam.Quarantine(:2595),AntiSpam.BlockedSenders(:2596), coclassesAppPasswords,QuarantinedMessage,Quarantine,MessageTrace*,BlockedSender(s) - Control Panel + tools require the .NET 10 Desktop Runtime; the server is native (source: alpha1 note 15; csproj TFM)
- Service-account setting (
ServiceAccountName/Password) had never worked on any path; fixed 14 Aug 2026 (6.2.19) (source: Roadmap audit table; IniFileSettings.cpp:246-247) - 6.2.25: the Control Panel follows Windows High Contrast for every control (runtime, both ways); every static caption carries an Alt-key mnemonic, checked in CI; page titles are headings and status text a live region; the Welcome page starts from what you want to do; the stalled-mail diagnosis guide is a page; the status palette is held apart for colour-blind eyes by a test; "Advanced & scripting" is "Advanced" (source: v6.2.25 body; MainWindow.xaml.cs
ApplySavedTheme, :873; .github/workflows/style.yml:72check-mnemonics.py) - 6.2.28: the Control Panel is localised — resx catalogues with the English text as the key, complete translations in 17 languages (cs, da, de, es, fi, fr, it, ja, nb, nl, pl, pt-BR, ru, sv, tr, uk, zh-Hans), a language picker, and CI checkers that fail an unmarked or untranslated caption; an Updates card and three status-page buttons for the live update; every INI setting the server reads has an editor, checked by
build/check-ini-coverage.py; the password boxes' caret no longer jumps to the start (GitHub issue 156) (source: Tools/ControlPanel/Resources/Strings.*.resx; Services/LanguageChoice.cs; Views/StatusView.xaml.cs:42-44; Views/FeatureSettingsView.xaml.cs:1932; .github/workflows/style.yml:79-95)
- Listener inert by default:
RestApiPort=0, bind127.0.0.1; TLS mandatory unless loopback (source: IniFileSettings.cpp:591-594; alpha1 body) - 6.2.14: request-arrival bounded; rejected admin credential logged (source: v6.2.14 body)
- 6.2.19 (Roadmap 13 Aug): API keys —
hmapi_-prefixed bearer tokens stored as SHA-256 inhMailServerApiKeys.inibeside hMailServer.ini (source: RestApiServer.cpp:158ApiKeyTokenPrefix, :2416) - Audit fix: queue
retry/DELETEnow404for unknown ids; oldDELETEcould delete a mailbox message (source: Roadmap audit table; hmailserver/source/Server/Common/Util/RestApiServer.cpp:2944-2945) - alpha1 routes:
GET /api/v1/queue,POST /api/v1/queue/{id}/retry,DELETE /api/v1/queue/{id},GET /api/v1/srv(RFC 6186/8314 SRV records from the live port table — generates, publishes nothing),GET /api/v1/quarantine(newest 1000),POST /api/v1/quarantine/{id}/release,DELETE /api/v1/quarantine/{id},GET /api/v1/domains/{domain}/aliases,GET /api/v1/openapi.json(source: RestApiServer.cpp:1672-1774, 3116-3121) - alpha1: REST, metrics and web-services listeners accept IPv6;
::binds dual-stack;::1counts as loopback (source: alpha1 body; Roadmap.md:984) - Apple
.mobileconfigprofile served by the web-services listener (GET /email.mobileconfig, alias/mail/config.mobileconfig) — Roadmap "shipped 12 Aug 2026"; HTTPS-only since 6.2.25 (source: Roadmap.md:983; WebServicesServer.cpp:653-659) - 6.2.25 routes, read-only where COM is the writer:
GET/POST /api/v1/ipranges,DELETE /api/v1/ipranges/{id},GET/POST /api/v1/domains/{domain}/lists,DELETE /api/v1/lists/{id},GET /api/v1/domains/{domain}/dkim,GET /api/v1/certificates(never the private-key password),GET /api/v1/rules,GET /api/v1/logs,GET /api/v1/logs/{name},POST/GET /api/v1/backup,GET /api/v1/settings(snapshot),GET /api/v1/archive,POST/DELETE /api/v1/archive/{id}/hold,GET /api/v1/metrics/history— each decided at the same authorisation choke point (source: RestApiServer.cpp:1847,1930-2045; v6.2.25 body) - 6.2.28: the REST API and web services are re-hosted on
HttpServer(HTTP/1.1 on Boost.Asio with its own io_context, 4 workers, 64 connections, absolute deadlines, every API answerCache-Control: no-store);GET /api/v1/update,POST /api/v1/update/check|download|install; the self-service portal —/portal,POST/DELETE /api/v1/session,/api/v1/meand its password, vacation, quarantine, folders, messages, drafts, settings, filters and search routes, with a 16 MB / 300 s cap on the two routes that carry attachments (source: Common/Util/HttpServer.h:79-101; RestApiServer.cpp:128-136,1281-1284,1581-1738,1853-1871,2312)
- 6.2.14: logging settings consolidated on Logging (
LogLevel=9,MaxLogLineLen=500,SepSvcLogs=0,JsonLogging=0,LogDeleteDays=0) (source: IniFileSettings.cpp:251-253, 406-407; v6.2.14 body) - 6.2.17/6.2.18: per-stage acceptance timings; work-queue saturation reports name the task, session and peer IP;
AsyncQueueStallThreshold=120,AsyncQueueReservedThreads=2 (source: IniFileSettings.cpp:314-315; v6.2.18 body) - 6.2.18:
Logger::WriteLogFilerotation uninitialised-variable fix (source: v6.2.18 body) - pre1: with Keep log files open, every line is flushed to the OS as written (GitHub issue 33) (source: pre1 body)
- pre6: message trace (schema 6020) —
MessageTraceEnabled=0,MessageTraceRetentionDays=30;Application.MessageTraceCOM; CP page (source: IniFileSettings.cpp:626-627; IDL:1901-1909, 2362). Off "because it records who corresponds with whom" (source: pre6 body). Not in README. - pre6: per-domain metric labels —
MetricsPerDomainEnabled=0 addshmailserver_domain_messages_received_total{domain="…"}and_sent_total; only hosted domains are labelled (source: IniFileSettings.cpp:656; hmailserver/source/Server/Common/Util/MetricsServer.cpp:1605-1615). Not in README. - Prometheus listener inert by default:
MetricsServerPort=0, bind127.0.0.1, optional token/basic auth/TLS (source: IniFileSettings.cpp:426-437) - alpha1: Windows Event Log —
WindowsEventLogEnabled=1,WindowsEventLogLevel=2 (1 Critical, 2 +High, 3 +Medium, 4 +Low); source namehMailServer; throttle per event id; source registration is an HKLM write that succeeds under LocalSystem and fails silently under a lesser account; no message DLL (best-effortEventLogMessages.dll) (source: IniFileSettings.cpp:628-629; hmailserver/source/Server/Common/Application/WindowsEventLog.cpp:98, 255-263, 335; alpha1 note 6). Event ids 2010–2015 / 2000–2003: see Unconfirmed. - alpha1: OTLP metrics and logs —
OtelMetricsEndpoint="" andOtelLogsEndpoint="" (empty = off),OtelMetricsInterval=60 (clamped 5–3600); traces via pre-existingOtelEndpoint="",OtelServiceName="hmailserver"; metrics push carries event counters only — nine gauges only on/metrics(source: IniFileSettings.cpp:448-452; alpha1 limitation 13) - alpha1: inbound W3C
traceparentread from HTTP and SMTP message headers; never refuses mail (source: alpha1 body) - alpha1: 20 new
HMerror codes (5528, 5540, 5541, 6210, 6230, 6250-6252, 6260, 6280-6281, 6300, 6320, 6340, 6350-6351, 6360-6363) (source: alpha1 "New error codes" table) - Fault injection (INI-only, off):
SimulateSpoolWriteFailure=0,SimulateDatabaseFailureFor="" (source: IniFileSettings.cpp:422-423) -
MessageStoreConsistencyCheck=0 (source: IniFileSettings.cpp:425); Diagnostics page shows its results since 6.2.15 (source: v6.2.15 body) -
docs/DiagnosingStalledMail.md(6.2.18) — corrected in alpha1: it had told admins the custom-DNS setting was broken, which stopped being true in 6.2.19 (source: alpha1 body) - 6.2.25: the server remembers its metrics (schema 6028) — one row per metric per minute in
hm_metricsamples, keptMetricsHistoryDays=7, sampled byMetricsHistoryTaskat start and every minute, read back over COMUtilities.GetMetricHistory/SampleMetricsNowandGET /api/v1/metrics/history, shown on the dashboard for a day, a week or a month (source: IniFileSettings.cpp:735; Application.cpp:685-692; RestApiServer.cpp:1847; hMailServer.idl:1240-1241)
- 6.2.12: .NET Desktop Runtime is a server prerequisite (needed by the DB tools at post-install); .NET Framework 4.5 gate removed; tools ship as
dotnet publishfolders viabuild/build-tools.ps1; CI builds every C# project; NUnit 4 (source: v6.2.12 body) - 6.2.13: user-customised
EventHandlers.vbskept across uninstall/reinstall; failed DB create/upgrade propagates a real exit code; "Windows version too low" names Win10 1607 / Server 2016 (source: v6.2.13 body) - 6.2.17: installer smoke test on a clean throwaway machine (source: v6.2.17 body;
.github/workflows/installer-smoke.yml) - 6.2.19/6.2.20: release-signing workflow fixed; assets signed (source: v6.2.20 body;
sign-release.yml) - 6.2.20: all 28 installer dialogs take their default under
/SUPPRESSMSGBOXES; create scripts have no multi-statement commands (SQL CE) (source: v6.2.20 body) - 6.2.21: Postfix test rig
hmailserver/test/PostfixBenchin the repo (source:ls hmailserver/test) - pre6: immutable releases — assets (SPDX + CycloneDX SBOMs, cosign bundles) attached while draft; a spent tag cannot be reused;
check-db-scriptsstep builds a throwaway DB from the create script (source: pre6 body;sbom.yml) - alpha1:
prisma.yml(always-red sample workflow) removed; CodeQL extended to every shipped .NET tool; managed "Code quality" scan switched off; committed*.tlh/*.tliremoved and ignored (source: alpha1 body; workflow list has no prisma.yml) - alpha2: Control Flow Guard (
/guard:cf,<ControlFlowGuard>Guard</ControlFlowGuard>) in both configurations; Release also/Brepro(reproducible) (source: hmailserver/source/Server/hMailServer/hMailServer.vcxproj:87, 96, 164, 173) - alpha2: signed, annotated release tags (
git -c gpg.ssh.allowedSignersFile=.github/allowed_signers verify-tag v6.2.24); signing workflow refuses lightweight tags; DCO check on PRs (dco.yml);dotnet formatverify (style.yml);hmailserver/docs/RegressionEnvironment.md(GitHub issue 48) (source: alpha2 body; workflow list;ls hmailserver/docs) - alpha2: reproducible server binary — two clean builds byte-identical; v145 toolset (MSVC 14.51), SDK 10.0.26100 (source: alpha2 / v6.2.24 bodies)
- 6.2.24: NuGet lock files beside all nine .NET tool projects,
--locked-modein CI; eight required status checks on master (source: v6.2.24 body) - Installer is not Authenticode-signed (until 6.3.1, below); verification is by cosign bundle (source: v6.2.24 body)
- 6.3.1: the installer is Authenticode-signed with Azure Artifact Signing, countersigned with an RFC 3161 timestamp, publisher Progressive Robot Ltd; the release path gained a six-value readiness gate, verification of the release as attached, a required-asset assertion and per-tag serialisation (source: v6.3.1 body)
- Known then, fixed since: C++ CodeQL ran on the hosted
windows-2025-vs2026runner on every push to master, weekly and on demand (not on pull requests), and C# per push and PR, until GitHub became unavailable on 18 September 2026 (source: .github/workflows/codeql.yml:5-30, 160-169) - 6.2.25: from then until 18 September 2026 the C++ server was compiled on a GitHub-hosted runner on every push and pull request (
server-build.yml), with OpenSSL, Boost and libpq built from pinned, digest-verified sources (libraries/build-openssl.ps1,build-boost.ps1,build-pgsql.ps1) and cached; the weekly C++ CodeQL analysis ran — the second known limitation of 6.2.24 is closed; the fourteen open C++ findings dealt with; Scorecard published to the OpenSSF API;build/analyze.ps1/analyzebuild with its 632 findings triaged (source: .github/workflows/server-build.yml;ls libraries/; v6.2.25 body) - 6.2.25: a Release build that keeps its assertions (
build.ps1 -Asserts, HM6364) is the dynamic-analysis build the suite runs against before a release (RELEASE.md step 8b), and a timed fuzz run is a release step (8c) — the harness had not built since 13 August (source: RELEASE.md:84-105; hmailserver/docs/Fuzzing.md:358-361) - 6.2.26:
build/check-db-scripts.ps1executes every DBUpdater probe against the database it builds, through the provider the server uses, with a negative control; a regression fixture runs them through the COM path against the SQL CE bench (source: v6.2.26 body; build/check-db-scripts.ps1:126-156)
None of the following was in a published release before v6.2.28; all of it ships there, in the 47 commits after v6.2.27 (git log v6.2.27..v6.2.28). The schema does not move: 6.2.28 is still 6031.
-
Live update:
UpdateCheckTaskreads the release feed once a day whenUpdateCheckEnabled=1 (on demand always:Status.CheckForUpdate,POST /api/v1/update/check);DownloadUpdatefetches the installer and its Sigstore bundle and keeps the installer only when the bundle verifies (Common/Util/SigstoreVerifier.cpp — no cosign, no network beyond the download);InstallUpdatehands it tohMailServer.Updater.exe, which runs it, waits for the service, rolls back if it does not return, and reports the outcome at the next start; DBUpdater authenticates with a single-use token; fourteenUpdate*INI keys (IniFileSettings.cpp:489-509), COMStatus.UpdateState...UpdateApplyOutcome(hMailServer.idl:540-551),GET /api/v1/update, an Updates card and three status-page buttons in the Control Panel (source: Application.cpp:709-715; Server/hMailServer.Updater/; installation/section_files_64.iss:6). -
HttpProxy([Settings], default "" = a direct connection): a forward proxy for every web request the server makes as a client — the update feed, the installer and its bundle, and, through the sameHttpsClient, the OAuth2 JWKS fetch and token introspection.host:port, or[ipv6]:port; a value without a port is an error rather than a silent direct connection, and there are no proxy credentials. An https target is reached with CONNECT and the TLS handshake runs inside the tunnel under the same certificate verification as a direct connection; plain http gets the absolute URL in the request line; a proxy that says no is reported asThe proxy <proxy> refused CONNECT to <host:port>: <its status line>. The editor is on the Control Panel's Updates card beside the feed URL (source: Common/Util/HttpsClient.cpp:86-198; IniFileSettings.cpp:492; FeatureSettingsView.xaml.cs:1936). -
IMAP COMPRESS=DEFLATE (RFC 4978):
IMAPCompressionEnabled=1 advertises it; zlib 1.3.1 vendored underServer/zlib(source: IMAPCommandCapability.cpp:61-62; IniFileSettings.cpp:497; IMAP/IMAPCommandCompress.cpp). -
HTTP foundation:
Common/Util/HttpServer.cpp— HTTP/1.1 on Boost.Asio with its own io_context, 4 worker threads, 64-connection cap, absolute per-request (30 s) and per-connection (300 s) deadlines, a 64 KB request cap with a 16 MB / 300 s cap a filter grants per request; REST API and web services re-hosted on it; every API answerCache-Control: no-store; every listener still builds its TLS context throughSslContextInitializer::InitServer(source: HttpServer.h:79-101; RestApiServer.cpp:128-136,618). -
Self-service portal:
/portaland/api/v1/me*on the REST listener — credentials, browser sessions (/api/v1/session, cookie known by its hash, bounded, CSRF header), own quarantine, folders and messages (read, flags, move, delete, attachments as downloads, search, drafts, send with attachments through the SMTP-submission pipeline, Sent copy), settings and the active Sieve script; an account with a TOTP secret sendsX-hMailServer-OTP; inert whileRestApiPort=0 (source: RestApiServer.cpp:1281-1738, 7660-7677). -
Control Panel localisation: resx catalogues, 17 languages complete and held complete by CI, a language picker (
Services/LanguageChoice.cs);build/check-ini-coverage.pyproves every INI setting has an editor (style.yml:95); password-box caret fix (GitHub issue 156); and the two text editors that shipped without a default no longer drop what they could not show — saving the SSL/TLS page kept the post-quantum key exchange groups, saving the Scripting page kept every script's COM objects. -
The Control Deck reads four things: the browser page at
/gained a log list with any file's last 200 or 2,000 lines, the server-wide settings as a table, every certificate by name and file (never a key password), and the global rules in the order the server evaluates them — all read-only, all reachable with a read-only key. It is served as the bytes on disk, so its own glyphs survive, with a Content-Security-Policy that allows only its own inline script and style,X-Content-Type-Options: nosniffandReferrer-Policy: no-referrer(source: installation/WebAdmin/index.html; RestApiServer.cpp:2977-3001).
-
6.3.0 - Linux. The server builds with CMake on Ubuntu with clang or GCC, for x86-64 and AArch64; the Linux build workflow makes
.deb,.rpmand AppImage packages and installs the.debon every run; the regression suite runs against a Linux server (net10.0, the COM object model shimmed onto/api/v1/…; 756 passed, 649 skipped of 1,405 on the 6.3.0 tag).[Directories]may be relative to the program folder. The schema stays 6031. -
6.3.1 - the signed installer.
hMailServer-6.3.1-x64.execarries an Authenticode signature (Azure Artifact Signing, publisher Progressive Robot Ltd, RFC 3161 countersigned);UpdateRequireAuthenticode=1 becomes usable. Nine release-path defects fixed after executing the real workflow bodies against the live asset list - the Authenticode gate checked the wrong settings, verification could pass vacuously, runs were not serialised per tag, the SBOM could be overwritten after signing, the smoke test could not read a draft. Known and named unfixed there, fixed in 6.3.2: the 6029→6030 upgrade step's orphan sweep ran children before parents.
Everything below ships in 6.3.2 and was in no published release before it. The schema moves from 6031 to 6038 in seven steps (section 2); DBUpdater runs them.
-
The webmail became a mail client (
/portal, everything over/api/v1/me; the OpenAPI document at/api/v1/openapi.json). Reading: conversations by thread; Archive, Junk and Trash as one-key actions (a move into or out of Junk teaches the spam filter); a search written as a reader writes it (from:,to:,subject:,has:attachment,before:,after:,in:,is:unreadand the rest,label:); labels as IMAP keywords (schema 6036) that every client sees; image and PDF previews; link and look-alike-sender warnings; shared and public folders. Writing: a formatting bar and an HTML editor; a From picker over the account's identities; signature; templates with{first_name},{subject},{date}; undo send; read receipts (RFC 8098); one-click unsubscribe (RFC 8058) to public addresses only; send later and snooze (schema 6034); an address book with To-field completion (schema 6032); files above a size as links that expire and can take a password (schema 6035). S/MIME in the browser with the Web Crypto API only: PEM, DER or PKCS#12 in, the private key wrapped under the account password before the server stores it (schema 6037), signatures verified against the system's roots, encryption and decryption in the page. Pages: Settings (name, signature, theme, density, notifications, language - the Control Panel's seventeen plus Greek, Korean and European Portuguese), Away, Filters (a rules editor that writes Sieve), Contacts, Storage, Files, Security (app passwords made only with the account's own password proven again, browser sessions, support access, the S/MIME key store), held mail. The page installs as an app, keeps the inbox listing and thirty opened messages offline, queues a send without a connection, carries the server's branding and announcement (PUT /api/v1/portal/branding), imports.emland exports mbox, works with a screen reader.hmailserver/docs/WebmailShortcuts.mdlists the keys and the search operators. -
SASL GSSAPI (RFC 4752) on SMTP, IMAP and POP3, on Windows:
GssapiEnabled=1 in[Settings]; the process's own credentials on a domain-joined host, orGssapiServiceAccountandGssapiServicePassword; the client's principal names the account it is linked to, elseuser@realm. Proven end to end against a Windows Server domain controller. -
Auto-ban reaches the operating system's firewall:
AutoBanFirewall=1 keeps a Windows Defender Firewall block rule per banned address (a packaged nftables hook and a fail2ban filter and jail on Linux),AutoBanCommandruns anything else,AutoBanNeverBanlists addresses never banned. All off as shipped. -
A SURBL server's answers are judged like a DNSBL's:
SURBLServer.ExpectedResult(schema 6038; the Control Panel's SURBL editor) in the DNSBL syntax; with none set any answer counts except the 127.255.255.0/24 codes with which the Spamhaus zones refuse a query, which were taken as listings until now (GitHub discussion 167). -
Linux: Boost linked statically, so the
.deband.rpminstall on the next distribution release (Ubuntu 26.04 proven in CI); a container image on GHCR from every tag, run as its own user, smoke-tested beside PostgreSQL over TLS; the three Windows-only calls the webmail waves had used gained POSIX counterparts. -
Database: the 6029→6030 step sweeps parents before children in all four dialects, and a gate now winds a created database back to 6029, seeds orphans and upgrades it on PostgreSQL and MySQL on every push; the create scripts drop dependent tables before
hm_accounts; every statement in a MySQL script is followed by a blank line, whichbuild/check-sql-separators.pyenforces after two schema steps had broken the MySQL create script between waves 10 and 15 (caught by the gate before any release carried them); the contacts index is unique. -
A server holding a database it refused answers COM with the refusal (HM5011 text, which names DBUpdater) rather than an access violation;
hMailServer.Databasestill works, which is DBUpdater's path. -
Build and supply chain: thirty-seven of forty committed binaries left git (fetched or gathered against the manifest at build time; the COM wrapper generated from the type library);
.sigstore.jsontwins and SLSA provenance beside every cosign bundle; a Windows job re-verifies the Authenticode signatures the manifest records; Harden-Runner on every Linux job; Dependabot for Actions and .NET. -
Quality: Code Quality at zero findings; the
/Debugconsole server reachable over COM and a suite mode for it (HM_CONSOLE_SERVER=1), so native coverage can be measured; the pre-flight refuses to pass while WSL runs; the test SMTP simulator reads a large message in linear time. -
Documentation and governance:
SECURITY.mdstates the vulnerability-management thresholds;GOVERNANCE.mdnames two maintainers; the release checklist says releases are immutable once published; README describes the webmail as it is and shows the Control Panel and the webmail; the ADO type-library licence question is closed (nothing of Microsoft's is redistributed).
Everything below ships in 6.3.3 and was in no published release before it; the full suite on the stamped binary: 2,302 tests, 2,294 passed, 0 failed, 8 skipped. Each of the three batches that carried it went through the full Windows suite on its exact tree before it landed: 2,267 tests, 2,259 passed, 0 failed, 8 skipped on the first, 2,279 tests, 2,268 passed, 3 failed, 8 skipped - the three put right in the commits above it on the second, 2,301 tests, 2,293 passed, 0 failed, 8 skipped on the third. The schema moves from 6038 to 6040 in two steps (section 2): 6039 widens a domain's relay-password column, 6040 adds the CardDAV columns; DBUpdater runs them.
-
The Linux regression suite's route backlog (waves A to H, 13 and 14 September): a REST route for every family the Linux fixtures had skipped - the directories and the ini section of the settings, the logon-failure list, domain aliases, a domain read whole and renamed, an IP range updated in place, the server messages, Sieve evaluation, fetch accounts, scripting, the backup settings, an account read whole, the caches, the message index and the relayer's password - and a build object on
/api/v1/status(script_engine,argon2id,post_quantum_key_exchange). The hosted Linux run after waves A to F: 1,457 tests, 1,185 passed, 0 failed, 272 skipped; on the tree with everything on this list: 1,493 tests, 1,246 passed, 2 failed, 245 skipped - the two put right above it. Three server defects on the way: a vacation save PostgreSQL refused,ncsarefused as a log format, a negative thread id on Linux; and one on every Windows installation, found by the first honest Windows gate of the branch: a domain's relay password could not be saved (hm_domains.domainrelaypasswordwas 255 characters, a DPAPI envelope is 314; schema 6039 widens it). -
One header on every source file, the project's, in one form, put there and checked by
build/add-license-headers.py. -
Two defects the tidy branch's own gates found:
generate-com-wrapper.ps1died under Windows PowerShell the first time its MIDL path ran (MIDL's stderr became a terminating error); a COM error out of a recordset walk ended a scheduled task with a minidump and no description -ADORecordset::MoveNextcatches it now (HM5037) and the exception handler names COM errors. -
An outbound BDAT chunk larger than one send buffer no longer stalls (GitHub issue 261, reported and fixed 14 September). The connection sends 60,000 bytes at a time; the SMTP client armed the read for the reply as soon as the BDAT command - or, with PIPELINING, the envelope - had been queued, and the second buffer of the chunk was queued behind that read in a queue that starts only its head, so nothing sent it and the remote timed out. Every message over 60,000 bytes to a remote advertising CHUNKING (Gmail, iCloud, Outlook.com, Postfix) had stalled this way since outbound BDAT arrived in 6.2.28; short ones went.
TCPConnection::ProcessOperationQueue_runs once more after a read starts; two tests relay a 222 KB message over BDAT with and without PIPELINING. Before 6.3.3,OutboundChunking=0was the workaround. -
On PostgreSQL a backslash is stored as one backslash (found by the hosted Linux run of the third batch). Every value with a backslash - a Windows path in a DKIM key-file setting, a backslash in a signature, a vacation message or a rule - was stored doubled, because the escaper doubled it for PostgreSQL unconditionally, which is right only while
standard_conforming_stringsis off, and it has been on by default since 9.1. The connection reads the server's answer when it comes up and doubles only where the server escapes. Values already stored doubled stay so until saved again. - An upgrade from a schema older than 6038 works again (GitHub issue 263, found and fixed 14 September). 6.3.2's guard, which makes a database the server has refused (HM5011) answer COM with the refusal instead of crashing, covered the whole Settings object, and DBUpdater reads the script directory from it before its first script - so every upgrade from 5.x or 6.2.x stopped with "The server has not loaded its configuration" and left the database untouched. The settings object answers its directories on a refused database again, everything in the configuration is refused with the reason, DBUpdater derives the script directory from its own location if the server cannot answer, and a regression test walks its path over COM against a refused database.
- The live update from an open Control Panel: the first real update, 6.3.1 to 6.3.2, failed twice with Inno's exit code 5 because the Control Panel that started it kept its own files open. The helper now ends whatever runs from under the installation before each installer run, the Control Panel closes itself once the helper has started, and a failed run's outcome quotes what the installer's own log says went wrong. An installation on 6.3.1 or 6.3.2 must be updated by hand once, since the helper that runs a live update is the one already installed.
-
CardDAV (RFC 6352) on the web-services listener: the account's address book to phones and desktop clients, found by
/.well-known/carddavdiscovery, over HTTPS with the account's password or an application password; the card a client sends is the card it reads back. -
The webmail rebuilt to the shape of Gmail and Outlook.com: a reading pane (right, below, off), a list with sender, subject, snippet, time and hover actions, a docked compose window with the reply written under the message, conversation cards, tabs (Primary, Social, Promotions, Updates, Forums - the server says which from the header) or Focused and Other, mute, pin, block, sweep, drag-and-drop to folders, a right-click menu, select-all across a whole folder, an attachment reminder, shift-click and ctrl-click selection, search history, pop-out windows, and twelve more search operators (
cc:,bcc:,filename:,larger:,smaller:,older_than:,newer_than:,is:muted,is:pinned,category:,-word,OR). Twenty languages throughout. -
The Linux control panel, measured and moved:
build/check-deck-parity.pycounts every field the desktop Control Panel writes against the REST API and the browser Control Deck (335 properties; 240 writable over REST and 153 reachable in the Deck at the start of the day, 306 and 248 by its end). New write routes: the anti-virus settings group, sixteen more account fields, nine more domain fields, the distribution list's settings and aPUTfor it, and DNS blacklists, SURBL servers, white-list addresses, blocked senders and incoming relays as resources. The Deck gained a harness of its own (build/check-deck-script.py, in CI), full domain editing, an IP-ranges view, and fetch-account and backup views. -
The webmail's next six (the fourth batch):
has:link(an address in the text or anhrefin the HTML) andin_reply_to:as search operators; a contact's name completing tofrom:<address>under the search box; follow-up flags with a date ($FollowUpand a$Due-YYYY-MM-DDkeyword on the message, so other clients still see a flag), a Starred view sorted soonest first and a reminder once a day; nudges - "Received N days ago. Reply?", "Sent N days ago. Follow up?"; quick steps (move, mark read, label, forward in one press, on the digit keys); and Clean up conversation, deleting what a later message quotes whole. The page's harness runs 224 checks. -
The Control Deck to parity (the fourth batch): the cache ceilings and lives, an IP range's expiry, blocked attachments and the greylisting white list as resources,
abort_spam_flagged,user_interface_language, and views for the account in full, distribution lists, aliases, the seven small collections, server messages and the scripting, cache and indexing groups - every property writable over REST that a Deck view covers, it reaches (322 of 328); the census itself corrected for what it had misread. The Deck's harness runs 292 checks. -
The Linux suite's last route families (the fourth batch): application passwords administered under
/api/v1/accounts/{address}/app-passwords, folder permissions under.../folders/{id}/permissions, and the message object - a folder's live collection, a message as a row with every header, a raw.emladded on APPEND's path, flags on STORE's, a delete on EXPUNGE's - with the shims rewritten. The hosted Linux run on that tree: 1,520 tests, 1,289 passed, 0 failed, 231 skipped. - Roadmap2.md: the programme after 6.3.2, linked from Roadmap.md.
Everything in this section is in 6.3.4, and none of it in 6.3.3. 6.3.4 is build 43, at schema 6104, sixty-four steps from 6.3.3's 6040; it is Authenticode-signed and carries no Sigstore bundle, so every server installs it by hand (Upgrading hMailServer 18.4c), and its Linux packages are x86-64 only. It is what landed on master after v6.3.3: first in four batches - the first checked against the code at commit 916dec6a1 of 15 September 2026, the second (everything from the settings store on) against 3233b0cc4 of the same day, the third against 8741806f4 and the fourth against a32bfd93b, both of 16 September - then in the batches the project's commits call batch 12 to batch 33, from 17 to 24 September, then in batches 34 to 39, from 24 to 26 September, and last in batch 40, on 26 and 27 September, which closes this section; the tag v6.3.4 is its last commit, 51e2dbda0. The repository's CHANGELOG.md section 6.3.4 is the version-by-version summary of the same work, and the repository document named beside each item is the operator's account of it.
The full Windows regression suite on 3233b0cc4: 2,410 tests, 2,402 passed, 0 failed, 8 skipped (seven explicit stress tests and one ignored). The Linux suite on 00bec4412, the same server with its Linux test shim corrected: 1,606 tests, 1,326 passed, 0 failed, 280 skipped.
In the first two batches the schema moves from 6040 to 6045 in five steps (section 2): 6041 adds hm_calendars (one row per calendar collection) and hm_calendarobjects (one row per event or task, with its UID, its ETag, the span it occupies and a sync counter), both cascading from hm_accounts; 6042 changes no table - the database becomes the settings store; 6043 adds the audit trail and the alerts; 6044 the domain transforms; 6045 remote-domain policies. DBUpdater registers them (new UpgradeScript(6040, 6041) to (6044, 6045)), and the server then required the last (REQUIRED_DB_VERSION 6045). There are no new [Settings] INI keys, and from 6042 there can be none - section 7 is closed; the second batch's new settings are rows in hm_settings. The third batch takes the schema to 6049 (6046 sender lists and digests, 6047 roles, 6048 DKIM rotation, 6049 bounces and the suppression list) and the fourth to 6059 (6050 the OpenPGP key store, 6051 webhooks, 6052 country blocking, 6053 sessions and devices, 6054 Web Push, 6055 complaints, 6056 mailbox move, 6057 the reputation watch, 6058 spam traps, 6059 the backup passphrase); REQUIRED_DB_VERSION was 6059 there; batches 12 to 33 take it to 6096 (below).
-
CalDAV (RFC 4791), beside the CardDAV that shipped in 6.3.3 and on the same web-services listener, with the same
/.well-known/discovery and the same credentials (the account's password or an application password). One calendar per account at/dav/calendars/<address>/calendar/, made the first time it is asked for, advertisingVEVENTandVTODOin the one collection - so a phone's calendar and its reminders are the same thing. Three reports:calendar-querywith a time range (the store narrows on the span it keeps per object before anything is parsed),calendar-multiget, andsync-collection(RFC 6578 - the collection's counter is the token, tombstones are kept so no token expires, and a token ahead of the collection is refused withDAV:valid-sync-token). ETags are the SHA-256 of the bytes served;If-Matchthat does not equal the current ETag is 412, andIf-None-Match: *creates or is 412. A PUT must be one VCALENDAR of VEVENT or VTODO components sharing one UID, and no other resource of the calendar may hold that UID. Not there, and said plainly: iMIP (an invitation arriving by mail is not turned into an event, and accepting one sends no reply), free-busy, a per-calendar time zone (calendar-timezoneis answered 404 rather than guessed at), partialcalendar-data, and a time-range filter on an alarm. Aparam-filterin a query is not evaluated. -
An iCalendar reader and writer of this project's own (RFC 5545) under
Common/Util/ICalendar.cpp, bytes in and bytes out, on the shape of the vCard module: the component tree with the folding, quoting and escaping rules, andRRULEexpansion forFREQdaily, weekly, monthly and yearly withINTERVAL,COUNT,UNTIL,BYMONTH,BYWEEKNO,BYYEARDAY,BYMONTHDAY,BYDAY,BYSETPOSandWKST,EXDATEremoved,RDATEadded and aRECURRENCE-IDoverride put in place of the instance it names. Expansion runs in the wall clock of the DTSTART's zone and is converted instance by instance, so a 09:00 weekly meeting stays at 09:00 across a daylight-saving change; the zone comes from the object's ownVTIMEZONEwhen it carries one, and otherwise from a built-in table of 592 IANA and Windows zone names, whose rules are those of 2026 rather than their history. Every expansion is bounded - a ceiling on instances, on periods scanned, on empty periods in a row, and the year 9999 - so a rule written to spin cannot hold a worker thread. What it will not approximate it refuses:FREQ=SECONDLY,MINUTELYandHOURLY,BYHOUR,BYMINUTEandBYSECOND,RANGE=THISANDFUTURE, and aTZIDthat is neither in the object nor in the table. -
A configured-but-unusable CalDAV redirect no longer falls back to this server. Giving
/.well-known/caldava built-in target turned aCalDavRedirectUrlthe administrator had got wrong - one with no scheme, say - from a 404 withHM5780in the log into a silent redirect to the calendar on this server, so clients meant for another server were quietly sent here while everything appeared to work. A configured value that cannot be used is refused with 404 again, and the fallback applies only when nothing is configured at all. -
The webmail's next twelve. The page installs as an app, with the unread count on its icon, a share from another app arriving as a new message, and
mailto:links opened here once the reader says so; contacts imported and exported as vCard 3.0 or CSV, so an address book can come in from Google Contacts or Outlook and go out again; a search saved with a name and kept as a folder; the reader's own time zone chosen in Settings, so a message's time is their time; Undo on three more actions (a label taken off a message, a rules change, a contact deleted); the blocked-tracker notice counting what it kept out and naming the hosts; swipe actions and snooze presets the reader chooses; three stars - the star pressed again cycles amber then red then none, carried as the IMAP keywords$Star2and$Star3on top of the flag every client already shows; rows coloured by a rule, which is Outlook's conditional formatting;@mentionscompleted from the address book in the plain-text editor, which add the person to the recipients; a formatted signature (bold, italic, underline, a link, and an image kept as a data URI - PNG, JPEG, GIF or WebP; the page takes an image of up to 200 KB, but the server refuses a signature longer than 20,000 characters, and base64 spends four characters on every three bytes, so an image of more than about 15,000 bytes cannot be saved) beside the plain one; and Report phishing beside Junk, which sends the message's source as an attachment to the postmaster of the reader's domain.hmailserver/docs/WebmailShortcuts.mdis corrected for the star key. -
The webmail in Arabic, Hebrew and Persian, with the layout mirrored - twenty-three catalogues beside English now, of 631 texts each. The stylesheet is rewritten in logical properties so one sheet serves both directions; the direction-bearing icons are flipped; a message's own text, its subject and what the reader writes are
dir="auto", so a Latin message in an Arabic page still reads left to right; and the page is served with its<html>tag already carrying the rightdirandlang, so there is no left-to-right first frame. The language is the one chosen in Settings, or the one the browser asks for throughAccept-Language. -
The browser Control Deck in twenty languages - the webmail's twenty that are read left to right - from catalogues of its own, 501 texts each, at
hmailserver/installation/WebAdmin/languages/<code>.json. The Windows installer ships them beside the page, CMake installs them into/usr/share/hmailserver/WebAdminand the AppImage carries them; the server serves one atGET /deck-lang/<code>.json, unauthenticated as the page at/is, because the sign-in card is in the reader's language before there is a session. The language is the browser's, or the one chosen in the switch the header and the sign-in card carry and that browser remembers, with English - which needs no catalogue and no request at all - as the fallback for anything a catalogue lacks. A settings key's caption and a refused route's sentence stay the server's own words inside a translated wrapper.build/check-deck-languages.pyholds every catalogue to the page, and the page's own harness,build/check-deck-script.py, ran 336 checks when the languages landed, from 292, and runs 458 at00bec4412. -
The desktop Control Panel on one design system, in four waves in a day: the tokens, the ten scaffold components and the shell; then every page; then the server settings page, the largest of the ten the settings are split across, with its twenty-nine tabs; then the feature settings page, all eighteen dialogs, the message boxes and the navigation palette. The shell is a command bar (the sidebar toggle, the brand, the Ctrl+K search, a server status pill reading Not connected / Connected / Reconnecting… / Connection lost, the theme toggle and the administrator's menu) above a sidebar that collapses to a rail of one glyph per group below 1,000 pixels of window width, by choice above it, the choice remembered in the registry beside the window bounds. Every dialog is sized to its content within the dialog tokens instead of to a pixel width and height typed into its constructor. What did not change is the part anybody has learned or scripted: the navigation map, every page title, every tab header, every setting caption and every automation id. Thirteen new texts, translated in each of the seventeen catalogues (3,403 now, from 3,390). The contract is written down at
hmailserver/docs/ControlPanelDesign.md, and a test holds the document to the tokens file and the components, so a token or a component that is not in the document does not build. -
Every property the desktop Control Panel writes is now writable over the REST API.
build/check-deck-parity.pycounted 330 such properties at916dec6a1and found 330 of them writable over REST - the census reaches zero - with 322 reachable from a Control Deck view; the eight that were not were folder permissions, application passwords and the group and member names (365, 365 and 359 at3233b0cc4- the second batch below). The routes that closed the last of the gap, which are also the three the Linux regression suite still skipped for want of one: account groups (GET/POST /api/v1/groups,GET/PUT/DELETE /api/v1/groups/{id}, and its/members), a message copied into another folder of its account (POST /api/v1/accounts/{address}/messages/{id}/copy), and a rule criterion tried against a value without a rule (POST /api/v1/rules/match). CalDAV addsPOST /api/v1/calendar/expand, which expands a recurrence rule through the same module the server uses. The census then read 58 desktop pages and 227 routes, 104 of them writes; at3233b0cc4it reads 242 routes, 110 of them writes (the second batch below). -
The winget and Chocolatey manifests (
ProgressiveRobot.hMailServer; on Chocolateyhmailserver-progressiverobotsince 20 October 2026, the idhmailserverbeing the original hMailServer's, and neitherwinget install hMailServernorchoco install hmailserverthis project's). The manifests are rendered bybuild/make-package-manifests.ps1from templates underhmailserver/installation/wingetandhmailserver/installation/chocolatey, and the hash in them is computed from the asset the release actually published, downloaded and required to carry a valid Authenticode signature - not from the binary the build produced, because signing changes the bytes and for 6.3.3 the two differ. A GitHub workflow,package-managers.yml, was to render and check them on every release; it arrived on 15 September 2026, after thev6.3.3tag, so no release ran it, and it was not ported to GitLab.hmailserver/docs/PackageManagers.mdsays what a user types and why an unattended install deliberately leaves the administrator password unset. Listed in Chocolatey's community repository since 23 October 2026; not yet in winget's: a new package identifier is reviewed by a person at each of them. winget's first submission is made by hand and has not been made yet; Chocolatey's was made by thechocolatey-publishjob, which pushedhmailserver-progressiverobot6.3.7 on 20 October 2026; a moderator returned it on 21 October 2026, asking that its authors also credit the original hMailServer's author, Martin Knafve; the job sent 6.3.7 again on 22 October 2026, saying that it is the fork but not yet crediting him, and a third time on 23 October 2026, with the credit, and a moderator approved it at 11:56 UTC that day, so thatchoco install hmailserver-progressiverobotinstalls it (hmailserver/docs/PackageManagers.md). -
Two command-line clients over the REST API, one vocabulary between them.
hmctlis one Python 3 file with nothing but the standard library behind it, which the.deband the.rpminstall as/usr/bin/hmctl; theHmailServerPowerShell module beside it is the same vocabulary in PowerShell's grammar. Both speak REST rather than COM, so both work against a Windows server, a Linux server, or one on another machine. Domains, accounts, aliases, lists, groups, rules, routes, listeners, IP ranges, certificates, the queue, held mail, the settings groups, the logs and the backup; accounts imported from and exported to a CSV file, with a dry run that changes nothing and the rule that an address which already exists is left alone rather than overwritten - though against a real server the export's name, size and administrator-level columns are always empty, because a domain's account listing answers only the address and whether it is active, an import drops anameoradmin_levelcolumn without a word (names go in asfirst_nameandlast_name), and a password of digits alone, oryesortrue, is sent as a number or a boolean and refused as missing.--insecureis refused anywhere but the loopback. Neither carries a message store, backups of its own, or the administrator password.hmailserver/docs/CommandLine.md, and Command Line here. -
A Helm chart and a Compose file beside the container image. The Compose file runs the server beside a PostgreSQL 16 the way the image's own smoke test does - the two passwords as Compose secrets from files rather than environment variables, the REST listener bound off loopback and therefore over TLS with a certificate and key mounted in, the three volumes the image declares, the image's own health check. The chart is a StatefulSet of one, because a message store is not something two pods may share, with its three volumes as claim templates, a LoadBalancer carrying the mail ports (
externalTrafficPolicy: Local, so the server sees the client's address), the passwords as a Secret it makes or one it is pointed at, and the database pointed at and never bundled - it refuses to render without a host. A workflow lints and renders the chart and resolves the Compose file on every change to either. -
Nightly fuzzing. The three MIME harnesses under
fuzz/ran every night on a hosted Windows runner of GitHub Actions from 15 September 2026 until the account was suspended on 18 September; in 6.3.4 the same job islinux-fuzzon the project's own Linux runner, nightly once the owner creates its pipeline schedule, and it had not run there when 6.3.4 was made (hmailserver/docs/ContinuousIntegration.md). On GitHub: every kept input and every committed reproducer replayed first, then each harness for forty minutes with two workers. The corpus is kept in the Actions cache under a key carrying the run id, so each night saves a new entry and the next restores the newest - the corpus grows. A crash, a hang or a violated invariant fails the job and the reproducer is kept as an artifact for thirty days. The release checklist's timed run on the release tree stays. -
The native server's line coverage, measured for the first time: 73.48% - 67,790 of 92,260 lines under
hmailserver/source/Server, OpenCppCoverage 0.9.9 over the whole Windows regression suite driven against the 6.3.3 binary run as a console server. The OpenSSF silver level asks for 80%, so that row stays open - but it is a number now rather than a blank. Three things had to be fixed before a run was possible at all, and they are the first three of the fixes below. -
Fixes. The server reads its mode from the last non-empty token of its command line: OpenCppCoverage appends a space to its child's arguments, so
hMailServer.exe /Debugarrived as/Debugand an empty token, matched no mode and ended at once saying nothing. The suite's DNS set-up fixture no longerIgnores a whole run under a console server - raised from a[SetUpFixture]that ignored all 2,302 tests in three seconds; the launcher writes the resolver into the ini before the console server starts and the fixture checks that it is there. The full-text indexer no longer logs a foreign-key refusal (HM5032) for a message expunged between its listing and the save of its terms: the term insert selects the message's ids fromhm_messagesitself, so a message that is gone inserts nothing and the statement succeeds. And the container image's smoke test could never have passed - it read the SMTP banner withhead -c 120when the banner is about thirty bytes and the server then waits for a command rather than closing, so the read blocked untiltimeout 5killed it and the step died with a bare exit code 124 on a job that had already built and pushed the image; it reads one line now, and a banner that is not the server's fails in words. - Sample data and the project's own name. The original author's name is out of the sample data - the address-list parser's self-test, the IMAP FETCH fixtures and the rest - and the maintainer's is in. No behaviour changes; it is listed here because a reader who greps the tree will see it.
The second batch, checked against 3233b0cc4:
-
The database is the settings store, and
hMailServer.iniits copy (schema 6042). The 238 keys of the[Settings]section are read from and written tohm_inisettings, and the section in the file is a copy the server keeps current where it may write the file - underNT SERVICE\hMailServer, to which the file is read-only, the server writes no line there and the copy is not refreshed (The Service Account). At each start the stored row decides: a key the table has never seen is adopted - on the first start at 6042 that is the whole migration, with no flag and nothing to run by hand; a line that still holds what the server last wrote is brought back into line where the file may be written; a line a person has edited is ignored in favour of the stored value and named in the error log asHM5804with what to do instead, once, and put back where the file may be written; and a row whose line has gone is kept rather than dropped, its line written back where the file may be written, so deleting a line no longer returns a setting to its default -Settings.DeleteIniSetting,DELETE /api/v1/settings/ini/{name}or the Control Panel does that, removing the line and then the row where the service may write the file, and where it may not and the file has the line keeping the row at the key's default, or refusing the removal ofPasswordPepperand of a key this build does not read. A write stores the row first and the file second, where the file may be written; a write of the file that fails isHM5806, and the stored value still counts. The Control Panel reads these settings over COM, falling back to the file, and writes them over COM only - without a connection it says that settings are stored in the server's database rather than writing a file whose edit would not be used.[SettingsOverride]is the deliberate door: a key there is applied over everything the store holds, is read even when the table cannot be, and is named asHM5807at every start; nothing but a person writes that section, and saving a value an override shadows is stored and reported asHM5808. Where it may write the file, the server writes a[SettingsStore]section of two keys,Store=databaseand aReadMesentence saying all this. If the table cannot be read the server runs on the file's copy and says so (HM5802).PasswordPepperstays in the file only: never adopted, never served from the table, written only to the file when set over COM or REST - refused with a sentence where the service may not write the file, so that underNT SERVICE\hMailServeran administrator sets it with an elevated editor and restarts the service - and left out of the backup; the row the mirror has held since 6011 is removed at the first start once the file holds the value. Values are stored trimmed, as the file reads them back.[Directories],[Database],[Security]and[GUILanguages]are read before the database is open and stay in the file; so do[SendingLimits],[SendingLimitsOverrides]and[LDAP], which are read outsideIniFileSettings.build/check-ini-coverage.pycompares the keys the server reads withbuild/ini-settings-baseline.txt- 263 keys, 238 of them under[Settings]- and refuses a new[Settings]key with the rule that a setting belongs inhm_settings. The regression suite changes settings through the store too, andbuild/check-test-settings-writes.pyinstyle.ymlrefuses a test that writes[Settings]into the file any other way. -
An audit trail (schema 6043): one row in
hm_auditper administrative change - who (Administratorover COM;administratororkey:<id>over REST), what (object type and name, created, updated or deleted, and for a server setting the value it had beside the value it was given), when, the peer address for REST and the Deck (none for COM, whose calls carry none the process can see), and over which interface. It is written from below the interfaces, at the statement choke point and inPropertySet, so COM and REST are covered by construction, and the actor is installed once per interface, which is also what keeps delivery, retention, greylisting and the index out of it. A value whose column or setting name contains password, secret, token, key file, hash, salt, pepper, passphrase or the like is recorded as(changed). Switching recording off is itself recorded. Each row carries the SHA-256 of the row before it and its own;GET /api/v1/auditpages and filters by who, what, when and action, andGET /api/v1/audit/verifywalks the chain and tells an edited row from a removed one - both for the administrator password only, an API key of any scope answered 401. The Control Deck has an Audit trail page with filters, paging and Verify the chain.AuditTrailEnabled=1,AuditRetentionDays=0 (keep everything). Not recorded, and said plainly: five of the table names it watches are spelt differently from the tables the server writes (hm_domainaliases,hm_incomingrelays,hm_whiteaddresses,hm_greyliststats,hm_blockedsendersagainsthm_domain_aliases,hm_incoming_relays,hm_whitelist,hm_greylisting_whiteaddresses,hm_blocked_senders), so a change to a domain alias, an incoming relay, the white list, the greylisting white list or a blocked sender leaves no row;hm_aclandhm_remotedomainpoliciesare not watched; the 238 settings inhm_inisettingsare not recorded; and the twelve syslog properties set over COM install no actor -build/check-audit-choke-point.pyreports them, and a remote-domain policy deleted by id over COM, as thirteen failures, and no workflow runs it. Reads are not recorded. No Control Panel page, and noauditverb inhmctlor the PowerShell module. -
Alerts (schema 6043): a condition is a row in
hm_alertrules- on or off, a threshold, mail and/or webhook, a cool-down, whether it waits for the digest - an event a row inhm_alertevents, andAlertTask, once a minute, is the only thing that sends, so whatever notices a problem never waits on the delivery queue. Six conditions ship:backup.failedandcertificate.expiring(7 days before a configured certificate's notAfter) on;disk.low,queue.stalled,autoban.storm(25 bans in an hour) andminidump.writtenoff. Mail goes through the server's own submission path toAlertRecipientfromAlertSenderAddress, and nothing is mailed while either is empty. Against flapping: a condition already raised is not raised again, an occurrence is bounded by its rule's cool-down, pastAlertMaxPerHour=20 everything goes into the digest, and the digest (AlertDigestEnabled=1, atAlertDigestHour=7 UTC) is not sent empty. A webhook body is signed HMAC-SHA256 over<timestamp>.<body>, and a rule that calls a webhook is refused without a secret; a failed delivery is retried on the next pass, then after 1, 5 and 15 minutes, and with the defaultAlertWebhookMaxAttempts=5 dead-lettered at the fifth failure (60 minutes between any attempts a larger number allows), the event kept andHM6543recorded once. Routes:GET /api/v1/alerts/rules,PUT /api/v1/alerts/rules/{condition}(the secret write-only),GET /api/v1/alerts/events,POST /api/v1/alerts/testandPOST /api/v1/alerts/run- the administrator password only, a key answered 401. The nine settings arehm_settingsrows in the REST settings table, and so on the Deck's Settings page. Not there: a Control Panel page for the rules, andalertsverbs inhmctlor the PowerShell module. -
What a domain does to a message (schema 6044), three things per domain, all off on every existing and every new domain, and while off nothing extra is read, queried or written. The external-sender tag:
external_tag_subjectputs the tag (external_tag_text, at most 100 characters; empty is[EXTERNAL]) at the front of the Subject, which breaks a DKIM signature the sender made over it as the anti-spam subject prefix always has;external_tag_headerstampsX-hMailServer-External: YES, which breaks nothing and which the webmail shows as a warning line above the message ("This message came from outside your organisation."), beside the "External sender" badge it puts on any message whose sender's domain is not the account's, stamped or not. Both go on the recipient account's own copy at local delivery, above its rules and Sieve script. The first-contact note:first_contact_tipstampsX-hMailServer-First-Contact: YESwhen an outside sender writes to an account that has had mail from others but never from them, answered fromhm_knownsenders(one indexed read per delivered copy, and a write), which also learns the recipients of what the account sends from an authenticated session; the webmail shows the stamp in the same box, as "You do not usually get mail from this sender."; a new installation, or the day the switch goes on, remembers nobody. The disclaimer:disclaimer_enabledappendsdisclaimer_plain_textanddisclaimer_htmlto mail leaving the organisation, in place in the text and HTML parts, before DKIM signing, so this server's signature covers it. Never to a signed or encrypted message (multipart/signed,multipart/encrypted,application/pkcs7-mime,application/x-pkcs7-mime,application/pgp-encrypted,application/pgp-signature- it goes out as written, with a line in the application log), an automatic reply, a bounce, a list posting, a message with no recipient outside, one already stampedX-hMailServer-Disclaimer, or a message relayed in from outside that merely claims a hosted domain; a reply quoting the footer, bold words and all, gets no second copy. Outside is one function,MessageOrigin::IsExternalSender: not authenticated (read from the topmostReceived:header, which this server wrote), not handed over by a trusted incoming relay, and the envelope sender or theFrom:at a domain this server does not host. Set in the Control Panel's domain dialog (Tagging and disclaimer), the Deck's domain editor,PUT /api/v1/domains/{name}or COMDomain.ExternalTagSubjecttoDomain.DisclaimerHTML. Not there, and said plainly: it is not anti-spoofing - a forgery of a hosted address in both envelope andFromcounts as inside; an inbound gateway listed as an incoming relay turns the tag off for everything it hands over; and the disclaimer, like the domain signature beside it, is added only on the SMTP accept path, so mail sent from the webmail or by send-later leaves without either.hmailserver/docs/DomainTransforms.md. -
Remote-domain policies (schema 6045): what this server will do for one named remote domain or a pattern (
*.example,*), as one record - the Control Panel's Remote domains page beside Routes, the Deck's Remote domains view,GET/POST /api/v1/remote-domains,PUT/DELETE /api/v1/remote-domains/{id},GET /api/v1/remote-domains/effective?domain=andPOST /api/v1/remote-domains/verification-cache/clear(server-wide; a domain-restricted key is refused), or COMSettings.RemoteDomainPolicies. The most specific active record governs (an exact name over a pattern, a longer pattern over a shorter, an inactive record skipped), and one delivery to several domains takes the strictest combination. Outbound TLS isnone,encrypted(STARTTLS must succeed; the certificate is not judged),verified(the chain and the name) ordane(a DNSSEC-validated TLSA record); it can only raise what MTA-STS and DANE already require, it applies to routed deliveries too, and a delivery that cannot meet it is deferred with4.7.0and a sentence naming the policy - never sent in the clear. Inbound TLS: a sender at the domain on an unencrypted session is answered530 5.7.0atMAIL FROM, except an authenticated submission or a configured incoming relay. Limits: a message over the largest size is bounced at once with5.3.4and no connection is made; past the connection ceiling or the messages-a-minute limit delivery is deferred with4.4.5. Out-of-office replies, an account's forward and a Sieveredirectto the domain can each be switched off. The recipient callout, for a domain this server is a backup MX for: atRCPT TO, last of the refusals and only for an unauthenticated session, anEHLO,MAIL FROM:<>,RCPT TOprobe to the configured host or the domain's MX hosts (never this server itself); a 5xx refuses the recipient with550 5.1.1and every other answer, or none, accepts; timeout 10 seconds (at most 60), verdicts cached 60 minutes, 10 probes a minute per domain. Nothing is inhMailServer.ini, and with no record delivery is unchanged. Not there: a per-domain message format (Exchange's rich-text and character-set choices), and the policies are not in thehmctl configdocument.hmailserver/docs/RemoteDomains.mdwarns that a callout can enumerate a third party's addresses. -
The built-in backup carries an account's own stores - not everything an account has: its passkeys, the shares of its calendars and address books, its recoverable and expiring mail, its IMAP annotations and the out-of-office memory stay out, each with its reason in
AccountStores.cpp'selsewhere_. The archive held what the business objects'XMLStoremethods write, and every per-account table added since 6.3.0 cascades fromhm_accountswhile a restore callsDomains::DeleteAll()first - so restoring an archive deleted an account's address book, webmail preferences, scheduled sends and snoozes, file links, S/MIME keys and calendars, none of which it held, and a message's keywords (labels, the second and third stars, follow-up flags, mute, pin) were lost the same way.AccountStoresis one declaration of each table, its columns and their roles, which the backup reads and the restore writes through:hm_contacts,hm_accountprefs,hm_scheduled,hm_files,hm_smimekeys,hm_passwordhistory,hm_calendarswith each calendar'shm_calendarobjects, andhm_knownsenders, plus aKeywordsattribute on every message. No identifier crosses the archive: a scheduled send names its folder by path and its message by UID and is resolved against the rows the restore has just made, and one that does not resolve is dropped and named in the backup log. A value an XML attribute cannot hold is written as base64 under the name with.base64. An archive written before this has none of it and restores exactly as it did - it does not gain what it never held. The verified restore (BackupVerifyRestore) now counts each table's rows against what the archive reads back and discards an archive that falls short.build/check-account-stores.pyinstyle.ymlfails when a table that reacheshm_accountsis neither carried nor named as carried elsewhere with a reason (84 checks, 23 per-account tables), and a test per store makes its data through the surface that writes it - the self-service API, CardDAV, CalDAV, IMAP - backs up, deletes the domain, restores and finds it.hmailserver/docs/Backup.mdlists what the archive holds and what it deliberately does not - though its table of what is left out does not yet name the password pepper, which the settings store keeps out of every archive (section 5, item 20). -
SPF is an implementation of RFC 7208, ported from upstream hMailServer's PR 645 (their commit
1beaeab9of 13 September 2026) and replacing RMSPF 1.10, the vendored RFC 4408 library, which did its own name resolution throughDnsQuery_A. SPF now asks the server's own resolver, so on Windows it followsDNSServer, the DNS cache setting, the query timeout and the DNSSEC gate a DKIM key or a DMARC policy passes, and on Linux it works at all - the POSIX build had answered every query with TempError (HM6406), so DMARC there had no SPF half. 127.0.0.0/8 and::1no longer get an unconditional pass before any policy is read. All seven results are written under their own RFC 8601 keyword - none, softfail, temperror and permerror used to be written as neutral - while scoring is unchanged: only a fail scores.SpfVoidLookupLimitstill overrides the RFC's two. The openspf.org conformance suite for RFC 7208 (release 2014.04, 203 cases, BSD-licensed, vendored underSMTP/SPF/Conformance) runs inside the server's internal tests and on Linux asspf-testsinlinux-build.yml;RegressionTests.AntiSpam.SpfEvaluationpublishes policies in the suite's own DNS zone and asserts pass, fail, softfail and none end to end, on Windows and on Linux. -
Tutorial tours in the Control Panel, the Control Deck and the webmail, one mechanism in all three: a step points at one element with one sentence, may wait for the reader to do what it describes, and never presses a button, fills a field, takes the keyboard or blocks the page. The Control Panel's first-run walk is seven stops - a domain, the domain list, a mailbox, the listeners, a listener that offers TLS, the certificates, the dashboard's verdict - started from the Welcome page, the Ctrl+K palette or a help button in the header of any page a tour visits, with F6 to reach the card and a UI Automation notification per step; the Deck walks the same path from a Show me around button in its command bar; the webmail's walk is four stops (the reading pane, compose, labels, search), from Show me around in the account menu. What has been finished is kept where each surface keeps things -
TourProgressunderHKCU\Software\hMailServer\ControlPanel,hmsToursin the browser,toursin the account's preferences - and a tour that grows a stop is offered again. -
The whole configuration as one document:
hmctl config export,diffandapply, andExport-HmConfiguration,Compare-HmConfigurationandImport-HmConfigurationin the PowerShell module, over the REST API - so against a Linux server too, and from another machine. The document holds the settings groups (not the directories), the[Settings]keys, the domains with their accounts, aliases and lists, the groups, the rules, the routes, the listeners and the IP ranges, every collection and key sorted and every identifier, count and time the server allocated stripped, so two exports of an unchanged server are the same bytes.diffprints the plan and exits 1 when anything would change;applyprints it and changes nothing without--force(-Force, with-WhatIfand-Confirm), and deletes nothing the server has and the document lacks without--allow-deleteas well.build/check-cli.pychecks that a document one client writes is one the other reads, against a recorded server. Not carried: passwords (an account the server lacks is refused, not made with a password nobody chose), certificates, directories, application passwords, fetch accounts, folder permissions, remote-domain policies and alert rules. And said plainly: against a real server theinisection comes back empty -GET /api/v1/settings/inianswers the key names only,{"names": [...]}, where both clients read name-and-value rows, which is what the recorded server incheck-cli.pyanswers - and with an API key that route answers 401, whichhmctl's export does not step over, sohmctl config exportwith a key stops there; export with the administrator password. -
Syslog. Every log entry is also sent as RFC 5424 to a collector over UDP, TCP or TCP with TLS, and on Linux under systemd with no collector named it goes to the journal over
/run/systemd/journal/socket, withHMAILSERVER_CATEGORY,HMAILSERVER_MSGID,HMAILSERVER_THREAD,HMAILSERVER_SESSIONandHMAILSERVER_CLIENTforjournalctlto filter on. It is a copy: the log files are written as before. The message id is the log category, or theHMnnnnnumber on an error line; the structured data carries the thread, the session and the client underhmailserver@32473. UDP truncates to 1,024 bytes on a UTF-8 boundary; TCP and TLS frame with octet counting (RFC 5425) and truncate nothing; TLS verifies the collector's chain and name. The sink never holds mail up: a 4,096-entry queue that drops the oldest, a worker thread with back-off, one line when the collector goes and one when it returns. Sevenhm_settingsrows -SyslogEnabled=0,SyslogHost="",SyslogPort=514,SyslogTransport=0 (UDP),SyslogFacility=2 (mail),SyslogMinimumSeverity=6 (informational),SyslogLogTypes(SMTP, POP3, IMAP and application; errors are sent whatever it says) - on COMSettings.Logging, as a card on the Control Panel's Logging page and as twelve keys under/api/v1/settings/logging; they take effect when the servers start, at a service start or a reinitialise. No schema step: a row that is missing is created with its default at start.hmailserver/docs/Syslog.mdhas rsyslog configurations for all three transports. -
Per-domain reports.
GET /api/v1/reportsis the index - the sections, the state of the three sources with how far back each can see, and what cannot be answered - andGET /api/v1/reports/{section}answerstraffic,failures,senders,recipients,mailboxes,volume,storageorsummaryfor a window (fromandto, inclusive, at most 366 days, the last 30 by default), optionally one domain, withtoprows andformat=csv. Traffic, failures by status code and the top senders and recipients come from the message trace, which is off by default (MessageTraceEnabled=0) - a report drawn while it is off says so rather than showing an empty table - and records deliveries and failures only, so a message still in the queue is absent; mailbox sizes are the store as it is now; the volume counts are server-wide, from the metric history. Two new gauges,store_bytesandstore_messages, are computed at most once an hour, sampled into the history and exported ashmailserver_store_bytesandhmailserver_store_messages, so the storage series starts the day the tree is installed. Only hosted domains are counted. A domain-restricted key is refused another domain and the two server-wide sections; a read-only key is enough. A section is one grouped query for the four counted from the trace (traffic,failures,senders,recipients), one formailboxes, two forstorageand six forvolume(one per metric), the trace query capped at 100,000 grouped rows withtruncatedin the answer; a CSV cell beginning=,+,-or@gets an apostrophe in front. The Deck has a Reports view (a date range, a domain, a table per section, two bar charts drawn as SVG in the page (messages in and out per day fortraffic, megabytes per day forstorage), a CSV link per table), andhmctl report <section>is the same from a shell. Not there: spam and virus counts per domain and storage growth per domain (both would need a per-domain daily sample), and a Control Panel page. -
The Control Deck redrawn on the webmail's design: one token block carrying
Portal.html's palette value for value, its dark theme beside it, and the desktop console's spacing, radii and type ramp, with a harness check that fails on a colour defined anywhere else; the theme is light, dark or the system's, followed as the system changes and remembered in the browser; the frame is a command bar (the brand, the view, a search box, the link's state as a pill with a word, the theme, the shortcut list, sign out) over a navigation column that becomes a rail of glyphs under 900 pixels. It needs no pointer: a skip link,/for the search box,?for the shortcut list, the arrows down a table's rows with Enter opening the row (never a row whose only button deletes) and between the views in the navigation column. A Groups view reaches the last thing the census had no view for. The census on3233b0cc4: 365 properties the desktop program writes, 365 of them writable over REST, 359 reachable from a Deck view (the six not are a folder permission's four fields and an application password's name and active flag), 242 routes of which 110 are writes, 19 Deck views - andstyle.ymlnow regenerateshmailserver/docs/DeckParity.mdand fails when the committed copy is behind. -
GET /api/v1/me/foldersreportsuidnextbesideuidvalidity- the folder's current UID plus one, what IMAP STATUS calls UIDNEXT - which the Linux suite's stand-in for COM needed to readIMAPFolder.CurrentUIDlive. -
Documentation:
hmailserver/docsgains AlertsAndAuditTrail.md, Backup.md, DomainTransforms.md, RemoteDomains.md, Reports.md and Syslog.md; README, ARCHITECTURE, WarmStandby, RelocatingAnInstallation, DiagnosingStalledMail, MigratingDatabaseBackend and the HighAvailabilityRunbook say where a setting lives now rather than telling the reader to edit the file, and WarmStandby adds that neither machine should carry a[SettingsOverride]section;CONTRIBUTING.md(at.github/CONTRIBUTING.mduntil 24 September 2026) states that a new setting belongs inhm_settings; every row of Roadmap2.md was read against the tree and corrected where it claimed more or less than the code does. -
Fixes in the second batch.
- On SQL Server Compact a string over 4,000 characters could not be written, even into an
ntextcolumn, because every string was bound asadWChar: a CalDAV object, a domain disclaimer or an alert's detail over that length failed. A longer value is now bound as a long value when it is written, and only then - in anINSERT'sVALUESlist with noSELECTorCASE, or ascolumn = @namein anUPDATE'sSETlist - because a long value compared with annvarcharcolumn faults the Compact provider insidehMailServer.exeand takes the service down; that was found by review and execution before any build carried it. The lookups an outsider reaches refuse an over-long key before the database is asked: an account address over 255 characters, a CalDAV or CardDAV name or UID over 255, an alert condition over 64. - On SQL Server the full-text index stored its terms through a bare
'...'literal after the indexer fix above, so a Cyrillic or CJK term was stored as question marks under the default collation; it isN'...'on SQL Server and Compact. - PostgreSQL compares strings case-sensitively: an alert rule saved as
Disk.Lowsat besidedisk.lowand was never the one read, and the audit trail's filters found nothing for a different case; conditions and filters are folded to lower case. The PostgreSQL connection also ignored a caller's request to ignore errors, so the first-contact memory's deliberate insert race loggedHM5032; a unique violation, and only that, now counts as success where asked. - On MySQL the calendar tables were
utf8mb3, so an event with an emoji in it could not be saved -utf8mb4in the create script and the 6041 step; and a known sender's address was compared accent-insensitively, so two different non-ASCII addresses collided -utf8_binin the create script and the 6044 step. - CalDAV resource names and UIDs are matched exactly: a
PUTtoABC.icsused to find and overwriteabc.ics. A name the database's unique index treats as the same as a live object's - a different case, or accents on MySQL - is refused with 409 naming that object; on PostgreSQL the two are two objects; and a deleted object's case twin is revived under the new spelling instead of being refused for ever. - The 6042→6043 step inserts its nine settings only where they are not already there, after the Linux upgrade gate failed on MySQL and PostgreSQL with a duplicate
AlertsEnabled(section 2). - The audit trail and the alert evaluator read their newest row with a
max()subquery compared by=, which SQL Server Compact refuses, so every test of a gate failed at SetUp on theHM5033logged once a minute; each read is two statements now. A failing test prints the last 256 KB of the server log rather than all of it. - An alert webhook's first retry waited a minute instead of the next pass. The disclaimer's reply-chain comparison treated a closing inline tag as a word break, so a reply that bolded a word collected the footer again on every hop.
Utilities.RunTestSuitereturns the failing tester's sentence rather than a generic error. The Ctrl+K settings index had lost the five scheduled-backup settings when their page moved onto the Control Panel's scaffold, and has them back. The Compose file and the Helm chart no longer spell 6.3.3: the image is:latestand the chart'sappVersionislatest, whichbuild/check-linux-version-stamp.shholds them to by refusing any6.x.yliteral underplatform/packaging/outside the PKGBUILD'spkgverline.
- On SQL Server Compact a string over 4,000 characters could not be written, even into an
The third batch, checked against 8741806f4 (16 September 2026; the full Windows suite on its gated tree 1981209c5: 2,580 tests, 2,572 passed, 0 failed, 8 skipped). Four schema steps, 6046 to 6049, and two new COM interfaces (IInterfaceSenderListEntry, IInterfaceSenderListEntries), so a working copy registers the server once before the suite reaches them.
-
Safe senders, the server's and a domain's allow and block lists, and quarantine digests (schema 6046). A sender or a domain can be allowed or blocked for the whole server or for one domain - over COM, REST, the Control Deck, the Control Panel and both command-line clients - and an entry covers the From header's address as well as the envelope sender; a reader keeps their own list at Settings → Safe senders in the webmail. Held mail is summarised once a day to the person it was held for, per domain and at an hour the domain sets, with a link that asks before it releases anything - once
QuarantineDigestBaseUrlis set and a domain, or an account over it, turns the digest on: it is off by default.hmailserver/docs/SenderListsAndDigests.md. -
Administrator roles (schema 6047). Four built-in roles -
serveradmin,domainadmin,helpdesk,auditor- over twenty-two permissions, custom roles made over REST, assigned per account and per domain and decided in one place that a checker holds every route to. A help desk resets passwords and reads a message trace for its own domain and nothing else; a custom role may never hold the permissions that mean "server administrator" by another name; a role somebody still holds cannot be deleted.hmailserver/docs/Roles.md. -
DKIM keys that rotate (schema 6048). A policy per domain: a second selector published while the first still signs, the swap once the world has seen the new key, and the retirement of the old one - startable and checkable over COM, REST, the Deck, the Control Panel and the command line, with an alert when a rotation stalls.
hmailserver/docs/DkimKeyRotation.md. -
Bounces read, and a suppression list (schema 6049). The delivery reports that come back are classified and counted per address, an address that has hard-bounced is suppressed, and the list and the counts are readable and editable per domain.
hmailserver/docs/BounceSuppression.md. -
A password checked against the published breach corpus. Every place a password is chosen asks first - the Control Panel, the Deck, the webmail, REST, COM and the import - by k-anonymity (five characters of the hash leave the server), and a checker keeps that list of places honest. An endpoint that cannot be reached is reported as unusable rather than as a pass.
hmailserver/docs/PasswordBreachCheck.md. -
Accounts from a spreadsheet. Import and export over REST,
hmctland the PowerShell module, with the column contract documented and refusals that say which row and which column. -
Signatures with images and layout, and a domain disclaimer on every path a person can send by, the webmail's identities included.
hmailserver/docs/Signatures.md,hmailserver/docs/DomainTransforms.md. - The audit trail's holes closed. Five table names it watched were misspelt and matched nothing; the settings store, the access lists and the remote-domain policies were not watched at all; a legal hold, a restore and a lockout lifted left no row. Each is recorded now, and a checker fails the build if a new administrative path forgets to.
-
The settings store, finished at the edges.
GET /api/v1/settings/inianswers values as well as names, with secrets masked, so a configuration export is whole;hmailserver --set-setting NAME=VALUEstores one setting with the database open and no listener running, which is how a Linux administrator turns the REST listener on now that an edit to the file is not used at the next start (HM5804). -
Fixes. Seventy-two COM classes carried no
ISupportErrorInfo, so every refusal they made reached the caller as a bareException from HRESULT: 0x800403E9; they all say why now, and a checker keeps it that way.
The fourth batch, checked against a32bfd93b (16 September 2026). Ten schema steps, 6050 to 6059, and no new COM interface. Its gate was the first run split across two Windows benches: this project's bench took 1,704 tests (1,703 passed, 1 skipped) and a second bench took 1,087 (1,085 passed; the two failures were in how a test read the server and were corrected and re-run), 2,791 tests between them.
-
Outbound webhooks (schema 6051). Five things that happen to mail -
message.received,message.delivered,message.bounced,message.quarantined,account.locked- reach a subscribed endpoint as a signedPOST, queued inhm_webhookdeliveriesso a restart loses nothing, retried with a backoff, dead-lettered, and capped per subscription per minute. Creating a subscription needs the server-administrator permission, because an endpoint is reached from this server.hmailserver/docs/Webhooks.md. -
Country blocking (schema 6052). Connections and sign-ins judged against a MaxMind database the administrator supplies, per protocol and per domain, with the loopback and private ranges exempt so a stock server cannot lock its own administrator out, and a missing or stale database meaning allow-everything with a warning. Refusals are counted per country.
hmailserver/docs/CountryBlocking.md. -
Sessions and devices (schema 6053). Every client that signs in - a browser, a mail program by protocol, address and user agent, with the credential it used by name - is a row the person and the administrator can see and revoke, and revoking one ends its browser session and drops its live IMAP, POP3 and SMTP connections;
credential=revokedeactivates the app password it signed in with, which is the answer to a stolen phone. Idle timeout, absolute lifetime, one session per device and address binding are policies per server and per domain, every one off after an upgrade.hmailserver/docs/DevicesAndSessions.md. - Web Push (schema 6054). A phone is told about new mail with the page closed: one subscription per device, VAPID keys the server keeps write-only, payloads encrypted to the device (RFC 8291), a count, the sender, or the sender and the subject as the device chooses, quiet hours on the device's own clock, a sender or label filter, retries with backoff, and removal when the push service says the device is gone. Off by default.
-
Complaints read (schema 6055). The abuse reports the large providers' feedback loops send are parsed, recorded per account and per domain, and fed into the suppression list the bounces already use; the complaint rate per domain is in the metrics, with an alert shipped at zero - observe only - because the only thing that tells a genuine report from a forged one is the mailbox it arrived at.
hmailserver/docs/Complaints.md. -
Mailbox rename and move (schema 6056). An address renamed, or an account moved to another domain, as one operation that carries the message store, every per-account store, memberships, aliases and the role assignment, leaves an alias behind, and refuses anything it cannot do safely - over COM, REST, the Deck, the Control Panel and both command-line clients.
hmailserver/docs/MailboxMove.md. -
Own-reputation watch (schema 6057). Once a day the server checks the addresses it sends from against the block lists, tells "nobody answered" apart from "not listed", and raises an alert naming the address and the list; off after an upgrade, on in a new installation.
hmailserver/docs/ReputationWatch.md. -
Spam traps (schema 6058). An address that was never real refuses like an unknown user, bans the connecting address through the auto-ban that exists, and puts the envelope sender on the block list.
hmailserver/docs/SpamTraps.md. -
An encrypted backup (schema 6059). AES-256 with the file names hidden, the passphrase write-only in the secret store and never inside the archive it protects, the archive staged away from its destination, and a restore that refuses a wrong or missing passphrase before touching anything.
hmailserver/docs/Backup.md. -
The server publishes its users' OpenPGP keys (schema 6050): a public-key store beside the S/MIME one, a parser that refuses a secret key at every entry point and computes version-4 and version-6 fingerprints as RFC 9580 says, and Web Key Directory serving of both methods - publishing off by default and switchable at the server, the domain and the account. Nothing encrypts or decrypts yet.
hmailserver/docs/OpenPGP.md. -
What this server has changed from stock: every setting whose value is not the shipped default, with the default beside it, over
GET /api/v1/settings/changed, COM, the Deck,hmctl config changedandGet-HmChangedSetting; the defaults are generated into the build from the create scripts so a change in the code cannot drift from the list without CI noticing.hmailserver/docs/ChangedFromStock.md. -
Response compression: gzip and deflate on both HTTP listeners for the answers that are the same for every reader - the webmail shell, its script and its service worker - and never for a mailbox answer, because compression beside attacker-chosen text and a secret is BREACH.
hmailserver/docs/ResponseCompression.md. -
What a sealed message does and does not get. A message the server cannot read is no longer gutted by the blocked-attachment list, given a clean verdict by a scanner that examined ciphertext, or matched by body criteria on its emptiness; the detached signature of a signed message is left alone while its real attachments are still checked.
hmailserver/docs/SealedMail.md. - The webmail's S/MIME made honest: signing out takes the private keys with it, an administrator's password change really re-wraps the keys, a wrapped key says how it was wrapped, a session opened with an app password never touches the keys, and a message that was to be sealed is refused as a draft rather than stored in the clear.
- A restore no longer destroys every share on every mailbox: an account folder's access grants are carried by the archive and put back in a deferred pass, group grants after the groups themselves.
-
An outbound bind address of one family no longer stops delivery to the other (GitHub issue 276).
SMTPDeliveryBindToIPmay name one address, or one of each family separated by a comma, and the one of the recipient's family is bound; an address of the other family is not bound for that connection and the log says so. - The star key means one thing in the webmail: the open message cycles the three stars as a row does.
-
The compose card above the conversation, as a choice (GitHub discussion 266). Reply card on the webmail's preferences page: Under the message it answers - the default, Gmail's shape - or Above the conversation, Outlook's, kept with the account as
reply_place. Nothing about the sent message changes; it is top-posted either way. -
Auto-delete and auto-archive the reader chooses. Three choices on the preferences page - Empty Trash after, Empty Junk after, Archive Inbox mail after: never, 7, 30, 90 days or a year - kept with the account as
retention_trash_days,retention_junk_daysandretention_archive_days, and run by the same six-hourly sweep as the administrator's retention policy: Trash and Junk emptied of what is older than the choice, Inbox mail older than the choice moved to the Archive folder, made if there is none. No schema step. -
Fixes in the fourth batch. The MySQL and PostgreSQL create scripts carried the domain table's
domaindkimrotationkeybitscolumn twice, the second copy without its comma, so a database created from either failed at that line. GCC and clang refused to link threestatic const intclass constants that were odr-used; they areconstexpr. Alonghanded to the formatter in the push route was ambiguous on Linux.
Everything below is in 6.3.4 too. It landed on master between 17 and 24 September 2026, in the batches the project's commits call batch 12 to batch 33; each landed as a fast-forward of master after its gate. The schema moves from 6059 to 6096 in thirty-seven steps, each listed in section 2; at master 540347500 DBUpdater registered them to new UpgradeScript(6095, 6096) (Tools/DBUpdater/formMain.cs:306) and the server required the last (REQUIRED_DB_VERSION 6096, Common/Application/Constants.h:987); the last batches, below, take it to 6104. One key is added to hMailServer.ini, and it is a bootstrap key read before the database is open, [Database] UpgradeOnStart (batch 25); no [Settings] key is added (section 7). Batch 33's gate, on 24 September 2026, ran 3,390 tests in two halves - 2,076 on this project's bench, 2,075 passed, 0 failed, 1 skipped, and 1,314 on the second bench, 1,311 passed, 0 failed, 3 inconclusive. The three inconclusive results were GnuPG proofs whose fixtures handed Git for Windows' gpg a home directory it could not read; the fixtures were fixed the same day (fd9d43e66) and ran 27 of 27. The Linux suite ran on GitHub Actions and last reported on the tree of 17 September - 1,725 results, 1,439 passed, 0 failed, 286 skipped - and nothing ran it after GitHub suspended the project's account on 18 September 2026 until it ran again on the project's own Linux machine, the lab VM, in the last batches (25 September 2026, below). The version-by-version summary of the same work is the 6.3.4 section of the repository's CHANGELOG.md.
Between batch 11 and batch 12 (16 and 17 September; no schema step). The Linux regression suite ran in CI (GitHub Actions, until 18 September) as two halves, because whole it was cut off at the job's ninety-minute limit (hmailserver/docs/RegressionEnvironment.md); the nightly fuzzing job's first scheduled run came back green; and the README's three screenshots were taken again on the batch-11 tree.
Batch 12 (17 September; schema 6059 → 6064).
-
A setup checklist the server judges.
GET /api/v1/setup/checklist(read-only,view_configuration;?dns=0skips the lookups) and COMUtilities.GetSetupChecklistanswer eight items in the order a new server meets them - a domain, an account, TLS on the submission port, SPF, DKIM, DMARC, a backup destination and a second factor on the administrator credential - eachdone,action_needed,cannot_tellornot_needed, with a code, its arguments and an English sentence per finding. SPF, DKIM and DMARC are looked up the way a receiving server looks them up, and a failed lookup is cannot tell rather than missing. The Control Deck gains a Checklist view, first after the dashboard.hmailserver/docs/SetupChecklist.md. -
An out-of-office policy, not just a message (schema 6060). Who has been answered is kept in
hm_autorepliesrather than in process memory, so a restart no longer sends every correspondent the reply again. A window of days (Account.VacationMessageDays,Domain.VacationDays, 0 to 3650,vacation_daysover REST) answers the same sender again once it has passed, 0 being once per absence as before; and the account gains a text of its own for colleagues (VacationInternalSubject,VacationInternalMessage) andVacationMessageInternalOnly, which answers them and nobody else.hmailserver/docs/OutOfOffice.md. -
The Linux suite's skips measured, and four routes that remove some of them: an account's own rules (
GET/POST /api/v1/accounts/{address}/rules,PUT/DELETE .../rules/{id}), a mailbox exported to a directory on the server's disk (POST /api/v1/accounts/{address}/export), an address's traces erased (POST /api/v1/addresses/erase) and the administrator's second factor (GET,POSTandDELETE /api/v1/administrator/second-factor). The export, the erasure and the enrolment answer to the administrator password alone. -
A URL as an allow or block list entry. Written as
https://host/anythingorhost/and stored ashost/, it covers a link in the message whose host is that host or a name beneath it. It is consulted only when neither the envelope sender nor the From header decided anything, and the body is read for links only for a recipient whose account, domain or server holds such an entry, so a server with none pays nothing. The Control Panel's two list editors gain the entry's expiry.hmailserver/docs/SenderListsAndDigests.md. -
A setting's change put back from its audit row.
POST /api/v1/audit/{id}/revertand COMUtilities.RevertAuditedChangeset a setting back to the value its row says it had, through the setter the change came through, and the put-back is a row of its own; a secret's row, a row whose value the trail cut and any row that is not a setting's are refused, and an API key cannot do it. The Control Panel gets an Audit trail page over the newUtilities.GetAuditTrailandUtilities.VerifyAuditTrail, which answer whatGET /api/v1/auditand/audit/verifyanswer.hmailserver/docs/AlertsAndAuditTrail.md. -
A quota policy (schema 6061): a domain's own warning share, subject and text (
Domain.QuotaWarningPercent,QuotaWarningSubject,QuotaWarningText); a grace past the limit for the mail other people send (QuotaGracePercent, server-wide and per domain), while the owner's own saves still stop at the limit; and a share of the limit at which the account can no longer send (QuotaSendStopPercent), where an authenticatedMAIL FROMis refused552 5.2.2.GET /api/v1/reports/quotalists the mailboxes nearest their limit.hmailserver/docs/QuotaPolicy.md. -
A person's data as one archive.
Account.ExportArchive,POST /api/v1/accounts/{address}/exportwitharchive,GET /api/v1/accounts/{address}/export.zip,hmctl privacy exportand, for the person themselves,GET /api/v1/me/export.zip(the webmail's Your data card) write one .zip: every folder as mbox, the address book as vCard 3.0, the calendars, the webmail's preferences, the active Sieve script, the log lines naming the address up to 50 MB and anaccount.xmlin the backup's own form. A download past 500 MB is refused with the sentence that says how to write the same archive to the server's disk. The erasure of an address also reaches other people's address books and first-contact memory now.hmailserver/docs/PersonalData.md. -
Spam, viruses and storage per domain (schema 6062,
hm_domainsamples): spam and viruses are counted by the domain of each local recipient as they happen, and each domain's store is measured at most hourly, so the reports' volume and storage sections answer per domain, and a domain-restricted key is no longer refused them.hmailserver/docs/Reports.md. -
Collected addresses become suggestions. An address the person wrote to is offered under Other contacts with Keep rather than filed as a contact; CardDAV serves only what was kept; an authenticated SMTP submission collects its recipients too; and the Contacts page reads what a phone put on a card - birthday, note, phones and the photo (
GET /api/v1/me/contacts/{id}/photo).hmailserver/docs/Contacts.md. -
Mail flow rules (schema 6063,
hm_rules.ruledomainid): a domain's own rules (Domain.Rules,/api/v1/domains/{name}/rules), run after the server's for the recipients' and the sender's domains; a condition on the attachment name; and four actions - add a disclaimer, reject with a text (550 5.7.1in the rule's words at the end of DATA, while the sender is there to be told), require TLS (a destination without STARTTLS is deferred with the rule named, never sent in the clear) and quarantine under the rule's name.hmailserver/docs/MailFlowRules.md. -
Calendars and tasks in the webmail (schema 6064:
hm_calendars.calendarcolor,hm_calendarshares). An account keeps several calendars with colours and shares one with another account of this server, read-only or read-write; CalDAV serves them all under the account's home, the shared ones asshared-{id}, withMKCALENDARandPROPPATCHfor the name and colour. The page's calendar and tasks views read/api/v1/me/calendars,/api/v1/me/calendar/eventsand/api/v1/me/tasks, occurrence by occurrence through the server's own recurrence module. Invitations by mail (iMIP, RFC 6047): an event with guests mails each atext/calendarREQUEST, an update steps the sequence and a delete sendsCANCEL; an invitation that arrives is answered from a card above the message; and aREPLYdelivered to the organiser sets that guest's answer in the stored event.hmailserver/docs/Calendar.md. -
The Control Deck's last eight pages - API keys, the quarantine, the archive with its legal holds, the metric history drawn in SVG, the live update, the DNS records to publish, a folder's access list and the diagnostic run (
POST /api/v1/diagnostics/run, new) - and with them the parity census read 402 of 402 on that day.hmailserver/docs/DeckParity.md. - Fixes found by the batch's gates, in its own new code: a read through a pointer the calendar's own edit had just invalidated, which the crash oracle caught as an access violation; the volume report sending no per-domain rows; a change to a recurring series not reaching an occurrence changed on its own; and the quota policy's two server-wide shares read from rows no database had, which failed 458 tests of one gate until both were ensured at start-up.
Batch 13 (17 September; schema 6064 → 6069).
-
Manage subscriptions. Settings → Subscriptions in the webmail lists every sender whose mail in the last 30, 90 or 365 days offers a way out, with how much each sent and how much is unread, one button to leave by the way the newest message names, and a block and a delete of everything from the address (
GET /api/v1/me/subscriptions). -
Brand logos. A sender domain's BIMI logo is the avatar of its mail only when this server's own
Authentication-Resultsheader says the message passed DMARC, the domain's policy isquarantineorrejectatpct=100, and the logo is an SVG Tiny Portable/Secure file fetched over https from a public address; every answer is kept a day per domain (GET /api/v1/me/brand-logo,BrandLogosEnabled, on).hmailserver/docs/BrandLogos.md. -
Other accounts, and an import with a sign-in (schema 6065:
hm_fetchaccounts.faoauthprovider,faoauthtoken,famirrorskip,famirrorseen). A person collects their own POP3 or IMAP accounts from the webmail's Settings → Other accounts, with the server filled in for Gmail, Outlook.com, Yahoo Mail and iCloud Mail, and may sign in to Gmail or Outlook.com instead of giving a password: OAuth 2.0 with PKCE, the refresh token kept protected and the mailbox read with XOAUTH2. The administrator registers the two clients (import_oauth_*).hmailserver/docs/Migration.md. -
Send mail as another address (schema 6066,
hm_senderaddresses): an address at another provider, proved with a six-digit code mailed to it, joins the account's own address, its aliases and the mailboxes whose owners granted it the post right, in the From picker and inMAIL FROMon an authenticated session whenSmtpAuthenticatedSenderCheckis on.hmailserver/docs/SendAs.md. -
Masked addresses (schema 6067,
hm_maskedaddresses): an address a person makes for one shop or one sign-up, in the account's own domain, delivered to the account and written from, and refused atRCPTlike an address that never existed once it is switched off or removed.hmailserver/docs/MaskedAddresses.md. -
Recover deleted items (schema 6068,
hm_recoverable): a message a person deletes is kept aside forrecover_deleted_days(0, off, by default; at most 365) and put back by its owner from the webmail (/api/v1/me/recoverable). Only a delete a person made is kept - an IMAPMOVE, a sent scheduled draft or an account removed leaves no copy - and the copies are not in the backup.hmailserver/docs/RecoverDeleted.md. -
A message that expires (schema 6069,
hm_confidential), Gmail's confidential mode done so that the message is not sent:POST /api/v1/me/messageswithexpires_in(a second to ninety days) and an optional passcode keeps the message on this server and mails each recipient a link to/confidential/<token>, where a recipient proves who they are with a six-digit code mailed to them or with the passcode. It is offered only whenConfidentialBaseUrl(or the digest's or the import sign-in's base URL) is set.hmailserver/docs/ConfidentialMessages.md.
Batch 14 (17 and 18 September; schema 6069 → 6071).
-
Offline for the last N days. The reader chooses 7 to 365 days under a ceiling the administrator sets,
ReaderOfflineDaysMax(30; 0 offers none), and the page keeps the bodies and attachments of that window in the browser's IndexedDB, reads and searches them with no connection, and forgets them at sign-out (/api/v1/me/offline,/api/v1/me/offline/messages).hmailserver/docs/OfflineMail.md. -
Outbound content policy (Exchange's data loss prevention), done as conditions of the mail flow rules with no second engine and no schema change:
sensitive_content(a Luhn-valid card number, an IBAN checked against its country's length and mod 97, orDlpAccountNumberPattern's own expression, in the readable text and text attachments),attachment_typeby the file's first bytes,external_recipientandcontent; and one new action,warn_sender, which mails a sender this server hosts a policy notice and never an outside one.hmailserver/docs/OutboundContentPolicy.md. -
The DMARC and TLS-RPT reports a domain receives, read and charted (schema 6070:
hm_domains.domainreportmailbox,hm_dmarcreports,hm_dmarcreportrecords,hm_tlsreports). A domain names a report mailbox (Domain.ReportMailbox,report_mailbox), which must be an address this server hosts; the aggregate and TLS-RPT reports delivered to it are recorded, keyed so that a retry counts once, and the message is still delivered unchanged (/api/v1/domains/{domain}/dmarc-reports,/tls-reports).hmailserver/docs/DmarcReports.md. -
Compromised-account detection (schema 6071:
hm_accountactivity,hm_accountrecipients,hm_accountreview,hm_heldmessages). An authenticated submission is scored against the account's own baseline - volume, recipients it has never written to, an hour and a network never seen - and nothing reads the message's text.watch, the mode a new and an upgraded server start in, raisesaccount.compromisedand holds nothing;enforceholds the message that completed a takeover's shape and locks the account for review (/api/v1/review,/api/v1/accounts/{address}/review).hmailserver/docs/CompromisedAccounts.md. -
One domain, read:
GET /api/v1/domains/{domain}, whichhmctl domain getandGet-HmDomain -Namehad always asked for and the server had never answered; a key confined to domains reads only its own. -
Contact groups, with no schema change: a group is a contact whose card says
KIND:group(vCard 4.0) or Apple'sX-ADDRESSBOOKSERVER-KIND:group(3.0), its members read from the card every time; a group may hold a group, followed eight deep.hmailserver/docs/Contacts.md. -
Important, and sections of the inbox: Gmail's importance marker as a heuristic of eight weighted reasons that runs in the reader's browser and writes the IMAP keyword
$Importantor$NotImportant; nothing is learned on the server.hmailserver/docs/ImportantMail.md. -
Emoji, pictures and tables in compose, in the page alone: a picker of about a thousand emoji, PNG, JPEG, GIF and WebP pictures judged by their first bytes, and tables.
hmailserver/docs/FormattedMessages.md. -
Follow one connection live. The Control Deck's Logs page lists every connection on the wire and the last two hundred that ended, and follows one: every line the log holds for that session, and each new one as it is written, from a ring of the last 4,096 entries the logger keeps (
/api/v1/logs/sessions,/api/v1/logs/sessions/{id}). No setting and no schema step.hmailserver/docs/FollowConnection.md. -
Fixed, in code that shipped: the message trace's search wrote
@ADDRESStwice and bound it once, so on SQL Server Compact, which binds by position, it answered nothing or was refused; the two are bound apart now, andbuild/check-sql-parameters.pyholds that rule over every statement it can read. Found in the batch's own new code: a card number written across a table's cells was not found, and a message could be delivered and held for review at once. - The C++ CodeQL analysis was set to run on pull requests as well as on master. It is a GitHub Actions workflow and has not run since 18 September, when GitHub became unavailable; nothing runs it on GitLab yet, and since 18 October 2026 CodeQL analyses the code of the copy on GitHub - GitHub's default setup at first, and then the project's workflow
.github/workflows/codeql.ymlin its place.
Batch 15 (18 September; no schema step).
-
A second account in the same page.
POST /api/v1/session?bearer=1answers an account's session as a token in the body, which the page holds in memory only and sends asAuthorization: Bearer, so two mailboxes stand side by side and one press switches the whole page between them; the form is never minted for the administrator.hmailserver/docs/SecondAccount.md. -
The live update applies on Linux. The server writes a request into
/run/hmailserver; a root path unit runs/usr/lib/hmailserver/hmailserver-update, which checks the request, stops the unit, verifies the package again as root (hmailserver --verify-update), installs it, waits for the service and rolls back when it does not come back, and writes the outcome the Deck reads. Installing on Linux has the whole of it;hmailserver/source/Server/platform/packaging/README.md. - Two roadmap rows for what one operator asked for in six GitHub discussions (281 to 286) on 17 September; batch 16 built both.
Batch 16 (18 to 22 September; schema 6071 → 6083).
-
Greylisting, hardened (schema 6072; GitHub discussions 281, 282 and 284), each part off by default. Each address of a triplet is keyed by a digest stored beside it, so two addresses that share their first 200 characters are no longer one triplet, and the domain is folded to lower case by hand on every backend.
GreyListingSlideOnEarlyRetry(0) moves the window on a retry that comes too early;GreyListingAbuseThreshold(0) andGreyListingAbuseLockMinutes(60) refuse a sender that hammers. A bypass list (hm_greylisting_bypass;/api/v1/greylisting-bypass; COMAntiSpam.GreyListingBypassEntries) names a domain or a dotted suffix, on the envelope domain, the HELO or either, and the SPF, DKIM or DMARC pass the message must carry for that identity; an entry requiring nothing is refused.hmailserver/docs/Greylisting.md. -
SMTP policy a score cannot express (schema 6073; GitHub discussions 283, 285 and 286), all off: scores of their own for SPF SoftFail, PermError and TempError (
UseSPFSoftFailScore,UseSPFPermErrorScore,UseSPFTempErrorScore) andSPFTempErrorDefer, which answers a TempError with451 4.4.3;RejectMultiRcptNullSender, which refuses a null sender's second recipient and its DATA with550 5.7.1, andBanMultiRcptNullSender, which bans the client forBanMultiRcptNullSenderHours(24) through the auto-ban; and a domain white and black list by envelope domain or HELO (hm_domainpolicylist,/api/v1/domain-policy-list), black refused atMAIL FROMbefore greylisting, white past greylisting and the score's refusal while every test still runs and writes its headers.hmailserver/docs/SmtpPolicy.md. -
A sample domain that can be thrown away. One call makes
example.testwith three mailboxes, a list, a rule and eight messages delivered through the real delivery path, and one takes all of it away and says what it removed:POST/DELETE /api/v1/setup/sample-domain,Utilities.CreateSampleDomain,hmctl sample-domain,hmailserver --sample-domain, a button on the Control Panel's Welcome page and another under the Deck's checklist.hmailserver/docs/SampleDomain.md. -
A distribution list that defines itself (schema 6074,
hm_distributionlists.distributionlistmembership):domain,domain:other.example,pattern:sales-*@example.comoreveryone, answered against the active accounts when a message reaches the list and joined with the list's own rows; nothing is stored.hmailserver/docs/DistributionLists.md. -
Journaling and dual delivery (schema 6075,
hm_journalrules): a copy of a hosted domain's mail, or one account's, arriving, sent or both, to an address - as Exchange's envelope report with the original attached, or as the message itself with one header in front. It is decided once per accepted message and never again on a retry (/api/v1/journal-rules;Utilities.GetJournalRules,SaveJournalRule,DeleteJournalRule; a Control Panel page).hmailserver/docs/Journaling.md. -
A recovery address, recovery codes and a password reset (schema 6076:
hm_accountrecovery,hm_recoverytokens,hm_recoverycodes). A person sets a second mailbox of their own, confirmed by a link, and ten one-time codes on the webmail's Security page. Forgotten your password? posts toPOST /api/v1/password-reset, which answers 202 with the same sentence whatever the address, and mails a link that opens/portal/resetto the confirmed recovery address and nowhere else; a code travels inX-hMailServer-Recovery-Codewherever an authenticator's code is asked for. The alertaccount.password-resetis seeded on.hmailserver/docs/AccountRecovery.md. -
Passkeys and single sign-on (schema 6077,
hm_passkeys): WebAuthn, and OpenID Connect from one issuer the administrator names (the authorization code flow with PKCE, theid_tokenverified against the issuer's keys), into the webmail and the Control Deck, both ending atPOST /api/v1/sessionunder the same session policy. Single sign-on makes no mailbox unlessOidcAutoProvisionis on and opens the Deck only for an address inOidcAdministrators; IMAP, POP3 and SMTP still take the password.hmailserver/docs/PasskeysAndSingleSignOn.md. -
The organisation's directory (schema 6078,
hm_directory): a domain's active accounts and enabled lists, findable by that domain's own people and never across domains, with a title, a department, three telephone numbers, a photo and a note, in the webmail's address completion and as a CardDAV collection;DirectoryEnabledandDirectoryUserEditable, both on.hmailserver/docs/Directory.md. -
Shared address books (schema 6079:
hm_addressbooks,hm_addressbookshares,hm_contacts.contactbookid): several books per account, one handed to an account or a whole domain, read-only or writable, through the webmail and CardDAV. The upgrade moves nothing: a contact with book 0 is in the account's default book.hmailserver/docs/SharedAddressBooks.md. -
OpenPGP in the page (schema 6080,
hm_pgpring): RFC 4880 version 4 keys and messages, as GnuPG makes them, made, encrypted, decrypted, signed and verified in the browser by/portal-openpgp.json the Web Crypto API, the secret key stored under a passphrase the server never sees.hmailserver/docs/OpenPGP.md. -
Trust made visible (schema 6081, five columns on
hm_pgpring): a correspondent's key is remembered from the first time it is seen, a different key for the same address is a warning and never a silent switch, and a key read back to its owner carries a green mark (PUT /api/v1/me/openpgp/keys/{id}/verified,.../accepted).hmailserver/docs/OpenPGP.md. -
A password-protected message to anyone (schema 6082,
hm_protectedmessages): the page encrypts the message and its attachments to a password as one OpenPGP message, this server keeps the ciphertext forProtectedMessageDays(30) and mails a short notice with a link to/portal/protected/<token>, where the recipient decrypts it in the browser and may reply under the same password.hmailserver/docs/OpenPGP.md. -
Zero-access mailboxes (schema 6083:
hm_accounts.accountzeroaccess,hm_zeroaccess), opt-in per account whereZeroAccessAllowedlets it: once the filters, the rules, the forward and the Sieve script have had the message, what is written into the mailbox is encrypted to the account's own OpenPGP key as PGP/MIME, the key's secret half sealed under the account password and under a recovery key only the person holds. The administrator cannot read, search or recover such mail.hmailserver/docs/ZeroAccess.md. -
Snooze, scheduled send and reminders on the phone: measured rather than built, because the features were already there.
Roadmap2.md. -
Fixes. Two rows had numbered their COM members from the same dispatch id;
build/check-com-dispids.pyholds that now. The OpenAPI document was not JSON at two joins;build/check-openapi-blocks.pyreads every join. Found on the benches, in code that shipped: a sender domain longer thanhm_domains.domainname's 80 characters made SQL Server Compact log an error for everyMAIL FROMandRCPT TOthat named it; such a name is now nobody's before any query is made.
Batches 17 to 22 (22 September; one row each).
-
Restricted delivery (batch 17, schema 6084:
hm_domains.domainrestricteddomains,hm_accounts.accountrestricteddelivery): accounts, or a whole domain, that may exchange mail only with their own domain and the domains it names (Domain.RestrictedDomains,restricted_domains); an account follows its domain, is restricted on its own, or is exempt (Account.RestrictedDelivery). The postmaster and the null sender are always let through.hmailserver/docs/RestrictedDelivery.md. -
Message recall within the server (batch 18, schema 6085,
hm_recall): an unread message an account sent to accounts on this server is withdrawn by its sender,POST /api/v1/me/recall, withinRecallDays(14). Mail from outside, and copies that left the server, are never recalled.hmailserver/docs/MessageRecall.md. -
Delegated mailboxes named in the Autodiscover answer (batch 19, no schema step): an
AlternativeMailboxelement, in Exchange's shape, for each account that opened a folder to the address asked about, answered only to a request signed in as that address. Outlook reads the element for an Exchange account and ignores it for an IMAP one, so for IMAP clients the#Usersnamespace is still what works; Connecting a Mail Client says so. The repository's README.md. -
A calendar shared with somebody (batch 20, schema 6086:
hm_calendarshares.sharegroupid,sharedelegate): free-busy for anyone in the caller's own domain (GET /api/v1/me/freebusy, and CalDAV'sfree-busy-queryreport), busy periods only and at most 62 days; a calendar shared with one of the server's groups; and a delegate who answers invitations on the owner's behalf, the reply carryingSENT-BY.hmailserver/docs/Calendar.md. -
Help where the question is (batch 21, no schema step): a help button on every page of the Control Panel, the Control Deck and the webmail opens the repository document for that page beside it, from the documents built into the server (
GET /api/v1/help,GET /api/v1/help/{page}, unauthenticated), so a server with no route to the Internet still has its documentation. Open on the wiki at the foot of each pane goes toHelpWikiBaseUrl(this wiki by default) and the page named after the document, and most documents have no wiki page of that name yet.hmailserver/docs/Help.md. -
Impersonation protection (batch 22, schema 6087:
hm_domains.domainimpersonationaction,domainprotectedpersons,domainprotectedpartners), Microsoft 365's anti-phishing policy, per domain and off on every domain: on mail from outside, a From display name that folds to the name of one of the domain's accounts or a protected person while the address is not theirs, and a sender domain that folds to a hosted or partner domain or is one edit away from one, are flagged with a header the webmail turns into a warning (1) or quarantined (2).hmailserver/docs/ImpersonationProtection.md. - Also on 22 September: the service-account row of the roadmap rewritten with the decision that the shared-hosting request of GitHub discussion 267 stays declined; and the heap-corruption abort under concurrent IMAP
LISTwritten up as a roadmap row of its own (fixed in batch 28).
Batches 23 to 29 (23 September).
-
Restore one mailbox, or one message, from a backup (batch 23, no schema step): an archive in the backup destination gives back one account's folders and messages, merged into the live mailbox or replacing it, or one message, and touches nothing else (
GET /api/v1/backup/archives,GETandPOST /api/v1/backup/{archive}/mailboxes/{address}[/restore],BackupManager.RestoreMailbox,RestoreMessage,MailboxRestoreStatus,DescribeMailbox). A deleted account is not made again, and a path outside the backup destination is refused.hmailserver/docs/Backup.md. -
Throttling per destination and per tenant (batch 24, no schema step). A destination that answers with a 421, or with one of the providers' rate-limit codes or phrases, is given a pause of
OutboundBackoffMinutes(2; 0 turns it off), doubling up toOutboundBackoffMaxMinutes(60), in which nothing is sent to it and nobody else is slowed;MaxOutboundConnectionsPerDestinationandMaxOutboundConnectionsPerTenant(both 0, off) keep one destination or one hosted domain from holding every delivery thread; and a wait this server chose no longer spends a message's retries until the message has been queued for longer than its retry schedule spans, so a destination that never stops asking still ends in a bounce (GET /api/v1/queue/shaping,DELETE /api/v1/queue/shaping/{destination}).hmailserver/docs/OutboundShaping.md. -
An upgrade nobody has to book a window for (batch 25, no schema step). A drain closes the listeners and pauses delivery before it waits, so a client that connects during it is refused at the socket rather than accepted and cut;
POST /api/v1/server/drainenters it as a state of its own (maintenance mode,/readyz503 "draining") andDELETEleaves it; the live update drains before its helper stops the service; and[Database] UpgradeOnStart=1inhMailServer.inimakes the service bring an older database forward as it starts.hmailserver/docs/Upgrading.md,hmailserver/docs/HighAvailabilityRunbook.md. -
A setting takes effect when it is saved (batch 26, no schema step). The
[Settings]values are one immutable block swapped atomically, and every key carries a mark inbuild/ini-settings-baseline.txt: 211 apply when saved, 28 wait for a reinitialise - the listeners, their TLS contexts, the fetch queue's size,DatabaseStatementTimeout,AcmeEnabledandAcmeCertificateDirectory- and 2 wait for a restart,ServiceAccountNameandServiceAccountPassword. The Control Panel, the Deck, COM (Settings.IniSettingApplies,IniSettingAppliesAll,PendingIniSettings) and REST (GET /api/v1/server/settings/pending,applieson every ini answer) say which keys still wait, and Apply now is a reinitialise.hmailserver/docs/SettingsApply.md. -
One value, three levels (batch 27, schema 6088,
hm_settingoverrides): 27 settings may be set for the server, a domain and an account, the deeper level winning within its kind - a limit may only be tightened below, a gate is the server administrator's alone, a preference is the tenant's own - resolved in one place (Application/EffectiveSetting), with what is in effect and where it came from shown beside each (GET /api/v1/settings/levels,<name>_effectiveand<name>_sourceon the domain and account routes, COMEffectiveSetting,EffectiveSettingSource,SettingOverride).hmailserver/docs/SettingLevels.md. -
Configuration from the environment, for every setting (batch 28, no schema step):
HM_SETTING_<key>for each of the 241[Settings]keys andHM_PROPERTY_<name>for eachhm_settingsrow the build reads, with a_FILEform of either for a value a Kubernetes Secret supplies.hmailserver --apply-environmentchecks every value before it stores any; the container's entrypoint runs it at every start; and because the environment wins, a setting it names is refused to an administrator's change over REST (409), COM, the Deck and the Control Panel until a start without the variable (GET /api/v1/settings/environment).hmailserver/docs/EnvironmentConfiguration.md. -
The heap-corruption abort under concurrent IMAP
LISTand folder churn, found and fixed (batch 28), in code that shipped:IMAPSpecialUseiterated the account's shared, cached folder tree through the live, unlocked vector while another session'sCREATEorDELETEcould reallocate or free it; it iterates the locked copy (GetSnapshot()) now. The crash oracle had recorded the same fault on 15 and 22 September.Roadmap2.md. -
Profiles (batch 29, no schema step): four named shapes of server - office, gateway, relay and sender - each a document in the tree that sets the settings that kind of server needs and says why beside each. A diff against this server comes first, an entry that closes a listener, trusts an address range, turns a filter off or shortens a retention asks for a confirmation, the values replaced are saved before the first write and can be put back (
/api/v1/settings/profiles;Settings.GetProfiles,ApplyProfile,RevertProfile;hmailserver --apply-profile;hmctl settings apply-profile; the Control Panel's Setting profiles page; the container'sHM_PROFILE), and a new server is offered one first.hmailserver/docs/Profiles.md. -
A rolled-back update's outcome is reported when the service comes back, not up to a quarter of an hour later: a start waits for the helper's outcome while the helper's log says one is still to come.
hmailserver/docs/Upgrading.md.
Batches 30 to 33 (24 September).
-
Every loop over a shared collection iterates a locked copy (batch 30): all 87 calls of
GetVector()andGetConstVector()were read, and every member of a collection class that touches the vector itself; six walked a collection another thread changes and are fixed - three loops now iterateGetSnapshot()and three members take the collection's lock - and each of the other 84 calls says on the line before it why the call owns what it walks.build/check-collection-iteration.pyholds the rule.ARCHITECTURE.md. -
A send API for applications (batch 31, schema 6089,
hm_apisends): an API key becomes a sender, with one address or*@domainof its own (send_from) and ceilings a minute and a day;POST /api/v1/sendsends through the webmail send's own code, andGET /api/v1/sendand/api/v1/send/{queue_id}tell that key, recipient by recipient, whether each was delivered, deferred or bounced and why. The route answers to such a key alone, and the key's role must holdsend_through_api.hmailserver/docs/SendApi.md. -
A migration that survives being interrupted (batch 32, schema 6090:
hm_migrations,hm_migrationjobs,hm_migrationfolders): many mailboxes copied from another IMAP server, Google Workspace or Microsoft 365 as a migration of jobs, one per mailbox, each with its state, throttled as the source will bear (MigrationConnectionsPerHost, 4), resumed after a failure or a restart without copying anything twice, reported on, and with the contacts and calendars beside the mail (/api/v1/migrations/..., a Deck view).hmailserver/docs/Migration.md. -
Attachments opened somewhere safe (batch 32, schema 6091:
hm_sandboxverdicts,hm_sandboxholds): a message from outside whose attachment is an executable, a script, an archive or an office document (SandboxTypes) is held - in no mailbox and not in the quarantine - until a sandbox service the administrator names answers over a two-route contract (SandboxSubmitUrl,SandboxVerdictUrl), and the answer decides; off (SandboxEnabled0), and no adapter is shipped (/api/v1/sandbox/held).hmailserver/docs/Sandbox.md. -
A list manager, and bounce handling for lists (batch 32, schema 6092: fourteen columns on
hm_distributionlists, nine onhm_distributionlistsrecipients,hm_listarchiveand fourteenLIST_*server messages). A list with the manager switched on answersname-subscribe@,-unsubscribe@,-request@,-owner@and-bounces@(with VERP where the list says so); people join and leave by mail or by page with a confirmation and, where the list says so, the owner's approval, and by RFC 8058's one-click; each member gets a copy of their own with the RFC 2369 fields, a subject prefix, a Reply-To and a footer; the list keeps an archive and sends a daily digest; and a hard bounce, or five soft ones in thirty days, pauses a member and tells the owner once. Nothing changes for a list until it is switched on.hmailserver/docs/MailingLists.md. -
IP warm-up (batch 32, schema 6093:
hm_warmup,hm_warmupcounts): a schedule for a new outbound address holds it to a daily ceiling of recipients at each large provider - Google, Microsoft, Yahoo, Apple, Proton - rising along a standard, gentle or fast plan, the provider decided from the mail hosts and never from the recipient's domain; nothing is held until a schedule exists (/api/v1/warmup).hmailserver/docs/IpWarmup.md. -
Gateway encryption to partner domains (batch 32, schema 6094:
policyencrypt,policyencryptkey,policyencryptfallbackonhm_remotedomainpolicies): a remote domain policy encrypts mail to its domain at this server to the partner's OpenPGP key or S/MIME certificate, whatever the sender's client did, and says whether to refuse or send unencrypted when it cannot; one key per partner domain.hmailserver/docs/RemoteDomains.md. -
An organisation CA for S/MIME (batch 33, schema 6095:
hm_smimeca,hm_smimeissued,hm_smimekeys.smimeauthority): a domain's own authority certifies its accounts' keys, so S/MIME works inside the organisation without a bought certificate. The key pair is made in the browser and sent as a PKCS#10 request; the authority signs and never makes a person's key, so nothing here can recover mail encrypted to a lost key (/api/v1/domains/{domain}/smime-ca,/api/v1/me/smime/enroll).hmailserver/docs/SmimeAuthority.md. -
SCIM 2.0 provisioning (batch 33, schema 6096,
hm_accounts.accountexternalid): the organisation's identity provider - Entra ID, Okta - makes, renames, suspends and removes accounts with their aliases, and distribution lists, at/scim/v2on the REST listener (RFC 7643 and RFC 7644), answering to an API key whose role holdsprovisionand nothing else. The LDAP backend authenticates; SCIM provisions.hmailserver/docs/Scim.md. -
SAML 2.0 sign-in beside OpenID Connect (batch 33, no schema step): ADFS, and Entra ID or Okta set up the SAML way, sign people in to the webmail and the Control Deck through the same one-use ticket and
POST /api/v1/session(/portal/saml/start,/portal/saml/acs,/saml/metadata), on a strict namespace-aware XML reader, an Exclusive XML Canonicalization and an XML-Signature verifier of the project's own.hmailserver/docs/PasskeysAndSingleSignOn.md. -
Fixes in batches 30 to 33. Found in code that shipped: every message this server relayed gained an empty last line, because the SMTP client ended DATA with
CRLF.whether or not the stored message already ended with a line break; it asks now. Found in the batch's own new code: an attachment sandbox's report to the sender waited up to a minute for the queue to be read. And after batch 33 (fd9d43e66), the GnuPG fixtures of gateway encryption and zero-access give Git for Windows'gpga home directory it can read, so the proof that another OpenPGP implementation opens what this server wrote is made on Windows again.
The work that landed on master after 540347500, up to 92b8f9f63; batch 40, after it, closes the section. The schema moves from 6096 to 6103 in seven steps (section 2), DBUpdater registers them to new UpgradeScript(6102, 6103) (Tools/DBUpdater/formMain.cs:313) and the server requires the last (REQUIRED_DB_VERSION 6103, Common/Application/Constants.h:1205). No [Settings] key is added; three keys are added to the file's bootstrap sections, each read before the database can be - [Database] TlsMode and TlsCaFile, and [Security] StoredSecretsKey - and twenty-seven rows to hm_settings (Settings Reference section 17). The version stamp is 6.3.4, build 43 (25b4eb65a). Each repository document named below is under hmailserver/docs at the repository.
24 September
-
eDiscovery (schema 6097:
hm_discoverycases,hm_discoverysearches,hm_discoveryholds,hm_discoveryexports;5f3359369). A compliance officer opens a case, searches every mailbox, the deleted items and the archive by person, date, words and attachment, puts the mailboxes, domains or archived copies it is about on a legal hold, and exports what was found as EML and mbox in a zip whose manifest's SHA-256s prove what was handed over. A hold stops the account's and its domain's deletion, a move to another domain, a purge of the deleted items, the retention sweep and a full restore, and keeps what its owner deletes among the deleted items until it is released. Every search, hold, export and download is on the audit trail. Two permissions,view_discovery(which the built-inauditorholds) andmanage_discovery(serveradmin);/api/v1/discovery/cases, a Control Deck view and a read-only Control Panel page.Discovery.md. -
Event scripts on Linux (no schema step; QuickJS 2025-04-26 vendored as source in
47498e67b, the row ind8308e04b). The Linux builds run event scripts in JavaScript, in Fabrice Bellard's QuickJS engine, for the same events under the same function names with the same objects;ScriptLanguagetakes a third word,JavaScript. Up to now a Linux server refused any script. Windows keeps VBScript and JScript and refuses JavaScript at load. A script has no file, process or network access, andScriptTimeoutand a 64 MB ceiling bound it.EventScripts.md; The COM API and Scripting 23.6. -
Fixed: a password could run code inside the server (no schema step;
5a3de9001, its test40003f6b6; CVE-2026-100741, CVSS 3.1 base score 9.8, issue #43). On Windows, with event scripting on, the language JScript and anOnClientValidatePasswordhandler inEventHandlers.js, any client that could reach a sign-in - SMTP AUTH, POP3, IMAP or anything else that checks a password - and name an existing, active account could have JScript of its own run insidehMailServer.exewith the rights of the service's account, before it had authenticated: the server escaped the apostrophe in the value it wrote into the handler's call and not the backslash before it. The same shape was open to a remote POP3 server through the UID it answers (OnExternalAccountDownload) and to a remote mail server through its error text and the failed recipient (OnDeliveryFailed), and a rule's Run script function action wrote its function name into the call unescaped in either language, so whoever may edit a rule, a domain's administrator included, could do the same. VBScript servers were not open through a password, a UID or an error text; the Linux builds had no engine. The code was there in every release from 6.0.0 to 6.3.3. Every JScript value is now escaped in one helper, and a rule's function name must be a plain identifier (HM5018 otherwise).EventScripts.md. -
Storage compression and attachment de-duplication (schema 6098:
hm_storeblobs, and thehm_settingsrowsMessageStoreCompression0 andMessageStoreDedupMinKB256; the seam236f25a94, the row2720e984e). Every place a message's file is read, sized, copied, moved or deleted goes through one unit,Common/Persistence/MessageStore. WithMessageStoreCompressionon - off as shipped - a message delivered or appended is stored deflated and each MIME part of at leastMessageStoreDedupMinKBis kept once for every message carrying the same bytes; every reader sees the message as it arrived, a stored file is checked by its SHA-256 before it is served, and the queue is never compressed.--measure-storesays what it would save and changes nothing;--compress-storerewrites what is stored, and--compress-store --decompresswrites it back plain;GET /api/v1/store, the Deck's dashboard, the Control Panel's Performance page and COMSettings.MessageStoreStatussay what it saves. With it on,Message.Filenameover COM names the stored form.MessageStore.md.
25 September
-
A COM change to a shared configuration object is written onto a copy (no schema step;
15cfff25d). For routes and their addresses, incoming relays, DNS black lists, SURBL servers, remote domain policies, server messages, blocked attachments, SSL certificates and groups, aput_*writes a copy the COM object holds, not the item every session is reading; it is in effect atSave(), a change never saved changes nothing, and an object read before the save keeps what it read. The old way let a session read a string while it was being freed. A save whose re-read fails is reported as error 7390.ARCHITECTURE.md,Upgrading.md; The COM API and Scripting 23.3. -
Address erasure reaches the stores beside the mailbox (no schema step;
e36b4f6fa): messages held for review, messages that expire, password-protected messages, the recipients an account remembers writing to, the out-of-office memory and the correspondents' S/MIME certificates and OpenPGP keys; every comma-joined list matched whole, so erasingann@never touchesjoann@; a legal hold outranks the erasure, andPOST /api/v1/addresses/eraseanswersheldbesideremoved.PersonalData.md. -
Mailbox encryption at rest (schema 6099:
hm_accountkeys,hm_domainrecovery, and the rowMailboxKeyIdleMinutes15;e3a1c9fc1,2e19cb336). Switched on per account - off on every account - after the domain's recovery key has been made: every message stored in the account's folders from then on is encrypted to an X25519 key of the account's own with AES-256-GCM, as the message store's last stage, whether or not anyone is signed in. Reading it needs the private half, which the account's own password opens at sign-in and which closesMailboxKeyIdleMinutesafter its last use. App passwords are refused for such a mailbox; an administrator's password reset while the key is closed goes through a recovery with the domain's passphrase; it cannot be combined with zero access or a TOTP second factor. REST and the Control Deck only, with no webmail card and no Control Panel page;--encrypt-storeand--decrypt-storerewrite what was stored before.EncryptionAtRest.md. -
Mail in, to a webhook (schema 6100:
hm_inboundhooks,hm_inboundhookqueue, and the permissionmanage_inbound_webhooksgiven toserveradmin;9786d15b7). An address, or a whole domain this server hosts, is routed to a URL, and the mail that arrives for it is posted there parsed - asmultipart/form-datain SendGrid's inbound-parse field names or as one JSON document - signed with the outbound webhooks' HMAC-SHA256 recipe and retried on their schedule; when the attempts run out a route keeps the message to be downloaded or retried, or returns it to its sender, and a route may deliver to the mailbox as well. Link-local and cloud metadata addresses are refused to everybody, an API key may name only a public address, and plainhttpis taken only to this machine./api/v1/inbound-webhooksand the Deck's Inbound webhooks view; no COM member and no Control Panel page. Nothing is on until a route is made.InboundWebhooks.md. -
PST import (no schema step; the row
4aa5feec8, its reader in a child process67fc697c2, codes 7550 to 7553). The server reads Outlook data files itself, with a reader of this project's own written from Microsoft's published specifications, as a migration of kindpst- one job per file into one account, with the migration row's throttle, resume marks, report and Deck view. Mail, folders and their flags and categories come in; contacts, calendar items and tasks go into the account's address books and calendars; notes and journal entries come in as messages. ANSI files are read, and an OST that is a PST in all but its name; the 4K-page layout, Windows Information Protection and an OST caching an Exchange mailbox are refused with a sentence saying what to do. The reader runs as a process of its own (hMailServer.exe --pst-read,hmailserver --pst-read), killed on a stop, a pause or a cancel, so a file that crashes it costs one message (reader_failed), not the service. The administrator copies the file into<PstImportDirectory>/<domain>/, andPstImportDirectoryis empty as shipped, so no PST job runs until an administrator names a directory.Migration.md. -
The project's own CI, on GitLab (no schema step;
9660893f1,d54c460ce,3b1356e34,2fff17b74,8868d54d1,7a1b9c817,11a7d7c9d)..gitlab-ci.ymldefines the platform-neutral checkers, the Deck's and the webmail's script harnesses and a sign-off check for a merge request from a fork on GitLab's shared runners; and, for a push tomaster, abatch*branch or av*tag only, the Linux build, its tester programs, the whole Linux suite, the packages, the container image, the AArch64 cross-compile, the database upgrade gates and the SBOMs on the project's Linux runner (linix), and the Control Panel, the tools and the whole Windows suite on its Windows runner (bench150); a nightly pipeline is to fuzz the three MIME harnesses once the owner creates its schedule. None of the jobs on the project's own runners had run when 6.3.4 was made - only the update feed'spagesjob had, on the shared runners - and the batches' Linux suites were run by hand onlinix. Security scanning - cppcheck and clang-tidy against a baseline, Semgrep, Trivy and OSV - is defined beside it, not yet blocking; Renovate is inert until its token and schedule exist. A test lab onlinix- MySQL 8.4, MariaDB 11.4, PostgreSQL 17, SQL Server 2022, Keycloak, Postfix and Dovecot,imaptestand Uptime Kuma - and an installer smoke test on a throwaway Windows Server 2025 VM, whose first run found four installer defects (847743193: an upgrade's CriticalHM5011event,hMailServer.exeand its DLLs left inBinby the uninstaller, the event log source and the COM AppID left behind). Nothing is read from GitHub.ContinuousIntegration.md,TestLab.md. -
The update feed off GitHub (no schema step;
7299c9e48, then the apply path's hardening186239784,589c7563c,c7215675a,4541e9313, and59ea917b8). The live update readshttps://updates.progressiverobot.com, which GitLab Pages serves from the project's releases, in place of GitHub's Releases API, which has answered 404 since 18 September 2026. It trusts two signers: the GitHub workflow that signed the releases from 6.2.19 to 6.3.3, for a certificate issued before the suspension and a release no later than 6.3.3, and the project's GitLab CI pipeline for av*tag, which is to sign the releases after 6.3.4 and has signed none yet. It already trusts where the project is going: the project is to move from the personal namespaceProgressiverobot/hmailserverto the grouphmailserver, which the owner reserved on 26 September 2026, and a move changes the path every signing certificate names, so 6.3.4 also trusts the same pipeline athttps://gitlab.com/hmailserver/hmailserver//.gitlab-ci.yml@refs/tags/v<version>, with the project's id,86730559, which a transfer keeps, required in the certificate whateverUpdateSigningIdentity,UpdateSigningIssuerandUpdateSourceRepositorysay (-included) - nobody who registers that path can sign as this project, and a release signed after the move installs by the live update of every server from 6.3.4 on; a server that still has the three GitLab valuesUpdateFeed.mdgave 6.2.28 to 6.3.3 set trusts the present path alone until they are cleared; a server from 6.2.28 to 6.3.3 checks no id, is never to be pointed at the group's path with them, and is upgraded by hand after the move. A file signed for one tag is refused as any other release; on Windows the installer's own version resource must name the release it is installed as; a version with a suffix is a pre-release whatever the feed says. On Windows theUpdatesfolder admits changes from SYSTEM, the Administrators and the service alone, a repaired one is emptied, one owned by another account is refused (HM6894), the folders are held open through the apply, and the update helper loads DLLs from System32 alone and runs the installer and the rollback image only with the SHA-256 the server read when it checked them. 6.3.4 carries no Sigstore bundle: every server installs it by hand, and a live update offered it says it cannot be verified and downloads nothing. A server from 6.2.28 to 6.3.3 still looks at GitHub untilUpdateFeedUrl=https://updates.progressiverobot.com/releases.jsonis added under[Settings]and the service restarted (Upgrading hMailServer 18.4c).UpdateFeed.md. -
SNMP (no schema step;
199b30ae4, codes 7420-7422). A read-only SNMPv3 agent (authPriv only: HMAC-SHA-256 and AES-128; SNMPv2c optional and off) answering the mail MIB (RFC 2789) and the standard groups from the same counters/metricsserves. Off, and answering nobody until a manager's address is inSnmpAllowedAddresses; UDP 161 by default; its eleven settings on REST and applied at a restart, with no COM property or Control Panel page. Its enterprise arc is RFC 5612's documentation number, not one registered to this project.Snmp.md; Ports Reference 4.6. -
Rooms and equipment (schema 6101:
hm_accounts.accountkind,hm_bookingpolicies,hm_bookinganswers,hm_bookingqueue,hm_bookingdecisions, andmessagesubmitteridandmessageauthdomainsonhm_messages;edb622ed6). An account can be a room or a piece of equipment (kindover REST, the Deck's account editor, COMAccount.Kind, dispid 88), which answers the invitations it is sent by itself, at delivery, by its booking policy - who may book, a booking window (180 days as shipped), a longest booking, working hours, conflicts and recurring meetings - and leaves what it does not decide to its delegates. It believes an organiser only from what the server recorded when the message arrived - the account that submitted it, or a DMARC or SPF pass for an outside domain the policy names - never from a header. A resource cannot be signed into. The policy is at/api/v1/accounts/{address}/booking-policy, with no COM or Control Panel page; nothing changes until an account is given a kind.RoomsAndEquipment.md. -
MySQL and MariaDB tables in utf8mb4 (schema 6102;
d4d84902c). Every table is converted to utf8mb4 underutf8mb4_general_ci, InnoDB andROW_FORMAT=DYNAMIC, and the database's default with them, so an emoji or a CJK Extension B character is stored instead of refused with error 1366; SQL Server, SQL Server Compact and PostgreSQL only move the version. The step rebuilds every table, needs MySQL 5.7.9 or MariaDB 10.2.2 and the ALTER privilege on the database, and can stop on error 1062 where two rows the old collation told apart are one to the new;build/check-mysql-charset.pyholds every later table to the same options.Upgrading.md,MigratingDatabaseBackend.md. -
The evidence pack and the supply-chain pins (no schema step;
d87ff717b,8cff42fac).EvidencePack.mdanswers a buyer's security questionnaire - where mail and personal data are kept, what leaves the server, ISO/IEC 27001:2022 Annex A and SOC 2 mapped to what the product provides, the supply chain - each fact with the file it can be checked in and each gap named. The .NET Desktop Runtime the installer bundles is pinned to 10.0.12 by version and SHA-512, the SBOM names seventeen components, andbuild/check-sbom-inputs.pyfails when a build input is missing from it. -
A new installation is secure before anyone configures it (no schema step;
d074a454e). A database this installation created - never an upgraded or attached one - is given a self-signed certificate for the machine (RSA 2048, 825 days), STARTTLS offered on 25, 587, 110 and 143, and TLS required for authentication on the Internet IP range, so a password from another machine no longer crosses the network in the clear; My computer is left as it was. The Windows installer does it after its database step; on Linux--create-databasemarks the database and the unit's first start within a day does it; the AppImage and the container run--apply-new-install-defaultsby hand. The setup checklist's new Passwords only over TLS item judges every server, new or upgraded.SecureDefaults.md. -
A dedicated low-privilege service account on Windows (no schema step;
3f8c44c8f,4d2c4a47f). A new installation runs the service asNT SERVICE\hMailServerinstead of LocalSystem; an upgrade keeps the account it has, and the installer's page, or/serviceaccount=virtual|localsystem|keep, offers the other.hMailServer.exe /GrantServiceAccessgrants the account the program folder's Data, Logs, Temp and Database folders and folders an administrator approved, and nothing the configuration merely names.hMailServer.INIis read-only to the service, which works on with the database's values. What only LocalSystem could do is refused with a sentence: the live update's apply (an administrator downloads the installer from the release page, checks its Authenticode signature and runs it), the auto-ban's firewall rules andUtilities.MakeDependent./Registerno longer usesServiceAccountNameandServiceAccountPassword, which are still read and nothing else: it takes the account from--service-accountalone.ServiceAccount.md. -
Mail labelled ISO-8859-1 is decoded as windows-1252 (no schema step;
b53a2c6e5), as the WHATWG Encoding standard and every mail client read it, and ISO-8859-2 to -9, -13 and -15 by their own code pages: there was no ISO-8859 name in the server's table, so a Western Windows server decoded Latin-1 right by accident, one with another ANSI code page decoded it wrong, and on Linux it became replacement characters. -
Encrypted database connections, and certificates for IP addresses (no schema step;
35c8b8ef6). MySQL and MariaDB through MariaDB Connector/C had never used TLS, whatever the documents said; with nothing configured they now use it whenever the server offers it. SQL Server through MSOLEDBSQL 18 and SQL Native Client encrypts the whole session. PostgreSQL is unchanged.[Database] TlsMode(prefer, the default,require,verify-full,disable) andTlsCaFilechoose otherwise; the application log says at every start what the connection got, and--check-databasesays the same. On Linux a certificate naming an address in aniPAddressentry is accepted, and no server name is sent for an address.DatabaseTls.md. -
The security scan's findings, fixed (no schema step;
9f414176f). The Windows installer's 7-Zip is 26.03, fetched from 7-zip.org, in place of 19.00, which predates the fixes for CVE-2023-31102 and CVE-2025-55188; every restore lists the archive first and refuses one whole with an absolute path, a..segment or a link with somewhere to point (HM7540); the in-process testers' barethrow;statements, which ended the service when a check failed, fail the test suite instead; MIME boundaries come from a per-thread generator seeded from the operating system instead ofrand(); the vendored zlib is 1.3.2; and VEX statements cover the advisories that do not reach the server.Backup.md. -
Secrets at rest (schema 6103:
hm_accounts.accounttotpsecretwidened to 1024;92fdd0243). An account's TOTP second factor and the[Settings]secrets (SRSSecret,BATVSecret,OAuth2HmacSecretand seven more) are sealed with AES-256-GCM under a stored-secrets key the installation's servers share, kept in each server'shMailServer.ini([Security] StoredSecretsKey) under DPAPI or the Linux key file, never in the database or a backup; the database records which key it is. The Windows installer makes the key on a new installation, and the container's--create-databaseon Linux; after an upgrade nothing is sealed until an administrator runs--create-stored-secrets-key, which the setup checklist offers.--export-stored-secrets-keyand--import-stored-secrets-keymove it, and--forget-sealed-secretswith a lost key's id clears only what that key sealed. The installer writes the administrator password as PBKDF2 itself, and a weaker stored hash is written again as PBKDF2 at the next administrator sign-in.SecretsAtRest.md. -
Object storage for the message store (no schema step;
13c961059,93806de9b,f81eb664e). Messages on an S3-compatible store (AWS S3, MinIO, Wasabi, Backblaze B2, Ceph RGW) or Azure Blob Storage, with the database as the index: a message is written to the local disk first and moved into the store by a background committer, so a store that cannot be reached delays nothing and loses nothing, and IMAP and POP3 are told to try again for a message only the store holds. TLS to the store is always verified.--copy-store-to-object-storageand--copy-store-from-object-storagecopy both ways, verified and resumable. Off until an administrator names a store:MessageStoreBackendisfileas shipped, and a server with no store reads and writes as before.ObjectStorage.md. -
The webmail at a hundred thousand messages (no schema step;
b2e1709a6). A folder's page copies one page of the folder, never all of it; Oldest reaches the end in one request; a search is bounded by time as well as by files read, spared by the full-text index, and goes on from where it stopped across every folder (GET /api/v1/me/searchanswerscomplete: falsewithnext_before_id); select-all hands the selection to the server (POST /api/v1/me/folders/{id}/bulk), which works through it in bounded steps. Built, and not yet measured: the seeding tool and the timing harness are inbuild/perf/, and the results table ofPerformance.mdis still empty. -
IMAP answers as the RFCs say, where Dovecot's imaptest found it did not (no schema step;
90df29ac5,2a16ebb2c). A command pipelined behind anAPPENDwith a non-synchronising literal is answered (mbsync copied one message and stopped);APPENDstores the literal byte for byte; a mailbox that does not exist and a missing right areNO, notBAD;*on the right of a UID range is the highest UID;ENVELOPEsaysNILfor what is missing and parses groups; andFETCH,SEARCH,SORT,THREAD,LIST,LSUB,LIST-EXTENDED,MOVE,CONDSTORE,UIDVALIDITYand\Recentfollow their RFCs. imaptest's scripted run went from 31 failing scripts of 71 to 4. Section 5 lists what a client may notice.
26 September
-
Published numbers, the tooling (no schema step;
86ce1bca0). A load generator,build/perf/load/loadgen.py- messages per second in and out, IMAP sessions per core under a latency limit, memory per session, and Dovecot's imaptest as a benchmark - and a lab recipe that runs it against hMailServer and against Postfix and Dovecot on the same machine under stated fairness rules. No number has been measured with it yet: the table ofPublishedNumbers.mdis empty. -
Windows on ARM, written, and compiled in batch 40 (no schema step;
2e9bee9eb). ARelease|ARM64configuration in the server's three projects,-Platform ARM64in the build, library and installer-input scripts, and a second installer,hMailServerARM64.iss(hMailServer-<version>-arm64.exe), sharing the x64 installer's sections; the ARM64 server would use SQL Server or PostgreSQL, since SQL Server Compact has no ARM64 build. Nothing of it was compiled at92b8f9f63; it was first compiled on 26 September 2026, in batch 40 (below), and has never run on ARM64 hardware, and 6.3.4 has no ARM64 installer; the x64 build and its SBOM are unchanged.WindowsOnArm.md. -
The version stamp and the feed (
25b4eb65a,59ea917b8): 6.3.4, build 43, inVersion.h, the installer script, the seven project files and the PKGBUILD; the feed's generator publishes 6.3.4 as a release without a bundle rather than leaving the feed at 6.3.3. And the repository's CHANGELOG.md gains 6.3.4's section (92b8f9f63).
What the tag v6.3.4 (51e2dbda0) adds to 92b8f9f63: the batch the project's commits call batch40, rebased onto master 95febd2fa. The schema moves from 6103 to 6104 in one step (section 2); DBUpdater registers new UpgradeScript(6103, 6104) (Tools/DBUpdater/formMain.cs:314) and the server requires it (REQUIRED_DB_VERSION 6104, Common/Application/Constants.h:1224). Two settings are added, kept in hm_settings as every setting has been since 6042: PasskeyAllowedOrigins and WebPushLoopbackHttpForTests (Settings Reference section 17). The suite has 3,811 [Test] attributes. The 6.3.4 section of the repository's CHANGELOG.md at the tag is the summary of each item below, with its operator document.
-
The audit before the release (schema 6104:
hm_fetchaccounts.faorigin; the folds05d7f1095,9132770e2,76087f3b6,c0998fb0a,67b7c193e,5e29e049e,9355865ea,9a267c267). Eight areas were audited in two rounds each, and what they found is fixed. Passwords outside Latin-1: every password hash was made over a conversion that turned a character above U+00FF into NUL bytes on Windows and?on Linux, so a password of Cyrillic, Greek or CJK letters could be opened by any other of its length; hashes are now made over UTF-8, and a hash made the old way of such a password is refused until the password is set again (item 51). Sign-in: SCRAM-SHA-256 took an account's own password past its second factor and pastPasswordPolicyMaximumAgeDays(6.3.3 had both); a sign-in under a domain alias, or with a token, a certificate or a Kerberos ticket, escaped the country policy; a one-time code could be used again within its window, and at several places a wrong one was not counted. Single sign-on and passkeys: a single sign-on ticket could be redeemed in another person's browser (it is now bound by thehmssocookie); with a parent-domain relying party a page on any sibling host could sign somebody in with their passkey, andpasskey_allowed_originsis now required beside it; a switched-off domain still let its people in by passkey or single sign-on. Delegated administration: where the server administrator's own mailbox sits in a domain somebody else administers, that domain's administrator could become the server administrator through an app password on it, delete or rename it, or repoint its fetch account, over REST and COM; COM no longer takes an app password as a logon. Keys: an app password or an administrator's support session could read the account's wrapped S/MIME, OpenPGP and zero-access keys; an app password could also replace the account's own S/MIME key and publish an OpenPGP key for the address, and a support session could change any of the account's keys or switch zero access off. Collecting mail: any person could collect any Microsoft 365 mailbox the server's own OAuth2 token reaches (a domain administrator over COM since 6.2.23 Alpha 1); a CR or LF in a fetch account's user name or password added commands; a fetch account's host could be pointed inside the network by DNS after it was saved - schema 6104 records who set each one up. IMAP: a heap-corruption abort when two sessions met on one message's keywords, flag changes lost when two arrived at once, an abandonedAPPENDthat left its file for good, an unboundedLISTpattern,ENVELOPEandBODYSTRUCTUREvalues that broke the response,COMPRESStaken before sign-in andSTARTTLSafter it. The REST listener and the webmail: request headers read from the body (a forged sign-in), one person stalling the webmail and the Deck for everybody, a Web Push endpoint pointing inside the network, large downloads cut off and held whole in memory, and a request head that could hold 16 MB. Personal data: the person's export refused to an app password and stripped of credentials, and the export and the log erasure matching an address whole. -
GitLab's SAST, and a release held by its scans (
d172914b1,21aedfcee,908623286). GitLab's Semgrep analyzer over master40c7d3e0creported findings that hold a release; seven of its High findings were real and are fixed - the crash-dump helper loads System32'sdbghelp.dllalone; the Control Panel's elevated service restart runs System32'scmd.exeandnet.exeby full path;DBSetupQuick.exehandsDBUpdater.exethe administrator password inHM_ADMIN_PASSWORD, on no command line (two findings; below); the backup index parser stops at the end of a malformed index and keeps an entity reference without its;as written (two); and a test of the MIME parser reads one array, where it compared pointers into two - and 169 others were read and judged not exploitable, each accepted with its reason in.gitlab/security-exceptions.yml. Found beside them and fixed too: the MySQL client's DLLs are looked for besidelibmysql.dlland in System32 only; the installer hands the password toDBSetupQuick.exethe same way; and a custom virus scanner whose command line holds a bare%FILE%is handed the path whole, where it was cut at its first space and scanned no file.build/ci/security-gate.pyholds a release while one of four scans did not finish or found something the policy counts that no baseline or exception accepts, andbuild/scan-secrets.pyruns gitleaks, with GitLab's rules, in the two git hooks the repository ships and in the maintainer's landing (Security Policy, Before a release and Secrets and credentials). -
The installer's administrator password (
21aedfcee). A password with a space, a double quote or backslashes before one failed a new installation at the database step, and a silent upgrade that moved the schema, with exit code 3: the installer put it unquoted onDBSetupQuick.exe's command line, and DBSetupQuick onDBUpdater.exe's. Both hand it on inHM_ADMIN_PASSWORDnow, the installer toDBSetup.exetoo, and an unattended install may give the installer the password inHM_ADMIN_PASSWORDin place of/adminpassword=, set in an elevated prompt (Installing hMailServer 4.5, Upgrading hMailServer 18.4a). -
An SBOM for every compiled file, and the signed sums of a release made by hand (
020f6c6c7,5c0d4a109). From 6.3.4 the installer, the.deband the.rpmeach go out with<file>.cdx.json, a CycloneDX 1.6 document whose subject is that file by its SHA-256 and SHA-512 (build/asset-sbom.py); the five GitLab releases before 6.2.3 that carry an installer carry one too since 26 September 2026. 6.3.4 carrieshmailserver-6.3.4-SHA256SUMS.txt, the SHA-256 of every file of the release but itself and its signature, andhmailserver-6.3.4-SHA256SUMS.txt.sig, an SSH signature of it made with the key that signs the release tags;ssh-keygen -Y verifychecks it against.gitlab/allowed_signers(Security Policy, Verifying a release). -
Stable download links (
dcf498aee).https://updates.progressiverobot.com/download/<name>, where<name>is a file's name without its version -hMailServer-x64.exe,hmailserver_amd64.deb,hmailserver.x86_64.rpmand the rest - leads to that file of the newest stable release, never a pre-release's, and/download/to that release's page; a name the stable release does not carry answers 404 (UpdateFeed.md). -
The release chain off a single forge, and landing through merge requests (
faca023d4,22c5a548d,c2d53e36c,00587974d, and the GitLab sweep fromdc75bbf23). The release jobs of.gitlab-ci.ymlfor 6.3.5 onwards, which refuse a tag that is not annotated, not onmasteror not signed by the one keymaster's.gitlab/allowed_signerslists; the live update trusts the grouphmailserverthe project is to move to, with the project's id required; and from the first landing after 6.3.4 a batch reachesmasterthrough a merge request, signed off (build/land-via-mr.py). None of the release jobs has run: 6.3.4 was made by hand (Release Process). -
What the first GitLab pipeline found (
286f1b03c,3df3dff22). The first pipeline of the whole.gitlab-ci.yml, on master40c7d3e0c, failed; each failure was the tree's or the job's, and each is fixed but for the static-analysis baseline, and the two findings of static analysis that were defects of the server's are fixed (the service account's name read withgetpwuid_ron Linux; an inbound webhook's URL printable ASCII on both platforms). The container image built from the Dockerfile installs7zipandpython3with the package. Then master's first full pipeline, 2886389460 on 27 September 2026 (master6bc09509d, 6.3.4 and one harness commit), ran the whole pipeline, its gates on the project's two runners, and passed 25 of its 29 jobs: both regression suites among them - windows-gate on bench150, 3,811 tests, 3,736 passed, 0 failed, 74 skipped and 1 inconclusive, and linux-suite on linix, 2,020 tests, 1,710 passed, 0 failed, 310 skipped, each in 2 h 23 min. It left db-matrix manual and failed secret_detection, semgrep and static-analysis, each on a fault of the tree's that batch41, the branch of 6.3.5, mends (work item #21, ContinuousIntegration.md). -
Windows on ARM, compiled (
6daf1e019). The first ARM64 compile, on the x64 development machine, built the three ARM64 libraries, the server, the tools and an ARM64 installer, and found two faults, both fixed for ARM64 only: libpq's cross build and the COM wrapper the tools load. Nothing of it has run, and 6.3.4 carries no ARM64 installer. -
What the release gate found, and the tag made again (
f5f6f0eae,ce1b030c1,604bd3e6d,7757d01ff,9e06e2f9c,d86295eda,27882888c,dd1159cef,545fef0d7,f89846acf,51e2dbda0, with the CHANGELOG'seb9c8f381). The tag v6.3.4 was first made on8adce850a, and that commit's release gate failed: 6 of 2,348 tests on this project's bench, 11 of 1,461 on the second, and the Linux suite in a cascade of 474 from its own test shim. Eleven fixes were folded into batch 40, and the gate, run again on the last of them,51e2dbda0, passed: the Windows suite in two halves, 3,811 tests, 0 failed (section 1, How the regression gate grew), and the 71 live-update tests again, elevated, on the second bench; the Linux suite on the packaged binary, 1,562 passed, 0 failed, 451 skipped; the database upgrade from 6029 to 6104 on MySQL and on PostgreSQL, each ending at the schema of a new database; the four security scans, whichbuild/ci/security-gate.pypassed, once the 286 findings that had held the release scan ofc76ff7e49, two commits before8adce850a, were read and accepted in.gitlab/security-exceptions.yml- 263 static-analysis, 4 semgrep and 19 semgrep-sast, read by AI review agents under the owner's delegation, expiring on 26 March 2027, all but one judged not a defect, the one a low-severity defect ofbuild/scan-secrets.py, a maintainer's tool no package ships (Security Policy, Before a release); the three MIME fuzzers for 30 minutes each, which found nothing; and the installer's smoke test from 6.3.3 to the signed 6.3.4, 65 of 65 once its check let the upgrade write the administrator password again as PBKDF2 (6bc09509d, a change of the test alone, on master after the tag). The tag was made on51e2dbda0, and8adce850awas never released. Three of the fixes change tests alone - a backup test that delivered no message for its control to find, a message-store test that left in the error log theHM7350it provokes, and the Linux shim's reset of the settings - and the other eight change the server. The first three below fix what 6.3.3 did too; the other five fix code new in 6.3.4, as8adce850ahad it, which no release carried:-
A relay that refuses the OAuth2 login no longer gets the server's token into the bounce. When a relay answered
AUTH XOAUTH2with a refusal - Exchange Online's535 5.7.3for a mailbox with SMTP AUTH off, say - the bounce to the message's sender quoted the line the server had sent, which is the server's own bearer token in base64, and the SMTP log and the POP3 collector's log kept that line too. The bounce now readshMailServer sent: AUTH XOAUTH2 <Token removed>and the logsAUTH XOAUTH2 ***, as they have always left out a password. Open since 6.2.22-pre1. - A rule's forward, copy, reply or notice to the sender, and an account's own forwarding, leave the queue at once, where each could wait up to a minute: each is queued from inside the delivery that caused it, after the delivery manager last read the queue, and nothing told the manager.
-
IMAP
LISTandLSUBread a reference or a pattern sent as a literal ({n}), as RFC 3501 allows, where they took the literal's length for the pattern, so such a command did not list what it asked for; 6.3.4's bound on a pattern - 8,192 characters with its reference, and 32 patterns in oneLIST,BADbeyond either - holds for a literal too. -
A real PGP/MIME message is sealed mail. The audit's check of the parts' names read a part with no name as
ATT-<n>.datand refused it, so every real PGP/MIME message - whose control part Thunderbird, Mutt and this server's own OpenPGP writer leave unnamed - and an unnamed S/MIME body were taken for mail the server can read: the blocked-attachment list and a scanner's strip action reached their ciphertext, and gateway encryption and a zero-access mailbox encrypted them again. A part with no name now passes, and a name a sender writes is still checked (SealedMail.md). -
A
FETCHrefused because a mailbox's key is closed is not an error. A delegate'sBODYSTRUCTUREof a message in a mailbox encrypted at rest whose owner is signed out is answeredNO [UNAVAILABLE], as before, and said in the debug log only, where each wrote a criticalHM10001to the error log; a damaged container is reported once, asHM7350(EncryptionAtRest.md). -
The help desk's password reset reads the password as the UTF-8 its body carries (
POST /api/v1/accounts/{address}/password), as the account holder's own change does, so the password policy counts its characters, the breach check asks about the password that was sent, and a mailbox encrypted at rest is wrapped under it;SecretsAtRest.mdnow says that a password's UTF-8 read one byte to a character opens, and sets, the same password. -
A brand-logo lookup or a key directory that took three seconds or more is held for an hour against its organization, whatever it came to - the organization the Public Suffix List names, where the DNS tree walk had made every subdomain of a domain without DMARC an organization of its own, so that the hold never covered the next one. A logo lookup's DNS queries count towards the three seconds; the name asked keeps its own answer, and a key that came is still given. A key is not looked for in a directory at an address literal, a name of one label or a name whose last label begins with a digit (
BrandLogos.md,OpenPGP.md). -
An alert mailed with a subject outside ASCII keeps every line of its encoded
Subjectwithin RFC 2047's 76 characters.
-
A relay that refuses the OAuth2 login no longer gets the server's token into the bounce. When a relay answered
Everything in this section is in 6.3.5, and none of it in 6.3.4. 6.3.5 is build 44, at schema 6105, one step from 6.3.4's 6104: hm_quarantine.quarantinehelo. It is the first release made by the project's GitLab CI from its tag - the Windows installer built on the project's Windows runner after the whole Windows suite had passed there, Authenticode-signed through Azure Artifact Signing, and every file signed with Sigstore as https://gitlab.com/Progressiverobot/hmailserver//.gitlab-ci.yml@refs/tags/v6.3.5 (issuer https://gitlab.com), the identity a 6.3.4 server trusts, so a 6.3.4 server's live update installs it on Windows and on Linux. Like 6.3.4 it carries the x86-64 .deb and .rpm only.
What changes on upgrade without a switch, five items: (1) the Control Panel's sidebar is in the order a new administrator needs - Welcome and Dashboard, then Set up this server with the Setup checklist (which was External setup), Setting profiles and DNS records; TLS & certificates is Certificates & encryption, Access & abuse protection is Sign-in & access, and Connections & protocols gains a Webmail page (a navgroup-/navrail- UI Automation id follows its new heading); (2) the quarantine shows the HELO name and groups held messages by the sender's domain, in the Control Panel and the Deck, which asks before a delete with a Yes or No of its own, offers whitelist and blacklist only to a credential holding manage_settings, and a held message's headers and preview only to one holding view_mailbox; (3) the webmail's keyboard - the message list is one Tab stop, Enter on a button presses it, Escape closes every modal dialogue, menus close on Escape, Plain is Remove formatting; (4) an invitation sent the way Outlook sends one is shown as an invitation (GET /api/v1/me/messages/{id} answers "invitation": true for a text/calendar part whose Content-Type carries parameters and which has no .ics name); (5) what a script sees: GET /api/v1/quarantine gives helo, sender_domain and helo_domain and four routes are new; COM Quarantine.Bulk (dispid 9) and QuarantinedMessage.HeloName, SenderDomain, HeloDomain, ReadHeaders, ReadPreview (11 to 15) are appended, no existing member or dispid changing.
- Reviewing the quarantine (schema 6105; GitLab issue #1), the feature behind items 2 and 5.
- 137 settings the server keeps in its database, which had no editor in the Control Panel, are given one.
- Everything else is in CHANGELOG.md's 6.3.5 section (CHANGELOG.md at v6.3.5).
Everything in this section is in 6.3.6, and none of it in 6.3.5. 6.3.6 is build 45, at schema 6108, three one-way steps from 6.3.5's 6105 (section 2: 6106 translates every IMAP external account's days to keep messages and changes no column; 6107 finishes the greylisting key, NOT NULL and unique - on MySQL and MariaDB over the stored generated column glipaddress2key, on PostgreSQL through two partial indexes; 6108 adds hm_neverbannames and hm_tcpipports.portsmtpauth). It is the first release signed at the group's path - https://gitlab.com/hmailserver/hmailserver//.gitlab-ci.yml@refs/tags/v6.3.6, with the project's id, 86730559, in the certificate - and the first that carries every file its pipeline makes: the x64 and ARM64 (preview, never run on ARM64 hardware) Windows installers, the generic .deb and .rpm for x86-64 and AArch64, each supported distribution release's own package, both AppImages, the source tarball and the Helm chart - sixty-two files, each with its Sigstore bundle, and the release's SLSA provenance. It is the last release for the time being: after it the project makes security releases only, for the 6.3 line until 30 September 2027 (Security Policy, While releases pause). Upgrading hMailServer 18.4e says what to do before and after upgrading. The full Windows regression suite and the Linux suite ran on the tag's own pipeline before it was made.
Every flaw below was found by the project's own review of the code; the work item beside each gives the details, public from the day of the release. The identifiers of #62 to #77 were not yet assigned when 6.3.6 was published; GitLab, as the CNA, assigned and published them on 8 October 2026.
| Identifier | What was wrong | Affected | Fixed |
|---|---|---|---|
| CVE-2026-103011 (#49) | A heap buffer overflow in the encryption routine of the legacy Blowfish scheme of stored secrets, inherited from the original hMailServer; 6.3.6 replaces that code with the project's own | every version up to 6.3.5 | 6.3.6 |
| CVE-2026-103010 (#49) | A heap buffer overflow in the decryption routine of the same code | Windows, every version up to 6.3.3 | 6.3.4 (its notes did not say so) |
| CVE-2026-104660 (#59) | COM objects that carry no credential - a message, its attachments or a fetch account a program creates itself, and the objects of an Application whose later Authenticate failed - were not refused what only a signed-in caller may do: with an administrator password set, a local Windows user with no hMailServer credential could queue mail, read and write files as the service's account, and attach a fetch account to any account |
Windows, every version up to 6.3.5 | 6.3.6 |
| CVE-2026-104658 (#58) | The Linux live update's apply step, which runs as root, took the unit it stops, and for an AppImage the image that checks the new file's signature, from the request the service account writes, so a service account that had been taken over could have root run a program of its choosing; after the restart the same helper wrote its log and outcome by name into the service account's Updates directory |
Linux, 6.3.4 and 6.3.5 | 6.3.6 |
| CVE-2026-107578 (#71) | The commands an administrator runs elevated, on a Windows server whose service runs as NT SERVICE\hMailServer, wrote their errors and crash records, and (--apply-new-install-defaults) a certificate and its key, by path into folders the service's account can change, and the store commands worked by path in the data folder; on Linux the store commands run as root followed a symbolic link the service's account planted, and every command run as root wrote its error reports into that account's log directory by name |
Windows, 6.3.4 and 6.3.5; on Linux the store commands in 6.3.4 and 6.3.5, the error reports from 6.3.0 | 6.3.6 |
| CVE-2026-107573 (#77) | The Windows installer left the Data, Logs, Temp, Database and Events folders and hMailServer.INI with the permissions Program Files hands down, which give BUILTIN\Users Read, so any account that could sign in to the computer could read every message, the logs, the event scripts, the built-in database while the service was stopped, and hMailServer.INI with the database's password. 6.3.6's installer makes them private to SYSTEM, the Administrators and the service at every installation and upgrade (/ProtectFolders); the Linux AppImage, which made its folders under the user's umask, makes them 0700 |
Windows, every version from 6.0.0 up to 6.3.5; the Linux AppImage too | 6.3.6 |
| CVE-2026-104659 (#57) | The REST administration listener's plain loopback port answered a request whatever its Host, so a web page that pointed a name of its own at the machine could reach the console through a browser on it; and wrong administrator passwords over REST from the loopback address were each looked at at once. A wrong one now costs a growing delay, up to ten seconds, from every source |
every version up to 6.3.5 | 6.3.6 |
| CVE-2026-103647 (#52) | The webmail handed the browser an attachment it had decrypted itself (S/MIME, OpenPGP) under the media type the message named; it is now always a file of no type | 6.3.2 to 6.3.5 | 6.3.6 |
| CVE-2026-104704 (#61) | Outbound DANE used DANE-EE records alone, so a recipient's MX host whose DNSSEC-validated TLSA records were all of another kind was delivered to as if it published none - in the clear when STARTTLS was not seen |
every version up to 6.3.5 | 6.3.6 |
| CVE-2026-107584 (#64) | Outbound DANE did not hold against anybody who could disturb a recipient domain's DNS: an MX or TLSA lookup that did not complete, or a signed zone's answer without its signatures or proof of absence, was read as "not signed", and the hosts came from the unvalidated MX lookup | every version up to 6.3.5 | 6.3.6 |
| CVE-2026-107586 (#62) | The REST listener's table of browser sessions dropped its least recently used session whoever held it, so an account holder signing in over and over with their own password signed out every idle webmail tab and browser on the server, the administrator's Control Deck among them from 6.3.0 | 6.2.28 to 6.3.5 | 6.3.6 |
| CVE-2026-107585 (#63) | The pending halves of a single sign-on and of a passkey sign-in, which anybody may start, were held the same way, so a client asking for sign-in starts a few times a second made every other person's OpenID Connect, SAML or passkey sign-in fail | 6.3.4 and 6.3.5 | 6.3.6 |
| CVE-2026-107587 (#66) | The webmail kept the S/MIME certificate of any signed message as a correspondent's, without a trust check and under the first address the certificate named, so later mail encrypted to that address was encrypted to it as well | 6.3.2 to 6.3.5 | 6.3.6 |
| CVE-2026-107583 (#65) | The webmail's view of a message's HTML put its embedded images back with work that grew with the square of the references, so one message could hold a REST worker for minutes and build a document of gigabytes | 6.3.2 to 6.3.5 | 6.3.6 |
| CVE-2026-107582 (#67) | Decoding the entities of a message with an HTML part and no text part took time growing with the square of their number, so one message held the REST worker that listed its folder, or an IMAP thread, for minutes; the same string replacement, reached by the DMARC test verifying a received message's DKIM signature, held the thread that accepts the message for seconds to tens of seconds | every version from 6.0.0 up to 6.3.5 (the message list from 6.3.3, the page of a message that expires from 6.3.4, IMAP PREVIEW from 6.2.22) |
6.3.6 |
| CVE-2026-107581 (#68) | Parsing one IMAP FETCH from a signed-in account could hold an IMAP worker thread, shared with SMTP and POP3, for minutes and far longer; the other parsers of a client's text with the same shape were fixed with it, and a FETCH whose sections of one message add up to more than sixteen times the message is now answered NO [LIMIT]
|
every version from 6.0.0 up to 6.3.5 | 6.3.6 |
| CVE-2026-107579 (#70) | With bounce or complaint processing on, or a list the list manager runs, a returned report begun by very many blank lines held a delivery thread for a minute and more | 6.3.4 and 6.3.5 | 6.3.6 |
| CVE-2026-107580 (#69) | A header field read as text was unfolded in time growing with the square of its line breaks, so a stranger's message held the REST worker that listed it, or the IMAP thread that searched, sorted or threaded it, for minutes and more; the DMARC test reads each received message's From that way | every version from 6.0.0 up to 6.3.5 | 6.3.6 |
| CVE-2026-107577 (#72) | Removing a MIME header parameter whose value is empty and directly followed by a ; did not terminate at all, holding a worker thread until the service was restarted, when the attachment blocker renamed a blocked attachment or a filename was set over the REST API; and decoding many encoded words of an encoding other than base64 or quoted-printable, or deleting many header fields of one name, took time growing with the square of their number |
every version from 6.0.0 up to 6.3.5 | 6.3.6 |
| CVE-2026-107576 (#73) | Verifying a received message's DKIM or ARC signatures took time growing with the square of the message's header, so a message with very many fields, or a field folded over very many lines, held a thread that serves delivery and SMTP for tens of seconds | every version from 6.0.0 up to 6.3.5 | 6.3.6 |
| CVE-2026-107575 (#74) | SPF macro expansion cut a name too long to look up one label at a time, so a record the sender publishes could make the SPF check - the SPF test's, or the DMARC test's - take several seconds | 6.3.4 and 6.3.5 | 6.3.6 |
| CVE-2026-107574 (#76) | The JSON reader kept the first of each member name in time growing with the square of an object's members; it reads the REST API's bodies and, where domain reports are on, a TLS report anybody may mail in, on the delivery queue, where a few such reports stopped all delivery for over an hour each | 6.3.2 to 6.3.5 (the REST API's bodies from 6.3.2, the reports from 6.3.4) | 6.3.6 |
| CVE-2026-107572 (#75) | A Sieve :matches test backtracked over every length of every *, and deleteheader erased its fields one at a time, over a sender's header but driven by the account holder's own filter script, on the delivery threads every account shares |
6.2.22 to 6.3.5 | 6.3.6 |
| CVE-2026-103649 (#53) | On Linux some network waits had no timeout - the MTA-STS policy fetch, the DANE TLSA query, the OAuth2 token request, the ACME client and its http-01 responder, the ManageSieve and metrics listeners, and in 6.3.0 to 6.3.2 the server's own DNS resolver - so a peer that stopped answering held the wait for as long as the connection stayed open | Linux, 6.3.0 to 6.3.5 | 6.3.6 |
Also fixed or hardened (no CVE for hMailServer; each in the CHANGELOG's Security section): the calendar's handling of an organiser's CANCEL and of the time zones a calendar object carries, and IMAP LIST's de-duplication (since 6.2.0), took time out of proportion to their input, and now take one pass; the list manager removes every List-*, Precedence, X-hMailServer-List-Member, Return-Path and DKIM-Signature field a posting brings, where it stopped after 64 of a name (6.3.4 and 6.3.5); calendar messages by mail (a cancellation, an answer, an updated invitation) changed a stored event without the server checking who sent them (6.3.4 and 6.3.5), and a room or a piece of equipment took a DMARC pass for an outside organiser's domain, or a message sent through the send API, as the organiser's own; IMAP COMPRESS=DEFLATE inflates no more than twice its receive buffer ahead of the commands it has read; ManageSieve (off by default) served one connection at a time with no bound on a command line, the TLS handshake or the time before authentication (since 6.2.3); the URI blocklists were asked about the first 16 names a message's links gave; a role holding manage_settings could put a [Settings] key back through the audit trail, and its breach-check test reached the server's own network (6.3.4 and 6.3.5); the Control Deck is served under a script policy that lets only its own script run (the SHA-256 of the page's one inline script, which the server takes from the page it serves), with Trusted Types, X-Frame-Options and Cross-Origin-Opener-Policy, the webmail's pages with the same three, and a response to a browser over HTTPS carries Strict-Transport-Security; an account's formatted signature reaches the webmail only as the subset its editor makes, and a message attachment, a file sent as a link and a contact's photo keep their declared type only when it is on a list of types no browser runs or renders as a page; the audit trail records creating or revoking a REST API key, enrolling or disabling the administrator's second factor, a read of another account's mail through an administrator's route, and a support session; COM's IP range, incoming relay and white-list addresses that could not be read were stored as 0.0.0.0 (every version up to 6.3.5) and are refused with the reason; a key put into base64 - the stored-secrets key --export-stored-secrets-key writes, an account's mailbox key before its first sign-in - and the stored-secrets key read from its line in hMailServer.ini are no longer left whole, in base64 or in hex, in memory freed without being written over (6.3.4 and 6.3.5). Third-party components: the Windows installers, x64 and ARM64, carry OpenSSL 4.0.3 where 6.3.5's carried 4.0.2 (CVE-2026-84783, Moderate, applies to the Windows server on a port that asks for client certificates, and every Windows installer from 6.0.0-B1 to 6.3.5 carried a 4.0.x it affects; CVE-2026-84782 is in DTLS, which hMailServer does not use), and with it libpq from PostgreSQL 18.6, built against 4.0.3, where 6.3.5's carried 18.3 (of PostgreSQL's fixes from 18.4 to 18.6 the one in libpq itself, CVE-2026-6477, is in the large-object functions, which the server never calls; the DLL keeps its name); event scripts on Linux run in QuickJS 2026-06-04, which fixes CVE-2025-62494, CVE-2025-62493 and CVE-2025-62492, reachable by a script, and CVE-2025-62495 and CVE-2025-62496, which the 64 MiB ceiling kept out of reach. Boost stays 1.92.0. The Linux packages, the AppImages and the container image use their distribution's OpenSSL and libpq.
- COM
Utilities: the helpers that do work (BlowfishEncrypt,BlowfishDecrypt,ResolveMXRecords,GetMailServer,IsLocalHost,CheckSieveSyntax,EvaluateSieveScript,CriteriaMatch) answer a caller that has signed in, or an event script in the service. - The live update's trust (
UpdateSigningIdentity,UpdateSigningIssuer,UpdateSourceRepository,UpdateTrustRootsFile,UpdateLogPublicKeyFile) is read fromhMailServer.inialone; COM and REST refuse a change (409). - The MX-record test falls back by the Public Suffix List: a sender at a public suffix with no MX fails.
- The SURBL test reads a link as a browser does, and a list's error answer (
127.255.255.x,127.0.0.1,127.0.1.255,127.0.2.255) is never a listing. - Backups leave out the live update's
Updatesfolder, and a destination inside the data directory works. -
tlds.txtis removed: the Windows installer deletesBin\tlds.txt, the Linux packages no longer carry it, error 4335 is retired. - The project's address is
gitlab.com/hmailserver/hmailserverin the Control Panel's links,security.txt, the systemd units and the packages; a database set up before keeps itsHelpWikiBaseUrl, which GitLab redirects. - The live update keeps its outcome after the start, tries a failed scheduled check again once ten minutes later, and keeps a verified installer across a restart.
- On Linux the backup and event logs are UTF-8, converted in place the first time each is opened; Windows keeps UTF-16LE.
- The
.deband.rpmrequireca-certificatesand carry the AGPL-3.0 text and a copyright file; every.rpmrequires glibc's extra character-set converters wherednfis installed on Fedora or EL and recommends them elsewhere. - Text in the Control Panel can be selected and copied on every page and dialogue, and every plain list copies its selected rows (work item 48).
- Scripting off runs no script engine: the event script is not compiled at start, reinitialise or reload; switching scripting on compiles it, and a script that does not compile is reported then.
-
Install update under
NT SERVICE\hMailServerinstalls the release from the Control Panel on the server with one Windows prompt;Status.InstallUpdatereturns false,POST /api/v1/update/installanswers 409update_install_by_administrator, and the checklist'supdate_apply_by_administratorandHM7463say so. - Smaller things a script, a monitor or a log reader may notice (the CHANGELOG lists them).
- Event scripts on Linux run in QuickJS 2026-06-04, ES2025.
- Three more web requests - the OAuth2 token request, the MTA-STS policy fetch and the ACME client - go through
HttpProxy; the MTA-STS fetch reaches only public addresses; on Windows the listeners claim their ports withSO_EXCLUSIVEADDRUSE, and a port another program holds fails to bind and says so. - A calendar message delivered by mail changes a stored event only from a sender the server verified.
- A cancellation of occurrences takes those occurrences out of a guest's calendar and leaves the rest.
- The answer from an invitation's card replaces a copy of its UID that nobody verified.
- Five more web requests - a brand logo, a Web Key Directory lookup, Web Push, one-click unsubscribe and an inbound webhook route held to public addresses - go through
HttpProxy. - The sender lists' URL entries read a message only as far as the anti-spam size limit, against every link within it, an address in its own order.
- An external account's days to keep messages means one thing for POP3 and IMAP (-1 delete once collected, 0 keep, N days), and the upgrade (schema 6106) starts no account deleting.
- The upgrade to schema 6107 finishes the greylisting key, and a greylisting race is never a pass.
- The Linux live update's helper takes the unit and the AppImage from its own command line (
--unit,--image). - Port 25 still takes
AUTHon an upgraded server, and the setup checklist's thirteenth item, No password on port 25, says so; a new installation's port 25 is a Honeypot (work item #54). - COM: a message or fetch account a program creates itself answers with the credential of an
Applicationthe same process holds and has signed in on;Application.CreateMessageis new. - The packaging Compose file's database is PostgreSQL 18.
- DANE-TA records authenticate a recipient's MX host, and a validated set of none this server can use requires TLS without judging the certificate.
- A ManageSieve connection is held to deadlines (30 seconds a command line and the TLS handshake, 300 a literal or a response, 60 to authenticate).
- The URI blocklists are asked about every domain a message links to (up to 200, then up to two host names under each, 256 names per list), and a message that links to too many scores
SurblTooManyDomainsScoreonce. - A host that the destination's TLSA records or MTA-STS policy require TLS of, and that offers no
STARTTLS, is deferred with450 4.7.0where it was refused permanently; a route's own STARTTLS (required) still refuses permanently. - An account, or the administrator, holds at most fifty REST browser sessions, and a full table takes from whoever holds the most.
- A pending single sign-on or passkey sign-in is carried by the browser, sealed (
hmoidc,hmsaml,hmpasskey), and is good once. - A
[Settings]key's audit row is put back by the administrator password alone, and the breach check's test reaches a public address only for an API key or a role. - A wrong administrator password over REST costs a growing delay, up to ten seconds, which an IPv6
/64shares and a passkey's registration asks for too. - DANE: an MX or TLSA lookup that cannot tell defers the delivery, and the validated MX records choose the hosts; a server whose resolver strips DNSSEC defers its mail.
- ManageSieve serves its connections side by side (16 at once, 64 waiting), one at a time per address before signing in, and bounds a signed-in session (ten idle lines, two minutes of waiting, four sessions an account).
- The webmail keeps a signer's S/MIME certificate only when its chain is trusted, under the From address, and a different one for an address that has one only on Keep this certificate.
- A message's HTML in the webmail shows its embedded images until 24 MB of them are written.
- The webmail refuses an OpenPGP message that inflates past 32 MB.
- What the auto-ban put below the server is lifted after a stop too (
AutoBanCommandtoldunban, the Linux hook toldreset; Windows' uninstaller removes the rules). - An IP address in text is read the same way on Windows as on Linux: four decimal parts, or IPv6 as RFC 4291 writes it.
-
hMailServer.exe's commands write nothing to the error log and say it all on their own output, and on Linux a store command run as root runs as the service's account (or on as root over a data directory root owns). -
/GrantServiceAccessthat cannot read the database ends in 7 unless an administrator confirmed. - The data, log, temp, database and event folders and
hMailServer.INIare the administrators' and the service's alone (/ProtectFolders, at every installation and upgrade). - A full restore keeps this installation's own self-signed certificate and key, and the server's own files at the data directory's root, and copies the archive's store in before it removes anything.
-
A crash fixed: an error reported where the settings held no
UseScriptServer- a database without that row - read the switch again, and reported it missing again, until the reporting thread's stack ran out and took the service down, at the first error reported, in every version up to 6.3.5. Such a report is now logged and fires no event, and the switch reads as off (Architecture, How the server reports a problem). -
Backups and restores: a message deleted by a POP3
DELEor an IMAPEXPUNGEwhile a backup copies the data directory no longer fails the backup, nor the update it was taken for; a full restore of messages failed on every installation made new with 6.3.4 or 6.3.5 and on Linux deleted the key everyLINUX1:secret is sealed under - 6.3.6 copies the archive's store in first, undoes a copy that fails, and only then removes what the archive does not hold (Backup and Restore). -
The installer exits 10 or 11 when a step after its files did not complete, and the live update's helper, on Windows and on Linux (
hmailserver --schema-version), puts the earlier version back only onto a database as it left it; an upgrade over a data, log or database folder behind a junction no longer fails at the service account's grant; a new installation is set up against SQL Server, PostgreSQL or MySQL with no window (/dbtype=and the other switches, orHM_DB_*), and on SQL Server with Windows authentication the installer givesNT SERVICE\hMailServerits login (--grant-database-login) (Installing hMailServer, The Service Account). -
Linux: the apply helper verifies and installs only copies it made after the stop, writes its log and outcome through files it opened while the server was stopped, and a request it refuses before stopping anything no longer leaves the server draining for ten minutes (
HM7151); the AppImage makes its folders 0700;hmailserver --check-configends with aCharacter sets:line; a store command or profile run as root runs as the service's account (Installing on Linux). -
Anti-spam and SMTP: the never-ban list by reverse name (
hm_neverbannames,/api/v1/never-ban-names, work item #56), an auto-ban widened to its reverse-name neighbours (AutoBanWidenByReverseName, off; #55), each SMTP listener'sAUTHOn, Off or Honeypot (#54),RefuseHeloNotFqdnandRefuseMailFromDomainNotFqdn(off; #51), the MX-record test and the SURBL test cutting names by the Public Suffix List compiled in (Stopping Spam, Ports Reference, Settings Reference section 17). - The Control Panel: text selected and copied everywhere, Live logs selecting several lines, the window's own buttons named for a screen reader, where the keyboard is shown in one violet (in the Deck and the webmail too), the SNMP tab under Logging, the never-ban list and the widening on the Auto-ban page, SMTP authentication in the port dialog, two boxes under Host checks and the SURBL score on the Anti-spam page, the live update's five trust settings shown and not edited, and Could not read the file on the live log page when it is not run as administrator (Control Panel Reference).
-
An account's retention through COM's
Authenticate(Windows): from 6.2.25 to 6.3.5 the copy of the account thatApplication.AuthenticateandAuthenticateWithCodehand out was made withoutMessageRetentionDays, so a webmail's password or out-of-office plugin that saved it could write another number over the account's retention, and the retention sweep then followed that number. After upgrading, check the retention of the accounts whose holders use such a plugin (the account's page in the Control Panel, COMAccount.MessageRetentionDays, RESTmessage_retention_days). -
IMAP: a
FETCHwhose sections of one message add up to more than sixteen times the message is answeredNO [LIMIT], each section counted by what it reads or makes, not only by what it keeps;$written into an element with anything else (1:$) is answeredBAD; aSORTcriterion named again is left out. -
A disk that fills as a message's last bytes are written is reported where the message was kept short: SMTP answers
451 4.3.0(HM5862), IMAPAPPENDanswers NO (HM5211), a collected message stays on the remote server for the next collection (Troubleshooting). -
Packaging, new in this release: a Nix flake with a NixOS module, an APKBUILD and an OpenRC service for Alpine Linux, a snap, a Debian source package and an RPM spec built from the source tarball,
hmctlas a PyPI package and the PowerShell module with a Gallery manifest, an Atom feed of releases (https://updates.progressiverobot.com/releases.atom), the container image atregistry.gitlab.com/hmailserver/hmailserverfor linux/amd64 and linux/arm64, and the Helm repository; the apt and dnf repositories and the other channels publish frommasteronce their keys and accounts exist, and none is live as the release is made. The last inherited code whose licence a distribution would have had to settle - the legacy Blowfish scheme, the backup index's XML and the date class - is the project's own. - Everything else is in CHANGELOG.md's 6.3.6 section (CHANGELOG.md at v6.3.6).
Everything in this section is in 6.3.7, and none of it in 6.3.6. 6.3.7 is build 46, at schema 6108, as 6.3.6 has it: it adds no step, so an upgrade from 6.3.6 changes no table (section 2). It is the first security release since releases paused, cut from master with the finished work master held then (Security Policy, While releases pause), and it fixes vulnerabilities in every earlier version: upgrade every server running 6.3.6 or earlier. The tag v6.3.7 (31d656c0c) was made on 17 October 2026, and the release was published by its pipeline the same day at 23:52 UTC, signed with Sigstore at the group's path as 6.3.6 was - https://gitlab.com/hmailserver/hmailserver//.gitlab-ci.yml@refs/tags/v6.3.7, issuer https://gitlab.com, with the project's id, 86730559, in the certificate. It carries the files 6.3.6 did: sixty-two, the thirty-five the release is made of - the x64 and ARM64 (a preview, never run on ARM64 hardware) Windows installers, the generic .deb and .rpm for x86-64 and AArch64, the eighteen packages of the supported distribution releases, both AppImages and the two archives of their sources, the source tarball, the Helm chart, the PowerShell module's two files, the Linux sums file and the source tree's two SBOMs - and an SBOM beside each of twenty-seven of them, each file with its Sigstore bundle, a .sigstore.json copy of the bundle beside each of the thirty-five, and the release's SLSA provenance: 160 links. The release's notes begin with a Download table, which gives the x64 installer's SHA-256, dcae96bd75d6cae8e5a910d15940567067539ed99db5aeed40a815added4d85f. The container image, registry.gitlab.com/hmailserver/hmailserver:6.3.7, also tagged 6.3 and latest (sha256:2a5de4ebd04cd1e39f69ebeca3e905dd80217c3178132bd17a07880845ae629f), for linux/amd64 and linux/arm64, was pushed by the release's own pipeline once the release was published, and signed by it (Running in a Container).
Its gates on the tag's pipeline, before anything was published: the Windows suite in its two halves at once, windows-gate on bench150 (2,794 tests, 2,732 passed, 0 failed, 62 skipped) and windows-gate-s1vm on S1-WINBENCH (1,652 tests, 1,634 passed, 0 failed, 18 skipped); linux-suite (2,652 tests, 2,284 passed, 0 failed, 368 skipped). The signed installer went through the installer smoke test: an upgrade from 6.3.6 passed 98 checks and a new installation 64, none failed.
Who installs it by the live update. The update is applied by the version already installed (Upgrading hMailServer 18.4f has each route):
-
From 6.3.6 the live update installs 6.3.7, with 6.3.6's own file as its rollback image: a Windows service running as LocalSystem by itself; one running as
NT SERVICE\hMailServeronce an administrator presses Install update on the Server status page of the Control Panel on the server; on Linux 6.3.6's own update helper, for the generic.deband.rpmof either architecture and both AppImages. A server installed from one of 6.3.6's per-distribution packages is offered the generic package, which apt refuses on Debian 12 and dnf on Enterprise Linux 9: upgrade it with its package manager and its own release's package of 6.3.7. -
From 6.3.4 or 6.3.5 what 6.3.6's notes said still holds: a Windows service running as LocalSystem installs 6.3.7 by its live update; one running as
NT SERVICE\hMailServerdownloads and verifies it and leaves it to an administrator (HM7463), who installs it by hand, once; on Linux install it with apt, dnf or zypper rather than by the live update. - A server whose three GitLab trust settings still name the path the project began at refuses 6.3.7, as it refused 6.3.6, until they are cleared (Upgrading hMailServer 18.7).
- From 6.2.28 to 6.3.3, and from 6.3.3's AArch64 packages or AppImages, by hand, once; older than 6.2.28, run the installer, as always.
Every flaw below was found by the project's own reviews of the code but #94 and #95, which upstream hMailServer found in code this project shares with it; the work item beside each is the project's record of it. The identifiers of #86 to #97 were not yet assigned when 6.3.7 was published; GitLab, as the CNA, assigned and published them on 21 October 2026. Five of the fixes - work items 78 and 81 to 84 - and their entries in CHANGELOG.md were public in the repository before the release, on GitLab from 6 and 7 October 2026 and on master from 7 and 9 October 2026, by the owner's decisions (Security Policy, What gets a release).
| Identifier | What was wrong | Affected | Fixed |
|---|---|---|---|
| CVE-2026-107571 (#78) | On a Windows server installed in a folder at the root of a drive, the program folder, Bin and ControlPanel took their permissions from the drive's root, so any account that could sign in to the computer could replace the programs the service runs, or put a library in Bin that Windows loads into it first. hMailServer.exe /ProtectFolders, which setup runs at every installation and upgrade, now hardens the three; four narrower cases are not fixed in this release (below) |
Windows, every release from 6.0.0 to 6.3.6 installed at a drive's root; the default folder under Program Files was never affected | 6.3.7 |
| CVE-2026-107567 (#84) | Mail from outside whose From named a domain hosted here was signed with that domain's DKIM key, and so was every forward and copy of it; and an authenticated submission was signed as whatever hosted domain its From named, which 6.3.7 does only where the account may send as that address |
every version that signs | 6.3.7 |
| CVE-2026-107568 (#82) | An account's Sieve notify was sent from whatever its :from said, so any account with a Sieve script could send mail through this server as any address, and the server signed it with its DKIM key where it hosts that address's domain |
from 6.2.22 | 6.3.7 |
| CVE-2026-107566 (#83) | An account's Sieve editheader could rewrite who a message was from ahead of a redirect, and have this server sign the redirected copy as that address's domain |
from 6.2.22 | 6.3.7 |
| CVE-2026-107568, CVE-2026-107566 (#82, #83) | A domain's or an account's rule had the same gap: a Reply from an address its owner may not send as, a Set header value of From, Sender, the Resent- pair or DKIM-Signature
|
every version | 6.3.7 |
| CVE-2026-107569 (#81) | Any sender could lift the rule loop limit off a message, an account's own Sieve script or rule could reset it, and a Sieve notify did not consult it, so a loop of generated mail could run without bound |
every version | 6.3.7 |
| CVE-2026-114478, CVE-2026-114474 (#90, #91) | A forward, a redirect, a list's copy or an alias's copy of mail from outside - a forward of a message downloaded from an external account among them - was given an envelope sender, or for a null one sent under a host name, in the organisation of a domain its From names, which it now is only when the message passed DMARC for it on arrival |
every version | 6.3.7 |
| CVE-2026-114489, CVE-2026-114475, CVE-2026-114477 (#86, #89, #96) | On a server with several tenants, a credential confined to named domains could make a few changes that reach beyond its domains - renaming a domain, a domain alias, a custom role, a rule's bind_to_address, send_using_route or script_function action, a domain's relay host on the server's own network - and could read the server-wide metric history |
CVE-2026-114489 every version, CVE-2026-114475 from 6.3.4, CVE-2026-114477 from 6.2.25 | 6.3.7 |
| CVE-2026-114490, CVE-2026-114476 (#87, #88) | Over COM an account given the serveradmin role with a list of domains was the server's administrator, its list aside; and over REST and COM a role could be taken away from an account by a credential that does not hold what the account's admin level then gives it |
from 6.3.4; CVE-2026-114490 on Windows | 6.3.7 |
| CVE-2026-114470 (#93) | A credentialed REST request a browser made from another site was accepted (cross-site request forgery): HTTP Basic, which a browser keeps once it has answered the sign-in box, was held to nothing | every version | 6.3.7 |
| CVE-2026-114472 (#97) | The ACME HTTP-01 challenge responder served one connection at a time, so idle clients could make a certificate's renewal fail | every version | 6.3.7 |
| CVE-2026-114479 (#92) | The anti-virus action Remove all attachments and deliver the text delivered a message whose one part is an attachment, and an S/MIME message signed as one part, as they arrived, with what the scanner had found in them; 6.3.7 replaces each with the action's note, and deletes a message it cannot read. The default action, which deletes the message, was never affected | every version | 6.3.7 |
| CVE-2026-114473 (#94) | With SRS on, mail to an SRS address this server had handed out was not held to the relay rules | from 6.2.1 | 6.3.7 |
| CVE-2026-114471 (#95) | The SMTP log wrote some credentials in the clear (upstream issue 654) | every version | 6.3.7 |
Also fixed or hardened (no CVE for hMailServer; each in the CHANGELOG's Security section): an ARC seal this server makes attests its own SPF result, is made by the domain that relayed the message, and names the domains its results are for (from 6.0.0, which brought ARC sealing); --import-stored-secrets-key, and the reading of the stored-secrets key from its line, left copies of the key in memory freed without being written over, and a mailbox key encrypted at rest stayed open past MailboxKeyIdleMinutes for as long as the scheduled tasks held up their minute (both from 6.3.4); an SRS address or a BATV tag almost a whole cycle old was taken for fresh (upstream PR 649); a SASL PLAIN client that asked to act as another account was signed in as itself and told it had succeeded (upstream issue 652). Both Windows installers carry the .NET Desktop Runtime 10.0.13, Microsoft's security release of 13 October 2026, where those of 6.3.4 to 6.3.6 carried 10.0.12 - setup leaves a runtime the machine already has as it is, so an upgraded server keeps the one it had - and the x64 one, as the MySQL and MariaDB client, MariaDB Connector/C 3.4.11, built from MariaDB's source, where 6.2.3 to 6.3.6 carried 3.4.9, which has CVE-2026-107819; the ARM64 installer carries no MySQL or MariaDB client (Database TLS, Third-Party Binaries).
Reported from outside and fixed besides: the Control Panel's IP ranges page, which could show no list of ranges at all (work item 79); mail to a DNSSEC-signed domain whose MX answer came compressed, live.com among them, which DANE enforcement deferred (work item 80; Diagnosing Stalled Mail); and classic Outlook's POP3 downloads, which stopped at the UTF8 it sends after the login (work item 85).
What 6.3.7 does not fix. Four narrower cases of work item 78 stay open, by the owner's decision: setup hardens a program folder before it copies anything into it only when setup makes the folder, and then just after making it; a folder already there is hardened in place, before the copy, only for an installation of the Control Panel alone, and otherwise by /ProtectFolders at the end of setup; a link or a junction in the program folder's path is named in setup's log as not hardened, and not refused; and the files in Bin and ControlPanel are not listed by their owners. None of them reaches an installation in a folder only the administrators may change - the default under Program Files is one - unless a link that another account controls is on its path. To have an installation at a drive's root hardened before the upgrade runs anything from it, follow The Service Account, Before upgrading an installation at a drive's root, first.
- Mail from outside is no longer signed as the hosted domain its
Fromnames, and nor is any copy of it. - An authenticated submission is signed as another hosted domain only where its account may send as the
From's address: give an account the alias or the grant first. - A rule or a Sieve script no longer edits who a message is from, its loop count or
Auto-Submitted: a script that tries is refused when it is stored or checked; one stored before runs without the edit, and the application log names it. - A rule's Reply goes from an address its owner may send as, and a rule's notices from its owner's domain.
- A Sieve
notifygoes from the account's own address unless its:fromreaches the account, as avacationreply already did. - The rule loop limit holds for every message: a count that is not digits alone, or is above the limit, reads as the limit, and
RuleLoopLimit0 now stops out-of-office replies too. - A line break in a header value is written as spaces unless it folds the line, and COM's
Message.Saverefuses a credential's message whose values hold one that does not fold them. - On Windows,
/ProtectFoldershardens the program folder,BinandControlPanelat every installation and upgrade; under Program Files the first run makes the three folders' inherited entries their own, and aCHECKline names what it does not change. - An ARC seal attests this server's own results, and says whose they are.
- The quarantine's file names carry where a message came from.
- A copy of mail from outside keeps its envelope sender unless the message passed DMARC for the organisation its
Fromnames; a copy that would still carry an identity of that organisation is not sent to an outside address, and the application log names each recipient. - Under Remove all attachments and deliver the text, a message that is one attachment arrives as the note alone, and one the server cannot read is deleted, its recipients told.
- SASL PLAIN that asks to act as another account is refused by SMTP, POP3 and ManageSieve.
- With SRS on, mail to an SRS address is held to the relay rules unless it is a bounce.
- The SMTP log hides every line of a SASL exchange but the user name of
AUTH LOGINand the authentication identity ofAUTH PLAIN. - MariaDB Connector/C 3.4.11, with two plugins fewer: a database account that signs in with MySQL Enterprise's Windows authentication can no longer sign in, and the server then cannot open its database.
- A credential confined to named domains is refused what reaches beyond them, over REST and COM; a cross-site state-changing request is refused; over COM an account given
serveradminwith a list of domains is confined to them - give the role with no list to an account that is to administer the whole server. - A database write that failed is reported (
HM5032), and a lost connection on MySQL and MariaDB is reconnected and the statement run again. - A COM
Databasetransaction that the database ended is refused, not carried on:Database.CommitTransactionthen fails. - A DNS lookup that fails is a failure, not a name with no addresses (upstream PR 640).
- With
DisconnectInvalidClientson, more refusals count towardsMaxNumberOfInvalidCommands, an event script's554among them. - A message over 80 MB goes through the rules, the forwards, a bounce and the spam headers by its header alone.
- A link's host that is not ASCII is read by UTS 46, and an HTML part's character references as a browser reads them, for the SURBL test and the sender lists' URL entries.
- SpamAssassin is sent a collected message's own
Return-Path, ahead of the message, never written into it. - IMAP:
COPYandMOVEby message number read their set in the client's numbering, and refuse a set naming a message just expunged (NO [EXPUNGEISSUED]); with what else the CHANGELOG lists. - POP3 accepts
UTF8after the login, so that classic Outlook downloads its mail. - A rule's Run function is refused when it is saved unless it names a plain function.
- Stops, starts and reinitialises of the servers take turns, and the stop gives the message indexer a minute; setup and the live update wait
ShutdownDrainSecondsplus 90 seconds for the stop. - Scheduled tasks run in the second they are due, and no longer wait an hour when the clocks go back.
- With the full-text index on, a search asks the index a window at a time, and a start no longer holds the database for minutes.
- An HTTP answer that outlasts the request deadline is written whole.
- The Control Panel's pages moved: Updates is a page of its own under Maintenance, API & monitoring is Monitoring & alerts, and Ctrl+K takes every old title and route to its page.
- Smaller things a script, a monitor or a log reader may notice, each in the CHANGELOG.
-
A security release, and more than fixes. By the owner's decision of 13 October 2026, 6.3.7 carries the work that was under way that day: the Control Panel's corrections to its pages, texts, translations and keyboard and screen-reader behaviour, and its regrouped sidebar (Control Panel Reference); IMAP and POP3 taking the client's address from a PROXY header (High Availability Runbook); a link's host that is not ASCII read by UTS 46 (Fuzzing, UTS 46 against ada); the first units of the active-active work, which is still not built; and the Windows suite run in two halves on two benches, with GitLab's hosted runners available by a switch (Regression Test Environment). By his decision of 15 October 2026 it also carries the work already done for a later release - the first step of the Control Deck's and the webmail's rebuild planned for 6.4, with no product code - and more fixes: the COM
Databaseobject once the database has ended a script's transaction, and the stop of a work queue that gave up on one of its threads. -
The .NET 10 Desktop Runtime on an upgraded server stays the release it was, 10.0.12 where 6.3.4, 6.3.5 or 6.3.6 installed it: bring it to 10.0.13 or later through Microsoft Update or with Microsoft's installer;
dotnet --list-runtimeslists what a machine has (Upgrading Guide, The .NET 10 runtime). -
Known issues: the ARM64 installer has never run on ARM64 hardware, and the AArch64 Linux files are cross-compiled and tested under emulation; the apt and dnf repositories and the other package channels publish from
masteronce their keys and accounts exist, and none was live as 6.3.7 was made; 6.3.7 is not on the Snap Store, which holds the project's name and no snap yet: the job that puts a release on itsedgeandcandidatechannels, started by hand once the release is published, failed for 6.3.7 on a tool missing from its image, after both snaps were built; an AppImage on Fedora, Enterprise Linux or openSUSE needsglibc-gconv-extraorglibc-gconv-modules-extrainstalled by hand (Installing on Linux). - Everything else is in CHANGELOG.md's 6.3.7 section (CHANGELOG.md at v6.3.7).
5. Behaviour that changes on upgrade without a switch (cumulative 6.2.10 → 6.2.28, and 6.3.4; 6.3.5's five, 6.3.6's forty-six and 6.3.7's thirty-three are in 4f, 4g and 4h)
The 6.2.24 seven, verbatim in substance (source: v6.2.24 body "Behaviour that changes on upgrade, without a switch"):
-
MAIL FROM/AUTHafter STARTTLS without a freshEHLO→503 Bad sequence of commands(source: SMTPConnection.cpp:527, 554). - Relayed/fetched mail: anti-spam tests run against the address the relay observed, not the HELO literal (source: alpha2 body).
- Mail refused with a temporary error below 100 MB free —
MinimumFreeDiskSpaceMB=100; set 0 to restore (source: IniFileSettings.cpp:417). - Critical/High errors also go to the Windows Application event log —
WindowsEventLogEnabled=1 (source: IniFileSettings.cpp:628). - Existing per-account out-of-office replies honour RFC 3834 suppressions (source: alpha1 note 10).
- One bulk prune of
hm_imapexpungedat first start —IMAPExpungeRetentionRecords=5000; set 0 first to defer (source: IniFileSettings.cpp:646). - Early-bound COM clients built against a 6.2.22 pre-release interop must be recompiled (source: alpha1 note 4).
The 6.2.25 eight (source: v6.2.25 body "Behaviour that changes on upgrade, without a switch"):
-
/email.mobileconfigis served over HTTPS only — 301 to the HTTPS listener when configured, 403 when not, unless a proxy sendsX-Forwarded-Proto: https(source: WebServicesServer.cpp:653-659,758,807). - A Message-ID is no longer added to relayed mail, only to submissions (source: SMTPMessageHeaderCreator.cpp:83-100).
- With IMAP ACL enforcement off, a rule or a delivery into a public folder is allowed, as every IMAP command already allowed it (source:
ACLManager::CheckPermission, Common/Application/ACLManager.h:49). - The four HTTPS clients (ACME, OAuth2 token, MTA-STS fetch, introspection/JWKS) require TLS 1.2 or later whatever the mail-protocol toggles allow (source: v6.2.25 body; not located by grep in HttpsClient.cpp).
- The AUTH command and its SASL responses accept lines of up to 12288 octets on SMTP and POP3 (were 510 and 500) (source: SMTPConnection.cpp:483; POP3Connection.cpp:299).
- IMAP sequence numbers are stable within a session (upstream PR 602).
-
Account.DeleteMessagesempties a designated special-use folder and keeps it, as it always kept the inbox. - A DANE TLSA line that cannot be computed after an ACME issuance is logged as such; the certificate is deployed first either way.
6.2.26 and 6.2.27 change no behaviour without a switch (6.2.27's schema step adds one defaulted column).
The 6.2.28 four:
- IMAP advertises
COMPRESS=DEFLATEand compresses both directions of a session that asks for it —IMAPCompressionEnabled=1; set 0 to withdraw the capability (source: IniFileSettings.cpp:497; IMAPCommandCapability.cpp:61-62). - The REST API and the web services are served by
HttpServer: 64 KB per request, 64 concurrent connections and absolute 30 s per-request / 300 s per-connection deadlines on REST, 128 connections and 15 s / 120 s on web services, andCache-Control: no-storeon API answers. A request that outlives its deadline is closed without a response, where before nothing bounded it (source: HttpServer.h:79-101; RestApiServer.cpp:128-136; WebServicesServer.cpp:45-49). - Wherever the REST listener is enabled,
GET /portalanswers with a sign-in page and an account's own address and password reach the/api/v1/meroutes. There is no separate switch: the portal is inert only whileRestApiPort=0 (source: RestApiServer.cpp:1281-1284, 1581-1738). - The Control Panel opens in the Windows display language when one of the seventeen translations matches it; the language picker chooses another, English included (source: Services/LanguageChoice.cs).
6.3.0 to 6.3.3 are not itemised here; sections 4b to 4d say what each ships.
In 6.3.4 (section 4e):
-
The database is the settings store, and an edit to
[Settings]inhMailServer.iniis not used (schema 6042). At the first start the store adopts every key it has not seen and keeps the rows the old mirror already holds, which agree with the file as the old server last read it - so a line edited after the old server's last start, including one edited to prepare the upgrade, is taken for an edit, and put back where the service may write the file. From then on an administrator who edits a[Settings]line finds the stored value in effect,HM5804in the error log naming the key, and the line restored where the service may write the file - underNT SERVICE\hMailServer, to which the file is read-only, the edit stays in the file (The Service Account); deleting a line no longer returns a setting to its default, because the row is kept, and the line written back where the file may be written. A setting is changed in the Control Panel (which needs a connection to the server to do it), withSettings.SetIniSetting/DeleteIniSettingover COM, withPUT/DELETE /api/v1/settings/ini/{name}(the administrator password; an API key gets 401), or withhmctl ini set- not from the Control Deck, which has no editor for these keys beyond the few its Settings page shows as typed rows, whatever theReadMeline the server writes into the file says - and a script or configuration-management tool that writes the file to configure the server has to move to one of those. To force a value from the file - the database unreachable, or holding a value the server will not start on - put the key in a[SettingsOverride]section: it is applied over the store even when the table cannot be read, and reported asHM5807at every start until it is removed; a value saved while an override shadows it is stored but not in effect (HM5808).PasswordPepperis the exception: it stays in the file only, its copy inhm_inisettingsis removed at the first start, and it is not in any backup archive written from then on. A restore onto new hardware therefore needs the pepper copied into the new file's[Settings]by hand, or no account whose password is stored as Argon2id (5) or scrypt (7) can sign in, since those two schemes are peppered and PBKDF2 (4, the default) is not. An archive written by 6.2.19 to 6.3.3 with settings included carried the pepper; restoring one writes it into a file that has none.[Directories],[Database],[Security],[GUILanguages],[SendingLimits],[SendingLimitsOverrides]and[LDAP]are still read from the file and changed there (source: Common/Application/IniSettingStore.cpp:309-406, 551-554, 683-767; Common/Application/IniFileSettings.cpp:917-944, 1012-1016; Common/Util/Crypt.cpp:64-78, 126-137; Tools/ControlPanel/Services/IniFeatureStore.cs:192). -
SPF no longer passes localhost unconditionally. The RFC 4408 library returned a pass for a client in 127.0.0.0/8 or
::1before reading any policy; the RFC 7208 evaluator has no such exemption. Where the SPF test (usespf, off in a freshly created database) or DMARC runs for a loopback client - the stock My computer IP range has anti-spam on - mail from a domain whose policy does not name the address is evaluated like any other: a-allpolicy fails and scoresusespfscore(3 by default), and DMARC loses that SPF pass. A content filter or script that re-injects mail over 127.0.0.1 should authenticate or connect from an IP range whose anti-spam is off. Also without a switch: SPF lookups on Windows go throughDNSServer, the DNS cache setting and the query timeout, which they did not; SPF works on Linux, where every check was TempError; andAuthentication-ResultsandReceived-SPF(both off by default) say none, softfail, temperror or permerror where they said neutral (source: SMTP/SPF/RMSPF.cpp:4348-4360 at v6.3.3; SMTP/SPF/SPF.cpp:66-92; Common/AntiSpam/SpamTestSPF.cpp:59-130; DBScripts/CreateTablesMSSQL.sql:937, 1058, 1145). -
The audit trail is on. After the 6043 step
AuditTrailEnabled=1 andAuditRetentionDays=0: an administrative change made over COM (the Control Panel, scripts) or REST (the Deck,hmctl, API keys) writes a row tohm_audit, kept until a retention is set, with secrets recorded as(changed)- with the gaps section 4e names: the five kinds of object whose table is misspelt in the watched list,hm_acl,hm_remotedomainpoliciesand the[Settings]keys leave no row. It is read atGET /api/v1/auditor on the Deck's Audit trail page; settingAuditTrailEnabledto 0 stops it, and is itself recorded (source: DBScripts/Upgrade6042to6043MSSQL.sql; Common/Util/AuditTrail.cpp:540, 849-852). -
Alerts are on, with seven of their eleven conditions.
AlertsEnabled=1, withbackup.failed(mailed at once) andcertificate.expiring(7 days before a configured certificate expires, in the daily digest at 07:00 UTC) switched on anddisk.low,queue.stalled,autoban.stormandminidump.writtenoff by the 6043 step; the steps after it add five more, each seeded on -dkim.rotation.stalled(6048),complaints.rate.high(6055),reputation.listed(6057),account.compromised(6071) andaccount.password-reset(6076) (source: thehm_alertrulesinserts of those five upgrade scripts; hmailserver/docs/AlertsAndAuditTrail.md, "What ships"). Nothing is mailed until bothAlertRecipientandAlertSenderAddressare set, and no webhook exists until a rule is given one, so an upgraded server sends nothing - but an enabled condition is recorded inhm_alerteventswhen it fires, andAlertTaskruns every minute from the first start (source: DBScripts/Upgrade6042to6043MSSQL.sql; Common/Util/AlertManager.cpp:489-498, 640-652; Common/Application/Application.cpp:833-840). -
A backup carries an account's own stores, and its restore puts them back. An archive written after the upgrade holds the account's own stores - nine when the second batch landed, twenty-one tables at batch 33 and twenty-four in 6.3.4, from the address books to the zero-access key, with every other per-account table listed beside them with the reason it is not carried (Common/Application/AccountStores.cpp
stores_andelsewhere_) - and every message's keywords, so it is larger by their size, and restoring it restores them. Restoring an archive written by 6.3.3 or earlier still leaves those stores empty, because it never held them (source: Common/Application/AccountStores.cpp:41-81; Common/BO/Message.cpp:315, 337). - The metric-history sampler also records
store_bytesandstore_messages, from an aggregate overhm_messagesrun at most once an hour (source: Common/Application/MetricsHistoryTask.cpp:133; Common/Util/Reports.cpp:39, 456). - The Control Deck at
/is redrawn on the webmail's design and follows the system's light or dark theme until one is chosen; its dark-only palette is gone (source: installation/WebAdmin/index.html).
From batches 12 to 33 (section 4e), also in 6.3.4:
-
Most saved settings take effect at once (batch 26). A save through the Control Panel, the Deck, COM or REST publishes: 211 of the 241
[Settings]keys apply when the save returns, 28 wait for a reinitialise and 2 for a restart, and those are listed as pending until they apply. A script that saved a setting on a live server and counted on nothing happening until the next start will now see it at once (source: build/ini-settings-baseline.txt, the fifth field; hmailserver/docs/SettingsApply.md). -
Compromised-account detection starts in watch mode (schema 6071): every authenticated submission adds to the account's baseline in
hm_accountactivityandhm_accountrecipients, and a submission in the shape of a takeover raisesaccount.compromised. Nothing is held or locked untilcompromise_detection_modeis set toenforce(source: DBScripts/Upgrade6070to6071MSSQL.sql;CompromiseDetectionMode1 in Common/Application/SettingsDefaults.h; hmailserver/docs/CompromisedAccounts.md). -
A destination that asks this server to slow down is given a pause (batch 24). A 421, or a 4xx carrying one of the providers' rate-limit codes or phrases, holds deliveries to that destination alone for
OutboundBackoffMinutes(2 by default, doubling up toOutboundBackoffMaxMinutes, 60); 0 turns it off. A deferral this server chose - the back-off, a connection ceiling, a per-minute rate - no longer spends a message's retries until the message has been queued for longer than its retry schedule spans (source:OutboundBackoffMinutes2 andOutboundBackoffMaxMinutes60 in SettingsDefaults.h; hmailserver/docs/OutboundShaping.md). -
A stop drains with the doors closed (batch 25). With
ShutdownDrainSecondsset, the listeners are closed and delivery paused before the wait, so a client that connects during it is refused at the socket rather than accepted and cut, and a delivery not yet started is not started (source: hmailserver/docs/Upgrading.md, "An upgrade without a window"). - The out-of-office memory survives a restart (schema 6060): who has been answered is a table, so a restart no longer sends every correspondent the reply again (source: DBScripts/Upgrade6059to6060MSSQL.sql; hmailserver/docs/OutOfOffice.md).
- Collected addresses are suggestions, not contacts (batch 12). An address a person wrote to is listed under Other contacts in the webmail until they keep it, and CardDAV serves only the kept ones - so a phone that synced collected addresses as contacts no longer receives them (source: hmailserver/docs/Contacts.md).
-
A relayed message no longer gains an empty last line (batch 32): the SMTP client wrote
CRLF.after every message whether or not it already ended with a line break, and now writes the line break only when the message lacks one (source: SMTP/SMTPClientConnection.cpp, commit0b6bae27f). -
Greylisting folds the sender's and the recipient's domain to lower case on every backend (schema 6072):
A@Example.comanda@example.comwere two triplets on PostgreSQL, and on MySQL with a binary collation, and are one everywhere now, for rows made after the upgrade (source: hmailserver/docs/Greylisting.md). -
In the container image, the environment wins (batch 28): every
HM_SETTING_<key>andHM_PROPERTY_<name>variable is stored at every start, and a setting the environment names is refused to the other interfaces until the container starts without it (source: hmailserver/docs/EnvironmentConfiguration.md).
From batches 34 to 39 (section 4e), also in 6.3.4:
-
MySQL and MariaDB tables become utf8mb4 (schema 6102). Every table is rebuilt under
utf8mb4_general_ci, InnoDB andROW_FORMAT=DYNAMIC, and so is the database's default; the step takes as long as copying the database, needs MySQL 5.7.9 or MariaDB 10.2.2 - as do 6041 and 6101 before it, each of which now opens with a probe, so on an older server the upgrade stops at 6041 with error 1071 and changes nothing - and the ALTER privilege on the database, and stops on error 1062 where two rows the old collation told apart are one to the new (on MariaDB 11.8,straßeandstrasein one address book; on MySQL 8.4, two names that differ only in an emoji) until one is removed. Afterwards atextcolumn may readmediumtext. SQL Server, SQL Server Compact and PostgreSQL only move the version (source: DBScripts/Upgrade6101to6102MySQL.sql; hmailserver/docs/Upgrading.md). -
The upgrade alters the message table (schema 6101): two columns on
hm_messages- the account an authenticated session submitted a message as, and the domains that passed DMARC and SPF when it arrived, recorded as properties of the message and never as headers; on a large store not instant on every backend (source: DBScripts/Upgrade6100to6101MSSQL.sql). -
The database connection is encrypted when the database server offers TLS. MySQL and MariaDB through MariaDB Connector/C - the client bundled on Windows and the one Ubuntu packages - had never used TLS; with nothing configured they now use it whenever the server offers it, without checking the certificate, and connect without it when it offers none. SQL Server through MSOLEDBSQL 18 and SQL Native Client encrypts the whole session, and tries once more without encryption when a failure reads as TLS; through SQLOLEDB, which the server picks when MSOLEDBSQL 18 or later is not installed and no failover partner is set, only the login is encrypted unless
TlsModeisrequireorverify-full. PostgreSQL is unchanged.[Database] TlsModeandTlsCaFilechoose otherwise (source: hmailserver/docs/DatabaseTls.md). -
The administrator password is hashed again at sign-in. The Windows installer wrote it as an unsalted MD5 digest nothing ever replaced; the first successful administrator sign-in - the Control Panel, COM, REST,
hmctlor the Control Deck - writes an MD5 or salted SHA-256 value, or a PBKDF2 hash with fewer iterations thanPasswordHashIterations, again as PBKDF2, and a browser session made under the old hash ends once. A file the service cannot write keeps the old value and says so once (HM7494). The installer now writes it as PBKDF2 itself (source: hmailserver/docs/SecretsAtRest.md). -
A COM change to a shared configuration object takes effect at
Save(): for routes and their addresses, incoming relays, DNS black lists, SURBL servers, remote domain policies, server messages, blocked attachments, SSL certificates and groups, a script that set a property and relied on it without saving must now save, and an object read before the save keeps what it read (source: commit15cfff25d; ARCHITECTURE.md; hmailserver/docs/Upgrading.md; The COM API and Scripting 23.3). -
Mail labelled ISO-8859-1 is decoded as windows-1252, and ISO-8859-2 to -9, -13 and -15 by their own code pages: a Western Windows server decodes Latin-1 as before, one with another ANSI code page now decodes it right, and on Linux Latin-1 is no longer replacement characters (source: commit
b53a2c6e5). -
A restore lists the archive before it extracts anything, and refuses an unsafe one whole: an entry with an absolute path, a
..segment or a link with somewhere to point is named in the backup log and asHM7540, and nothing is extracted; no backup this server writes has such an entry. The Windows installer's 7-Zip is 26.03 (source: commit9f414176f; hmailserver/docs/Backup.md). -
Event scripts: a rule's Run script function names a plain function. A name that is not ASCII letters, digits,
_and$, not starting with a digit, is refused asHM5018each time the rule fires, where it used to be written into the script's call as it was; and on a JScript server every value a handler is given - a password, a remote UID, an error text - arrives exactly as it was, backslashes included (source: commit5a3de9001; hmailserver/docs/EventScripts.md). -
IMAP answers as the RFCs say. A mailbox that does not exist, or a right the account lacks, is refused
NOrather thanBAD, and a failedSELECTorEXAMINEleaves no mailbox selected; an unknownLISTselection or return option isBAD; everyBODY[<section>]without.PEEKsets\Seen,BODY[HEADER]andBODY[1.MIME]included;SORTuses every criterion andREVERSEreverses only its own, andTHREAD ORDEREDSUBJECTandREFERENCESbuild the RFC's trees, so both can order messages differently; a session that is the first to be told of new messages counts them as\Recent; and anAPPENDstores the literal byte for byte, soRFC822.SIZEof a newly appended message is two octets smaller than the same message appended before (source: commit90df29ac5). -
GET /api/v1/me/searchgoes on from where it stopped. A search with more hits thanlimitansweredcomplete: true, more: trueand could not reach the rest; it now answerscomplete: falsewithnext_before_id, whichbefore_idcontinues from, and hits come newest first across every folder by message id, where they came folder by folder (source: hmailserver/docs/Performance.md). -
The update check reads the project's own feed. A 6.3.4 server reads
https://updates.progressiverobot.comwithUpdateFeedUrlempty, where 6.2.28 to 6.3.3 read GitHub's API, which answers 404; it trusts the GitHub signer only for a release no later than 6.3.3, and the project's GitLab CI pipeline for later ones (source: Common/Util/UpdateChecker.cppSTABLE_FEED; hmailserver/docs/UpdateFeed.md). -
A domain can no longer be named as a directory the server keeps its own files in (batch 40): a single-label name such as
protected,confidential,recoverable,discovery,quarantine,files,listsorblobs, ortemp,eventsanddatabase, is refused for a new domain and for a rename over COM and REST; a domain that already has one is still saved, and is named asHM7597at every start and asdomain_name_reservedon the first-hour checklist: rename it (source: hmailserver/docs/SetupChecklist.md). -
A one-time code is taken once (batch 40): over COM and REST, a TOTP code the server has accepted is refused if it comes again, for the administrator and for an account alike. A script that sent the administrator's password and a code with every REST request signs in once instead -
POST /api/v1/session, and thehmailsessioncookie from there - or uses an API key; otherwise only its first request in each half minute is accepted, and the others are refused with 401 and not counted as wrong codes (source: hmailserver/docs/AccountRecovery.md). -
The outbound relay sends its OAuth2 token over TLS only (batch 40): a route or the SMTP relay whose host is on
OutboundOAuth2Hostsand whose connection security is none - or STARTTLS optional against a server that does not offer it - now sends nothing in place ofAUTH XOAUTH2, fails the delivery with a temporary450 4.7.0, and the error log says to set TLS or STARTTLS required (HM5912). Exchange Online offers STARTTLS on 587, so a route set up as Microsoft documents it is not affected (source: CHANGELOG.md 6.3.4). -
The fetchers send a bearer token over TLS only, and log in with their own credentials where the server's token is withheld (schema 6104): a fetch account a person made in the webmail, or moved to another server or port there, is connected only to the public addresses its name resolves to at that moment (
HM5909when there are none), unlessreader_fetch_accounts_private_addressesis on, and one whose server or user name somebody without the server's authority named is never given the server's own OAuth2 token. Accounts from before 6104 are not marked and behave as they did (source: hmailserver/docs/Migration.md). - A password hash stored before 6.3.4 of a password with a character above U+00FF is refused (batch 40), to its own password too, and the application log names the account: that hash was made over NUL bytes (Windows) or question marks (Linux), and any other password of that form opened it. An administrator sets such a password again, and soon: until then SCRAM-SHA-256 still opens such a PBKDF2 hash to the bytes it was made over. Every ASCII and Latin-1 password signs in as before (source: hmailserver/docs/SecretsAtRest.md).
Earlier ones an upgrader from 6.2.10 also gets:
- 6.2.14: DKIM
t=yfailures are neutral; IMAP SASL credentials masked; settings relocated in the CP (source: v6.2.14 body) - 6.2.15: IMAP
*andn:msemantics per RFC 3501 (UID STORE *:*addresses one message) (source: v6.2.15 body) - 6.2.17: acceptance bounded at 240 s →
451 4.3.1(source: IniFileSettings.cpp:281) - 6.2.18: DB-unavailable recipient lookup →
451;DBConnectionAcquireTimeout=60; outbound sessions capped at 30 min; DNS/script/external-process timeouts (source: IniFileSettings.cpp:293-308) - 6.2.19:
[Settings]mirrored tohm_inisettings(file wins - inverted in 6.3.4, item 20); TLS key-exchange groups default to hybrid ML-KEM first; Sieveimap4flagsnow takes effect; account addresses with:refused (source: IniFileSettings.cpp:572; IniSettingStore.cpp; v6.2.19 body) - 6.2.20:
THREAD=…advertised unconditionally (source: IMAPCommandCapability.cpp:44-49) - 6.2.21: Sieve trees move/delete with renames/deletes; unimplemented Sieve constructs refused at upload; SORT with empty criteria rejected (source: v6.2.21 body)
- pre1: EHLO on every outbound delivery;
SIZE=declared; oversize refused locally; every literal{n+}accepted without continuation; kept-open logs flushed per line (source: SMTPClientConnection.cpp:588; pre1 body) - pre2: POP3
-ERR [AUTH]/[SYS/TEMP]prefixes;PIPELINING/IMPLEMENTATIONin CAPA; faster idle shutdown (source: POP3Connection.cpp:493-508; pre2 body) - pre3:
EXPIRE NEVERin CAPA; ES256 tokens verify once allow-listed; passwords chosen from now on must not contain the account name (source: POP3Connection.cpp:520; PasswordPolicy.cpp:170) - pre6: DMARC organizational domain by tree walk (
DmarcTreeWalkEnabled=1);np=honoured;rsa-sha1DKIM refused and re-signed asrsa-sha256; SPF void lookups capped at 2; ACME renewal at 2/3 lifetime + ARI; full mailbox refused at RCPT with452 4.2.2; quota warning at 90 %; DMARC report ids and<selector>(source: IniFileSettings.cpp:639-643; DKIM.cpp:974; AcmeClient.cpp:456-459) - alpha1: DSNs are
multipart/report; blocked-sender check is spam test number one (log positions shift);PreferredHashAlgorithm< 3 refused;DatabaseStatementTimeout=30 on PostgreSQL/MySQL;#Usersnamespace reachable whereverenableimapacl=1; every IMAPLISTruns one more query (source: alpha1 notes 5-12)
-
source/Migration(five VB6 wizards, three for dead products) — replaced bysource/Tools/ImportTool(6.2.12) (source:ls hmailserver/source; v6.2.12 body) - .NET Framework 4.5 installer gate (6.2.12); .NET Framework 4.8.1 targets for the tools (6.2.12); .NET 8 targets (6.2.19, → .NET 10) (source: v6.2.12 body; Roadmap.md:184)
- Windows 7/8 support: floor is Windows 10 1607 / Server 2016 (6.2.13) (source: section_setup.iss:28)
-
prisma.ymlworkflow (alpha1) (source: alpha1 body;.github/workflowslisting) - Committed
msado28-x64.tlh/.tli(alpha1) (source: Roadmap audit table) - GitHub-managed "Code quality" buildless scan (alpha1) (source: alpha1 body)
- DKIM
rsa-sha1signing/verification by default (pre6) (source: DKIM.cpp:974) - Fixed 30-day ACME renewal window (pre6) (source: AcmeClient.cpp:456-459)
- Pretty-printed-JSON-incompatible ACME challenge locator (pre1) (source: pre1 body)
- Per-version change history in README (6.2.17: "now lives on the Releases page") (source: v6.2.17 body)
- Releases
v6.2.22-pre4andpre5(withdrawn/superseded; tags remain) (source: pre6 body;git tag -l) - (Before scope, for context) WebAdmin and the Administrator were removed in 6.2.10 (source: v6.2.10 release title)
All read in IniFileSettings.cpp with the default shown; "CP" = a view file other than the generated index references the key; "README" = key present in README.md's annotated [Settings] block (checked 2026-09-04). Every key below is in the Ctrl+K index.
The table is closed. From schema 6042 - in 6.3.4 - the database is the settings store and the [Settings] section of hMailServer.ini is its copy, and no key is added to it: build/check-ini-coverage.py compares the keys IniFileSettings reads with build/ini-settings-baseline.txt (263 keys, 238 of them under [Settings], when the store landed; 267 at batch 33; 270 in 6.3.4 - 241 [Settings] keys, now counting the three PasswordHash* work factors, 20 [Database] with TlsMode and TlsCaFile, 5 [Directories], 3 [Security] with StoredSecretsKey, and 1 [GUILanguages]) and fails on a new [Settings] key, quoting the rule that a setting belongs in hm_settings; a new key under [Directories], [Database], [Security] or [GUILanguages], which are read before the database is open, must be added to the baseline with its reason. A new setting is a row in hm_settings, reached over REST and, for most, COM - in 6.3.4 191 rows (Settings Reference section 17), among them the nine of the audit trail and the alerts (AuditTrailEnabled, AuditRetentionDays, AlertsEnabled, AlertRecipient, AlertSenderAddress, AlertDigestEnabled, AlertDigestHour, AlertMaxPerHour, AlertWebhookMaxAttempts) and the seven of syslog (SyslogEnabled, SyslogHost, SyslogPort, SyslogTransport, SyslogFacility, SyslogMinimumSeverity, SyslogLogTypes), with their defaults in section 4e. Two sections of the file are new and hold no settings: [SettingsStore], which the server writes where it may write the file (Store=database and a ReadMe sentence), and [SettingsOverride], which only a person writes (section 5, item 20) (source: build/check-ini-coverage.py; build/ini-settings-baseline.txt; Common/Application/Constants.h:160-190; Common/Application/IniSettingStore.cpp:241-269).
One bootstrap key has been added since, and it is not a setting of the store: [Database] UpgradeOnStart (integer, default 0, applies at a restart; batch 25). With it at 1 the service brings an older database forward through the upgrade scripts as it starts, before it decides whether it can run, and a step that fails leaves HM5011 naming the step and the service not started. It is a file key because it has to be read before the database is at a version this build can open (source: build/ini-settings-baseline.txt; hmailserver/docs/Upgrading.md). Every [Settings] key also carries two marks in the baseline now: when a save applies it (now, reinitialize or restart; batch 26) and the deepest level it may be set at (server for all but QuotaWarningPercent, which is account; batch 27); and every one has an environment name, HM_SETTING_<key> (batch 28). The settings the batches after 6.3.3 added are rows of hm_settings, listed in Settings Reference section 17.
| Key | Default | First release | CP view | README | Source line |
|---|---|---|---|---|---|
FinalizationTimeout |
240 | 6.2.17 | FeatureSettingsView | no | :281 |
DBConnectionAcquireTimeout |
60 (0 in 6.2.17) | 6.2.17/18 | FeatureSettingsView | no | :306 |
ClientSessionCeiling |
1800 | 6.2.18 | FeatureSettingsView | no | :299 |
DNSQueryTimeout |
10 | 6.2.18 | FeatureSettingsView | no | :293 |
AutoBanFirewall |
0 | 6.3.2 | settings index | no | AutoBanFirewall.cpp |
AutoBanCommand |
(empty) | 6.3.2 | settings index | no | AutoBanFirewall.cpp |
AutoBanNeverBan |
(empty) | 6.3.2 | settings index | no | AutoBanFirewall.cpp |
GssapiEnabled |
0 | 6.3.2 | — | no | GssapiAcceptor.cpp |
GssapiServiceAccount |
(empty) | 6.3.2 | — | no | GssapiAcceptor.cpp |
GssapiServicePassword |
(empty) | 6.3.2 | — | no | GssapiAcceptor.cpp |
ScriptTimeout |
60 | 6.2.18 | FeatureSettingsView | no | :307 |
ExternalProcessTimeout |
300 | 6.2.18 | FeatureSettingsView | no | :308 |
TlsKeyExchangeGroups |
X25519MLKEM768:SecP256r1MLKEM768:X25519:secp384r1:secp256r1 |
6.2.19 | (not checked) | (not checked) | :572 |
OutboundOAuth2TokenUrl / ClientId / ClientSecret / FixedToken
|
"" | pre1 | ServerSettingsView | no | :210-216 |
OutboundOAuth2Scope |
https://outlook.office365.com/.default |
pre1 | ServerSettingsView | no | :214 |
OutboundOAuth2Hosts |
smtp.office365.com |
pre1 | ServerSettingsView | no | :215 |
FetchOAuth2Hosts |
outlook.office365.com |
pre1 | ServerSettingsView | no | :217 |
DmarcRptFromAddress |
"" (inert) | pre2 | FeatureSettingsView | no | :490 |
DmarcRptOrganizationName |
hMailServer |
pre2 | — | no | :491 |
AccountLockoutThreshold |
0 (off) | pre2 | ServerSettingsView | no | :558 |
AccountLockoutWindowMinutes / AccountLockoutMinutes
|
30 / 30 | pre2 | ServerSettingsView | no | :559-560 |
AVFailAction |
0 (deliver) | pre2 | ServerSettingsView | no | :537 |
AVFailRetryMinutes / AVFailMaxHolds
|
15 / 16 | pre2 | — | no | :544, :551 |
QuarantineEnabled |
0 | pre3 | ServerSettingsView, QuarantineView | no | :624 |
QuarantineRetentionDays |
30 | pre3 | — | no | :625 |
Pop3LoginDelaySeconds |
0 (off) | pre3 | ServerSettingsView | no | :619 |
PasswordPolicyMinimumLength / RequireMixedCase / RequireDigit / RequireNonAlphanumeric / RejectCommon
|
0 | pre3 | ServerSettingsView | no | :666-670 |
PasswordPolicyHistoryCount / PasswordPolicyMaximumAgeDays
|
0 / 0 | pre3 | ServerSettingsView | no | :671-672 |
MessageTraceEnabled / MessageTraceRetentionDays
|
0 / 30 | pre4→pre6 | ServerSettingsView, MessageTraceView | no | :626-627 |
DmarcTreeWalkEnabled |
1 | pre6 | ServerSettingsView | no | :639 |
SpfVoidLookupLimit |
2 | pre6 | ServerSettingsView | no | :640 |
RejectFullMailboxAtRcpt |
1 | pre6 | ServerSettingsView | no | :641 |
DkimAcceptSha1 |
0 | pre6 | ServerSettingsView | no | :642 |
QuotaWarningPercent |
90 | pre6 | ServerSettingsView | no | :643 |
ArchiveRetentionDays |
0 (never) | pre6 | ServerSettingsView | no | :644 |
MetricsPerDomainEnabled |
0 | pre6 | ServerSettingsView | no | :656 |
IndexerFullText (+ BatchSize 250, MinTokenLength 3, MaxTokensPerMessage 2048) |
0 | alpha1 | ServerSettingsView | yes | :365-390 |
MinimumFreeDiskSpaceMB |
100 | alpha1 | ServerSettingsView | yes | :417 |
DiskSpaceWarningThresholdMB |
1024 | alpha1 | ServerSettingsView | yes | :419 |
DatabaseStatementTimeout |
30 | alpha1 | ServerSettingsView | yes | :645 |
IMAPExpungeRetentionRecords |
5000 | alpha1 | ServerSettingsView | yes | :646 |
DmarcRptSchemaVersion |
1 | alpha1 | ServerSettingsView | yes (:440) | :506 |
FilterHookUrl |
"" (off) | alpha1 | ServerSettingsView | no | :658 |
FilterHookTimeoutSeconds / FailClosed / RejectScore / MaxMessageSizeKB
|
10 / 0 / 100 / 10240 | alpha1 | ServerSettingsView | no | :659-662 |
SMTPProxyProtocolEnabled / TrustedIPs
|
0 / "" | alpha1 | FeatureSettingsView | yes | :620-621 |
SMTPXClientEnabled / TrustedIPs
|
0 / "" | alpha1 | FeatureSettingsView | yes | :622-623 |
WindowsEventLogEnabled / Level
|
1 / 2 | alpha1 | FeatureSettingsView | yes | :628-629 |
OtelMetricsEndpoint / OtelLogsEndpoint
|
"" / "" | alpha1 | FeatureSettingsView | yes | :450-451 |
OtelMetricsInterval |
60 | alpha1 | FeatureSettingsView | yes | :452 |
OutboundPipelining / OutboundChunking
|
1 / 1 | 6.2.25 | (not checked) | no | :732-733 |
DeliveryHardLinks |
0 | 6.2.25 | (not checked) | no | :312 |
ArchiveDomains |
"" (all domains) | 6.2.25 | (not checked) | no | :317 |
SpamAssassinUser / SpamAssassinUserFromRecipient / SpamAssassinLearnOnMove
|
"" / 0 / 0 | 6.2.25 | (not checked) | no | :341-354 |
ScriptAllowedObjects |
* |
6.2.25 | (not checked) | no | :389 |
BackupVerifyRestore |
1 | 6.2.25 | (not checked) | no | :480 |
LogonTarpitSeconds / SmtpTarpitCount / SmtpTarpitDelaySeconds
|
0 / 0 / 0 | 6.2.25 | (not checked) | no | :675-679 |
SmtpAuthenticatedSenderCheck |
0 | 6.2.25 | (not checked) | no | :734 |
MetricsHistoryDays |
7 | 6.2.25 | (not checked) | no | :735 |
PasswordHashIterations / PasswordHashMemoryKB / PasswordHashTimeCost
|
bounded by ReadPasswordHashWorkFactor_ (10000-10000000 / 4096-1048576 / 1-20) |
6.2.25 | (not checked) | no | :233-235 |
OAuth2JwksUrl / OAuth2JwksCacheSeconds
|
"" / 3600 | 6.2.25 | (not checked) | no | :265-266 |
OAuth2IntrospectionUrl / ClientId / ClientSecret / CacheSeconds / FailOpen
|
"" / "" / "" / 300 / 0 | 6.2.25 | (not checked) | no | :271-275 |
IMAPCompressionEnabled |
1 | 6.2.28 | ServerSettingsView (Protocols > IMAP, :1111) | yes | :497 |
HttpProxy |
"" (direct) | 6.2.28 | FeatureSettingsView (Updates card, :1936) | yes | :492 in the release tree |
UpdateCheckEnabled / UpdateChannel / UpdateFeedUrl / UpdateCheckHours / UpdateTrustRootsFile / UpdateLogPublicKeyFile / UpdateSigningIdentity / UpdateSigningIssuer / UpdateSourceRepository / UpdateRequireAuthenticode / UpdateServiceWaitSeconds / UpdateWindow / UpdateAutoDownload / UpdateBackupBeforeApply
|
0 / stable / "" / 24 / "" / "" / "" / "" / "" / 0 / 180 / "" / 0 / 1 | 6.2.28 | FeatureSettingsView (Updates card, :1932) | yes | :489-509 |
Rows from 6.2.25 on were added 8 Sep 2026; their "CP view" and Ctrl+K-index presence were not re-checked except where a line is cited. Also new outside [Settings] in 6.2.25: [Database] AllowUnencryptedConnection=0, PostgreSQLSslMode="", PostgreSQLSslRootCert="", ConnectionStringOptions="" (:162-165); [Security] AdministratorTotpSecret (:112).
Pre-existing keys that only became findable in this range (6.2.14/6.2.15): LogDeleteDays, LogLevel, MaxLogLineLen, SepSvcLogs, JsonLogging, QuickRetries, QuickRetriesMinutes, QueueRandomnessMinutes, MaxOutboundPerDestinationPerMinute, SAMinTimeout/SAMaxTimeout, ClamMinTimeout/ClamMaxTimeout, IndexerFullMinutes/IndexerFullLimit/IndexerQuickLimit, UseDNSCache, DNSServer, BackupMessagesDBOnly, ArchiveDir, DisableAUTHList, PreferredHashAlgorithm, MinimumAcceptedHashAlgorithm, PasswordPepper, OAuth2*, WebServices*, Autoconfig*, MtaSts* (source: v6.2.14, v6.2.15 bodies; all present in IniFileSettings.cpp)
DB-stored (hm_settings) defaults relevant to "inert by default": enableimapacl=1, enableimapsort=1, EnableImapSASLPlain=0, MessageIndexing=0, ASArcFilteringEnabled=0 (source: DBScripts/CreateTablesMSSQL.sql:914, 918, 995, 1013, 1037)
- 6.2.18 "Correction (12 Aug 2026)": ARC sealing was narrower than described (only author-domain-signed mail); fifteen other audit defects listed in the Roadmap (source: v6.2.18 body)
- 6.2.21: the bare-LF work described in 6.2.18 was real but was not the defect behind GitHub discussion 18 (Postfix normalises bare LF) (source: v6.2.21 body)
- 6.2.21: six Roadmap rows had said unimplemented Sieve constructs were silently accepted; they are refused at upload (source: v6.2.21 body)
- pre6: Roadmap row said THREAD was not implemented; it was (shipped 6.2.20); per-domain-metrics row overclaimed that every counter was global (source: pre6 body)
- alpha1:
DiagnosingStalledMail.mdtold admins the custom-DNS setting was broken; not true since 6.2.19 (source: alpha1 body) - alpha1 note 14: fourteen new settings absent from README, nine with no GUI — the nine gained README entries and CP rows on 22 Aug 2026 (source: Roadmap.md:988; grep of README.md and CP views). The five
FilterHook*keys are still absent from README (source:grep -c FilterHook README.md= 0). - Roadmap prose paragraphs are hand-counted and had drifted twice;
check-roadmap.ps1reconciles tables, not prose (source: Roadmap.md:96-116) - Release titles: 6.2.24 says "TOTP for the Control Panel" while pre3 and the code say per-account TOTP is enforced by the server (source: v6.2.24 body vs PasswordValidator.cpp:189)
-
README settings coverage: since 6.2.25 README's configuration reference says it "lists the keys an operator is most likely to need and is not the whole file" and points at the wiki Settings Reference for the other ~90 keys (README.md:379-384), so the
FilterHook*, 6.2.22-pre-line and timeout keys being absent from README is by design, not a gap (grep -c FilterHook README.mdis still 0). The 6.2.24 body's "full list ... in README.md" was inaccurate at the time (18 of 23). -
pre1 schema range: pre1 says "6012 → 6014"; the previous stable (6.2.21) was 6011 (source: pre1 vs v6.2.24 bodies;
Upgrade6011to6012*.sqlexists). - THREAD provenance: pre6 says "implemented all along"; the 6.2.20 body says it was added in 6.2.20 (source: both bodies). Both agree it is shipped and unconditional.
-
Post-quantum, LDAP auth, .NET 10, API keys, spam overview, DANE-MX fix, LOGINDISABLED, POP3 conformance: shipped in 6.2.19 by commit/tag date but absent from the 6.2.19 release body (source:
git log2026-08-12..15 vs v6.2.19 body). - 6.2.18 schema: body is silent; 6.2.19 says the move is "from 6005", so 6.2.18 was still 6005.
-
Ctrl+K index size: 6.2.14 says 227 settings indexed; the generated index has 393 entries in 6.3.3, 406 at 3233b0cc4, 447 at batch 33 and 448 in 6.3.4 (source:
new SettingEntry(counted in SettingsSearchIndex.g.cs at v6.3.3, 3233b0cc4 and 540347500). -
IMAP SCRAM: Roadmap "ahead" list cites SCRAM-SHA-256-PLUS; over IMAP it is unreachable on a stock install because
EnableImapSASLPlain=0 gatesAUTHENTICATE(source: alpha1 limitation 22; CreateTablesMSSQL.sql:1037). -
Alpha1's
.NET 10vs 6.2.12's.NET 8: both true at their dates; the switch happened 12 Aug 2026 in the 6.2.19 window (source: commit 5409ae8d5).
- Windows Event Log curated ids
2010–2015and catch-all2000–2003; throttle "five per id per ten minutes" (only the source name, throttle function and bucket were found: WindowsEventLog.cpp:98, 255-263) - (Located since and stated in their sections on 8 Sep 2026: DKIM RSA keys under 1024 bits — DKIM.cpp:268; the IMAP 11 MB pre-authentication buffer — IMAPConnection.cpp:238; REST API keys — RestApiServer.cpp:158,2416; the Apple
.mobileconfigendpoint — WebServicesServer.cpp:653. Whether the profile first shipped in 6.2.18 or 6.2.19 is still not settled.) - ACME http-01 locator fix (GitHub issue 34) location in
AcmeClient.cpp - Exact DBUpdater step counts ("76 for MySQL/MSSQL, 49 for PostgreSQL/SQL CE") (alpha1 body)
-
OnClientLogonfiring from every IMAP AUTHENTICATE mechanism (6.2.13) -
TlsKeyExchangeGroupsCP/README presence (not checked) - Regression-gate counts for 6.2.17, 6.2.19, 6.2.21 (bodies say "validated by the full regression suite" without a number)
- The 6.2.25 claim that the four HTTPS clients require TLS 1.2 or later (release body only; not located by grep in HttpsClient.cpp)
hMailServer 6.3.7, the latest release (17 October 2026, schema 6108) · AGPL-3.0-or-later · Repository · Mirror on GitHub · Report a documentation error (a GitLab issue naming the page)
Home — full index
Start here
1. Install and run
- Before You Install
- Installing hMailServer
- Installing on Linux
- Running in a Container
- Profiles and Environment
- The Control Panel
- The Control Deck
- Your First Domain and Mailbox
- Connecting a Mail Client
- The Webmail
- DNS for Your Domain
- Migration
2. Secure it
- Encryption and Certificates
- Database TLS
- Encryption for People
- Stopping Spam
- Stopping Viruses
- Security Hardening
- Secure Defaults
- The Service Account
- Secrets at Rest
- Sign-in and Identity
- Security Policy
3. Operate it
- Monitoring and Health
- The SNMP Agent
- Backup and Restore
- Message Storage
- Compliance and eDiscovery
- Troubleshooting
- Diagnosing Stalled Mail
- Relocating an Installation
- Upgrading hMailServer
- Upgrading Guide
- The Update Feed
- Migrating the Database Backend
- High Availability Runbook
- Warm Standby
- Runbooks Digest
4. Extend it
- Rules and Sieve
- Aliases Lists and Public Folders
- Rooms and Equipment
- Mailing Lists
- Routes and Relays
- Mail In to a Webhook
- Outbound Shaping
- The COM API and Scripting
- Event Scripts
- The REST API
- Command Line
- APIs Reference
5. Contribute to it
- Project Handbook
- Architecture
- Contributing
- Release Process
- Governance
- Assurance Case
- Regression Test Environment
- Fuzzing
- Regulatory Scope
- Third-Party Binaries
Look it up — from any journey
- Settings Reference
- Settings Overview
- Capabilities and Configuration
- Control Panel Reference
- Control Panel Pages
- Ports Reference
- Changes-Since-6210
- Version-History-to-6210
- Glossary
About this wiki
- hMailServer 6.3.7 is the latest release (17 October 2026, schema 6108)
- Pages say since 6.3.4 where a feature is new in it
- AGPL-3.0-or-later
- Repository, the one place the project is worked on
- Its mirror on GitHub, since 18 October 2026, with no Issues
- Questions: a GitLab issue with the Question template, GitHub Discussions Q&A, or the forum
- Report a documentation error: a GitLab issue naming the page