hMailServer by Progressive Robot

Latest release · 6.3.3

Download hMailServer 6.3.3

The free, open-source mail server for Windows and Linux, maintained by Progressive Robot. Pick a version, pick your platform. Every package is signed and checksummed, and each release ships with a software bill of materials and build provenance.

  • 6.3.3Sigstore-signed 15 September 2026
  • Windows + Linuxx86-64 and AArch64
  • AGPL-3.0free & open source
hMailServer 6.3.3

Latest release · signed 15 September 2026

Latest release

hMailServer 6.3.3

Pick your platform. Each package shows its size and a shortened SHA-256; the full checksums and signature bundles are in the verification table below.

Windows

Windows 10 (version 1607 or later) and Windows 11, or Windows Server 2016 and newer; 64-bit only. Run the installer as an administrator: it upgrades any existing 5.x or 6.x installation in place. The installer is Authenticode-signed by Progressive Robot Ltd.

  • hMailServer-6.3.3-x64.exex64 · 76.2 MB
    Download
    SHA-2563188be2fa16a…9610a057 Verified against release notesSigstore bundle: .cosign.bundle / .sigstore.json (same bundle, two names)

Debian and Ubuntu

A .deb for Debian, Ubuntu and their derivatives on x86-64 and AArch64, built on Ubuntu 24.04 with Boost linked statically, so it installs on later releases too. It sets up the service, its systemd unit and the configuration under /etc/hmailserver.

  • hmailserver_6.3.3_amd64.debx86-64 · 4.3 MB
    Download
    SHA-2568fc110d1dd06…3c26e4b2 Verified against SHA256SUMSSigstore bundle: .cosign.bundle / .sigstore.json (same bundle, two names)
  • hmailserver_6.3.3_arm64.debAArch64 · 4.2 MB
    Download
    SHA-256f435491e7b90…6714f6fb Verified against SHA256SUMSSigstore bundle: .cosign.bundle / .sigstore.json (same bundle, two names)

Installsudo apt install ./hmailserver_6.3.3_amd64.debthen the three first-run commands

Fedora, RHEL and openSUSE

An .rpm for Fedora, Red Hat Enterprise Linux, RHEL-compatible distributions and openSUSE on x86-64 and AArch64. dnf resolves its dependencies from your own repositories; Boost is linked statically from 6.3.2.

  • hmailserver-6.3.3-1.x86_64.rpmx86-64 · 4.4 MB
    Download
    SHA-2566209a8ff6e32…86ea1c25 Verified against SHA256SUMSSigstore bundle: .cosign.bundle / .sigstore.json (same bundle, two names)
  • hmailserver-6.3.3-1.aarch64.rpmAArch64 · 4.3 MB
    Download
    SHA-25661a10e5d9481…e3de269b Verified against SHA256SUMSSigstore bundle: .cosign.bundle / .sigstore.json (same bundle, two names)

Installsudo dnf install ./hmailserver-6.3.3-1.x86_64.rpmthen the three first-run commands

AppImage (try it out)

For seeing hMailServer run on a laptop, not for running mail: it runs as whoever starts it, with no service user, no systemd unit and no hardening, and it cannot bind port 25. Install the .deb or the .rpm to run mail.

  • hMailServer-6.3.3-x86_64.AppImagex86-64 · 9.8 MB
    Download
    SHA-256b65ac41c9393…5a608907 Verified against SHA256SUMSSigstore bundle: .cosign.bundle / .sigstore.json (same bundle, two names)
  • hMailServer-6.3.3-aarch64.AppImageAArch64 · 9.5 MB
    Download
    SHA-2565b17694df13b…42c6aa00 Verified against SHA256SUMSSigstore bundle: .cosign.bundle / .sigstore.json (same bundle, two names)

Installchmod +x hMailServer-6.3.3-x86_64.AppImage

What’s new in 6.3.3

A message larger than 60,000 bytes sent with CHUNKING to Gmail, iCloud, Outlook.com or Postfix stalled after its first buffer and the remote timed out - every such message since outbound BDAT arrived - and does not now; the webmail was rebuilt to the shape of Gmail and Outlook.com and gained tabs, mute, pop-out windows, search history and fourteen more search operators; the account's address book is served over CardDAV to phones and desktop clients; the browser Control Deck is measured against the desktop Control Panel and moved a long way toward it; the Linux regression suite's route backlog is largely lifted; and a live update started from an open Control Panel no longer fails on its own files.

Before you upgrade

Read this first if you are upgrading from anything older than 6.3.1 (issue #263). 6.3.2 could not upgrade a database whose schema was older than its own: the guard added in 6.3.2, which makes a database the server has refused answer COM with the refusal instead of crashing, covered the whole Settings object, and DBUpdater reads the script directory from it before it runs the 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. 6.3.3 upgrades such a database in one go; nothing in the database was changed by the failed attempt. The settings object answers its directories on a refused database again, everything that lives 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 DBUpdater's path over COM against a database the server has refused.

Before you upgrade

Read this too if you run 6.3.1 or 6.3.2 and use the Control Panel's live update. The first real update, 6.3.1 to 6.3.2, failed with the installer's exit code 5 because the Control Panel that started it kept its own files open. That is fixed here - but the helper that runs a live update is the one already installed, so an installation on 6.3.1 or 6.3.2 has to be updated by hand once: close the Control Panel, run this installer. Live updates work from then on.

Full release notes for 6.3.3 10 sections

Delivery

An outbound BDAT chunk larger than one send buffer no longer stalls (issue #261). The connection sends a message 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 then queued behind that read in the connection's operation queue, which starts only the operation at its head. The read could not complete while the remote waited for the bytes behind it, so nothing sent them, and the remote gave up after its own timeout (iCloud's 421 after five minutes, in the report). Every message over 60,000 bytes to a server advertising CHUNKING has stalled this way since outbound BDAT arrived in 6.2.28; a message that fit one buffer went, which is why short ones did; DATA never met it, because its body streams only after the 354. The operation queue runs once more after a read has started, so a write behind it goes at once. Two regression tests relay a 222 KB message over BDAT, with the envelope pipelined and without. Until you upgrade, OutboundChunking=0 in hMailServer.ini is the workaround.

The full-text indexer no longer reports an error for a message deleted before its terms were saved. On PostgreSQL the foreign key refuses the terms of a row that is gone (SQL Server Compact accepted them); the message is logged at debug level and skipped, since there is nothing left to index.

The webmail

Rebuilt to the shape of Gmail and Outlook.com. The page is real files now (Portal.html, Portal.js), embedded into the binary at build time. A top bar with one search box and its options panel; a navigation column with the system folders by their designation, then the account's folders, labels, Held mail, Contacts, Scheduled and the storage meter; a list whose row is a checkbox, a star, an avatar, the sender's name, the subject and the first line of the text, an attachment mark and a time, with archive, delete and mark-read under the pointer; a reading pane on the right, below, or off; a toast with Undo after every move; the keys a mail client has (c, r, a, f, s, o, u, /); settings behind the gear; the same page on a phone; light and dark.

Writing. The compose window docks at the bottom right, minimises to its bar or opens full; a reply, reply to all or forward is written under the message it answers, with the earlier messages of the conversation above it as cards. An attachment reminder asks once before a send whose text speaks of an attachment and carries none. A message, or the message being written, pops out into a window of its own.

The list. A row dragged onto a folder, with undo. A right-click menu on a row (open, archive, junk, delete, read, star, pin, block, sweep). A pin keeps a message at the top of its list (the $Pinned keyword, the way every mail program sees it). Block this sender writes a rule that files the sender into Junk; Delete all from this sender sweeps the folder. Shift-click selects a range and ctrl-click one row, as every desktop client has them. A selection can take in every message of the folder, or of the open tab.

Mute, and the inbox in tabs. A muted conversation leaves the inbox, its messages carry $Muted, and a rule the page writes files whatever answers any of them into the archive; Undo takes all of it back. The inbox comes in tabs - Primary, Social, Promotions, Updates and Forums the way Gmail sorts them, or Focused and Other the way Outlook does, or one list - the server saying which from the headers alone (the sender's domain, List-Post, List-Unsubscribe, Precedence, Auto-Submitted), the reader overruling it per sender from the row's menu.

Search. The last ten searches are offered under the empty box (kept in this browser, forgotten at sign-out), and a contact's name typed there completes to from:<address> from the address book. Fourteen more operators, parsed by the server beside the ones it had: cc:, bcc:, filename:, larger:, smaller:, older_than:, newer_than:, is:muted, is:pinned, category:, has:link, in_reply_to:, a -word to exclude, and OR. The reference is hmailserver/docs/WebmailShortcuts.md.

Follow-up, nudges, quick steps, clean-up. A flag can carry a due date - today, tomorrow, next week or a date - kept as $FollowUp and a $Due-YYYY-MM-DD keyword on the message itself, so other clients still see a flag and a move keeps the date; the row shows it, the Starred view sorts by it, and a reminder comes once a day. Nudges say "Received N days ago. Reply?" on a message that asked and "Sent N days ago. Follow up?" on one nothing answered; a preference turns them off. Quick steps are buttons a reader defines - move, mark read, label, forward, in one press, on the digit keys. Clean up conversation deletes the messages whose every line a later message quotes, keeping the newest, the unread, the starred and those with attachments, with Undo.

Everything above is in the twenty languages the page speaks, and the harness that executes the page's script runs 224 checks on it in CI.

CardDAV

The account's address book, on phones and desktop clients (RFC 6352). On the web services listener: /.well-known/carddav redirects to /dav/, a PROPFIND there answers the principal, the principal the address-book home, and the home holds one book, Contacts - the address book the webmail keeps and completes recipients from. The card a client sends is the card it reads back: the card itself is stored beside the contact (schema 6040), vCard 3.0 or 4.0, both advertised, and an edit made in the webmail is written into the stored card with the UID and the telephone number the phone gave it left alone. HTTP Basic as the account or an application password, over HTTPS only, with the IMAP logon's lockout and auto-ban; addressbook-query, addressbook-multiget and sync-collection reports; ETags with If-Match and If-None-Match. iOS, macOS Contacts, DAVx5 and Thunderbird speak it. The web services HTTPS listener has to be on (WebServicesHttpsPort).

The Linux control panel

Measured. build/check-deck-parity.py counts every field the desktop Control Panel writes against the REST API and the browser Control Deck, from the sources; hmailserver/docs/DeckParity.md is its report. Of 330 properties, 240 were writable over REST and 153 reachable from a Deck view at the start of 14 September; 328 and 322 by its end, the two not writable being groups and their members.

Moved. New write routes: the anti-virus settings as a group (/api/v1/settings/antivirus), sixteen more account fields and nine more domain fields on their PUTs, the distribution list's settings and a PUT for it, DNS blacklists, SURBL servers, white-list addresses, blocked senders, incoming relays, blocked attachments and the greylisting white list as resources, the cache group's eight ceilings and lives, an IP range's expiry, abort_spam_flagged on a rule's forward and reply, user_interface_language, and - under an account's address, for the administrator - its application passwords, its folders' permissions and its messages. The Deck gained a harness of its own (build/check-deck-script.py, 292 checks, in CI), full domain editing, an IP-ranges view with expiry, fetch-account and backup views, the account editor in full, distribution lists and aliases under their domain, the seven small collections, server messages, and the scripting, cache and indexing groups with their verbs.

The Linux regression suite

The route backlog, waves A to H. 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. Then, in the day's last batch, the three families that stood behind most of what was left: application passwords administered under /api/v1/accounts/{address}/app-passwords, folder permissions under .../folders/{id}/permissions with the rights by the names the COM interface gives them and the letters SETACL takes, and the message object - a folder's whole live collection, a message read as a row with every header, a raw .eml added on APPEND's path, the flags changed on STORE's, a delete on EXPUNGE's - with the shims rewritten so that a fixture skips only against an older server whose OpenAPI lacks the route. The hosted Linux run on this release's tree: 1520 tests, 1289 passed, 0 failed, 231 skipped. Three server defects found on the way: a vacation save PostgreSQL refused, ncsa refused 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 - the column was 255 characters and a DPAPI envelope is 314. Schema 6039 widens it.

The Control Panel

The live update from an open Control Panel. The helper 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. See the note at the top.

Two defects the tidy branch's own gates found. generate-com-wrapper.ps1 died 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::MoveNext catches it now (HM5037) and the exception handler names COM errors. The fetch-account editor's connection-security captions no longer carry POP3's port numbers into an IMAP account.

Security, from the release's own review

An adversarial review of everything the server gained since 6.3.2 ran before this release was cut, and what it confirmed is fixed here. The [Settings] section of hMailServer.ini over REST answered to any api key: a read-only key could read every secret the section holds (the OAuth2 HMAC secret, the password pepper, the service account's password), and a write key could set AutoBanCommand, which the firewall reconciliation runs as the service. The four ini routes answer to the administrator password only now, as the api-key routes do; a key of any scope gets the same 401. A key restricted to a domain could lift that domain's limits through PUT /api/v1/domains/{domain} - the eleven fields the COM interface gates on the server administrator - and is refused them with 403 now. In CardDAV, a REPORT body of a million ampersands cost a worker thread tens of seconds (the entity scan is bounded), a multiget answered the same href as often as it was asked, five thousand times at most (a repeated href is answered once, and a multistatus over 64 MiB is refused), and on MySQL the card column could not hold the megabyte the collection advertises (it can). Smaller: a refused save on a collection update no longer leaves the in-memory item changed; one restart thread at a time; the escaper's PostgreSQL flag is re-read per statement; the packaged fail2ban filter matches a logon failure whose name carries a double quote; an XML name may not carry an ampersand, a semicolon or a control byte. Two tests hold the two key-scope findings.

Build and supply chain

One header on every source file, the project's, in one form, put there and checked by build/add-license-headers.py.

The container image job of a tag run asks docker for the image by a lower-case name. At 6.3.2 the image was pushed and the smoke test that starts it was refused the capital letter in the owner's name; the image itself was fine.

The Linux suite's shims learned the anti-virus group and the nine domain properties the new fixtures read.

Database

On PostgreSQL, a backslash is stored as one backslash. Every value with a backslash in it - a Windows path in a DKIM key-file setting, a backslash in a signature, a vacation message or a rule - was stored with the backslash doubled, because the escaper doubled it for PostgreSQL unconditionally, which is right only while standard_conforming_strings is off, and it has been on by default since PostgreSQL 9.1. The connection now reads the server's own answer when it comes up and doubles only where the server would otherwise read the backslash as an escape. Found by the hosted Linux run reading a domain's key-file path back as C:\\keys\\next.pem. Values already stored doubled stay as they are; edit and save them once.

Schema 6039: a domain's relay-password column widened for a DPAPI envelope. Schema 6040: the CardDAV tables. Both upgrade in place.

Verification

The full regression suite ran on the stamped binary: 2,302 tests, 2,294 passed, 0 failed, 8 skipped. hMailServer.exe 6.3.3.42, SHA-256 2c57f78050ecc43ee6d69ac3a2dc42b1dd802cc35ec369c09457d8119d53510a.

The installer has two hashes, and both are given deliberately. hMailServer-6.3.3-x64.exe as built, 79,926,537 bytes: SHA-256 45a9d7f1a78bb31031dde27849fa21828a60aff178f32cbc2b0d9cd93f6b33f2. As published, after Authenticode signing: SHA-256 3188BE2FA16A4C43D3B43DDBD7274E4CFE878C082343EF25FFBD00489610A057.

The assertion build, first. A separate build with HM_ASSERT on - every internal invariant checked and any failure fatal - ran the whole suite before the shipping binary was built: 2,302 tests, 2,294 passed, 0 failed, 8 skipped. Nothing asserted.

Reproducible. Three clean Release builds of the stamped tree - the first two compared for the purpose, the third the one that ships - produced the same hMailServer.exe.

On the way here. Every batch that landed on master went through the full Windows suite on its exact tree before it was pushed, and the hosted Linux run on the last of them: the numbers are in the wiki's "Changes since 6.2.10" page, section 4d. The hosted Linux run on the release tree itself: 1520 tests, 1289 passed, 0 failed, 231 skipped (run 34904809203).

Release facts

Version
6.3.3
Signed
15 September 2026Sigstore transparency log
Packages
7Windows and Linux, x86-64 and AArch64
Database schema
6040upgrades in place
Regression suite
2,302 tests2,294 passed · 0 failed · 8 skipped
Windows build
6.3.3.42reproducible
Installer SHA-256
3188be2fa16a…9610a057hMailServer-6.3.3-x64.exe

Before you install

Verify what you downloaded

Compare the SHA-256 your machine computes with the one in the table, or verify the Sigstore bundle that rides beside every installer, package, checksum list and SBOM. Either proves the download is byte-for-byte what the release workflow produced.

Windows

PowerShell prints the hash. The installer’s Digital Signatures tab (right-click → Properties) names Progressive Robot Ltd from 6.3.1.

Get-FileHash .\hMailServer-6.3.3-x64.exe -Algorithm SHA256

Linux

Check every package you downloaded against the signed checksum list in one go.

sha256sum -c hmailserver-linux-6.3.3-SHA256SUMS.txt --ignore-missing

Any file, with cosign

Each file has a .cosign.bundle beside it. The identity below is the release workflow that signed it; verification does not need the repository to be online.

cosign verify-blob --bundle hMailServer-6.3.3-x64.exe.cosign.bundle \
  --certificate-identity-regexp '^https://github\.com/Progressiverobot/hmailserver/' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  hMailServer-6.3.3-x64.exe

Every file in 6.3.3

Full SHA-256 of each file as recomputed on this server, with the signature bundles beside it. The SBOMs and provenance are not needed to install; they let anyone audit exactly what was built.

FileWhat it isSizeSHA-256Signature
hMailServer-6.3.3-x64.exeWindows installer · x6476.2 MB3188be2fa16a4c43d3b43ddbd7274e4cfe878c082343ef25ffbd00489610a057 release notesSigstore bundle: .cosign.bundle / .sigstore.json (same bundle, two names)
hmailserver_6.3.3_amd64.debDebian package · x86-644.3 MB8fc110d1dd0669868a5cdabad14bdc1e7c50df141210c883453dbe5b3c26e4b2 SHA256SUMSSigstore bundle: .cosign.bundle / .sigstore.json (same bundle, two names)
hmailserver_6.3.3_arm64.debDebian package · AArch644.2 MBf435491e7b9036c23ac7a39d1431bbfc391215087b78ae855dc524146714f6fb SHA256SUMSSigstore bundle: .cosign.bundle / .sigstore.json (same bundle, two names)
hmailserver-6.3.3-1.x86_64.rpmRPM package · x86-644.4 MB6209a8ff6e3212d87a71b1b815f466841eb91005f50e5788f7244e0986ea1c25 SHA256SUMSSigstore bundle: .cosign.bundle / .sigstore.json (same bundle, two names)
hmailserver-6.3.3-1.aarch64.rpmRPM package · AArch644.3 MB61a10e5d94811cb9326e85579ba0bcc796c82d2a5f7b1ab683281a70e3de269b SHA256SUMSSigstore bundle: .cosign.bundle / .sigstore.json (same bundle, two names)
hMailServer-6.3.3-x86_64.AppImageAppImage · x86-649.8 MBb65ac41c9393922fbb00b676313a3c55cfb7256aeebc23904701148c5a608907 SHA256SUMSSigstore bundle: .cosign.bundle / .sigstore.json (same bundle, two names)
hMailServer-6.3.3-aarch64.AppImageAppImage · AArch649.5 MB5b17694df13b0c68cb147fb68210ceccae050e51cb93961221d572fb42c6aa00 SHA256SUMSSigstore bundle: .cosign.bundle / .sigstore.json (same bundle, two names)
hmailserver-linux-6.3.3-SHA256SUMS.txtSHA-256 checksumsOne line per Linux package; feed it to sha256sum -c.584 bytes020c3a28ccf333ab98b680e296781647edcbb8d96c36d0bffcc089375204b904Sigstore bundle: .cosign.bundle / .sigstore.json (same bundle, two names)
hmailserver.spdx.jsonSBOM (SPDX)Every component in the build, in SPDX JSON.279 KBfb37a474bc445f85f78000836ed3e325c4369006d6a60bdc9adf73cfd46764deSigstore bundle: .cosign.bundle / .sigstore.json (same bundle, two names)
hmailserver.cyclonedx.jsonSBOM (CycloneDX)The same inventory in CycloneDX JSON.176 KBf4ad84f2db639d71682776a74d5df15244bce27bba8834e2b6c3e3b745327ac7Sigstore bundle: .cosign.bundle / .sigstore.json (same bundle, two names)
hmailserver-v6.3.3.intoto.jsonlSLSA build provenancein-toto attestation of how and where the release was built.24 KB039d5b3d9ac4d4c658ce9bf659283ca94214d33d28a06f5c82ca4d98af29833d
Progressiverobot-hmailserver-attestation-47493826.sigstore.jsonBuild attestationGitHub artifact attestation for the release, as a Sigstore bundle.8 KB37f7204ebcf54239283dce575d5e3dfd260acdc175bd80ba39ca9e64db4cd77a

Release history

Every release, kept

14 releases and 6 pre-releases, 12 August – 15 September 2026

A release is never altered once published, so a rollback or a like-for-like rebuild is always possible. Every release keeps its installer, packages, checksums, signatures and notes; open one to see them.

6.3 series

4 releases · 10–15 September 2026 · Windows and Linux
  1. 6.3.3

    LatestSigned 15 September 20267 packages · schema 6040

    A message larger than 60,000 bytes sent with CHUNKING to Gmail, iCloud, Outlook.com or Postfix stalled after its first buffer and the remote timed out…

    Packages, checksums and notes in full above
  2. 6.3.2

    Signed 13 September 20266 packages · schema 6038

    The webmail became a client people would choose, Kerberos single sign-on arrived on all three protocols, an auto-ban now reaches the operating system's firewall…

    Release notes, checksums and signatures
    Windows

    Windows 10 (version 1607 or later) and Windows 11, or Windows Server 2016 and newer; 64-bit only. Run the installer as an administrator: it upgrades any existing 5.x or 6.x installation in place. The installer is Authenticode-signed by Progressive Robot Ltd.

    • hMailServer-6.3.2-x64.exex64 · 76.0 MB
      Download
      SHA-256d10eb643c7d4…f2140de9 Verified against release notesSigstore bundle: .cosign.bundle / .sigstore.json (same bundle, two names)
    Debian and Ubuntu

    A .deb for Debian, Ubuntu and their derivatives on x86-64 and AArch64, built on Ubuntu 24.04 with Boost linked statically, so it installs on later releases too. It sets up the service, its systemd unit and the configuration under /etc/hmailserver.

    • hmailserver_6.3.2_amd64.debx86-64 · 4.0 MB
      Download
      SHA-2562a937e48df11…fffc2730 Verified against SHA256SUMSSigstore bundle: .cosign.bundle / .sigstore.json (same bundle, two names)
    • hmailserver_6.3.2_arm64.debAArch64 · 3.9 MB
      Download
      SHA-2567705dd264474…8b722878 Verified against SHA256SUMSSigstore bundle: .cosign.bundle / .sigstore.json (same bundle, two names)

    Installsudo apt install ./hmailserver_6.3.2_amd64.debthen the three first-run commands

    Fedora, RHEL and openSUSE

    An .rpm for Fedora, Red Hat Enterprise Linux, RHEL-compatible distributions and openSUSE on x86-64 and AArch64. dnf resolves its dependencies from your own repositories; Boost is linked statically from 6.3.2.

    • hmailserver-6.3.2-1.x86_64.rpmx86-64 · 4.0 MB
      Download
      SHA-256ffa659a11735…5ce0cb80 Verified against SHA256SUMSSigstore bundle: .cosign.bundle / .sigstore.json (same bundle, two names)
    • hmailserver-6.3.2-1.aarch64.rpmAArch64 · 3.9 MB
      Download
      SHA-256b151032d434a…963d341d Verified against SHA256SUMSSigstore bundle: .cosign.bundle / .sigstore.json (same bundle, two names)

    Installsudo dnf install ./hmailserver-6.3.2-1.x86_64.rpmthen the three first-run commands

    AppImage (try it out)

    For seeing hMailServer run on a laptop, not for running mail: it runs as whoever starts it, with no service user, no systemd unit and no hardening, and it cannot bind port 25. Install the .deb or the .rpm to run mail.

    • hMailServer-6.3.2-x86_64.AppImagex86-64 · 9.5 MB
      Download
      SHA-2560354e8ae2dd0…b10ef3b9 Verified against SHA256SUMSSigstore bundle: .cosign.bundle / .sigstore.json (same bundle, two names)

    Installchmod +x hMailServer-6.3.2-x86_64.AppImage

    What’s new in 6.3.2

    The webmail became a client people would choose, Kerberos single sign-on arrived on all three protocols, an auto-ban now reaches the operating system's firewall, the Linux packages install on the next distribution release, and thirty-seven binaries left the repository. Every change below went in through a pull request with the full Windows regression suite run on the built server, and the Linux build compiled every translation unit on every push.

    Full release notes for 6.3.2 8 sections
    The webmail

    The self-service portal at /portal shipped in 6.3.0 with a mailbox page. Fifteen waves later it is a mail client. Everything it does is over /api/v1/me, answers to the account's own credentials and to nothing else, and is described in the OpenAPI document at /api/v1/openapi.json.

    Reading. Conversations grouped by thread. Archive, Junk and Trash as one-key actions (e, !, #), with a move into or out of Junk taking the same path as an IMAP MOVE, so the spam filter learns from it. A search written the way a reader writes one: words that must all be found, quoted phrases, from:, to:, subject:, has:attachment, before:, after:, in:, is:unread, is:flagged and the rest, and label: for labels. Labels are IMAP keywords: the server stores them (schema 6036), STORE, FETCH, SEARCH, APPEND, COPY and a Sieve addflag all carry them, and every IMAP client sees the same labels the page shows in colour. Image attachments are shown under the message and a PDF is previewed in a frame of its own, from bytes the page fetched itself. A warning sits above a message whose link text names one site and goes to another, or whose sender is named like a contact but writes from another address. Shared and public folders appear under a heading naming their owner.

    Writing. A formatting bar and an editor whose HTML is rebuilt from an allowed subset before it goes, so a message is sent as text beside HTML. A From picker offering exactly the addresses the server would accept from the account - its own, its aliases, and the accounts whose INBOX grants it the post right - checked by the same rule SMTP submission applies. A signature. Templates with {first_name}, {subject} and {date} filled in. Undo send, for up to thirty seconds. Read receipts asked for and answered (RFC 8098). One-click unsubscribe (RFC 8058), made only to a public address: the server resolves the list's host and refuses loopback, private, link-local and every other non-public address before it connects. Send later, and snooze until a time, both kept on the server (schema 6034) and run by a task every minute. To, Cc and Bcc complete from an address book (schema 6032) that collects every recipient the account writes to once. A file above the size the domain allows goes as a link that expires and can take a password, uploaded in chunks and served from /files/{token} (schema 6035); an administrator sets the threshold and the lifetime server-wide or per domain.

    S/MIME, in the browser. The page signs, verifies, encrypts and decrypts with the Web Crypto API and nothing else. A certificate and its key come in from PEM, DER or a PKCS#12; the private key is wrapped in the browser under a key derived from the account password and stored on the server so wrapped (schema 6037) - the server cannot open it. The certificates of correspondents are kept from the signed messages they send. A signed message shows a badge beside the SPF, DKIM and DMARC ones; the chain is checked against the system's roots (the Windows ROOT and CA stores, the distribution's bundle on Linux). What the page signs is what arrives: the entity goes to the server byte for byte under the server's own headers. Proven against OpenSSL in both directions on every push. Not in this release, and said so where it is met: 3DES content, EC key agreement for encryption, legacy PKCS#12 encryption, OpenPGP.

    The pages. Settings - name, signature, theme, density, notifications, and the language: the seventeen the Control Panel speaks, and Greek, Korean and European Portuguese besides, beside English. The keyboard map (?) lists every key the page answers, l for the Label menu included, and the keys and the search operators are documented on one page, hmailserver/docs/WebmailShortcuts.md; README shows the inbox, a message open, and the Control Panel's Welcome page. Away - out of office, forwarding, and what is waiting to be sent later or to come back from snooze. Filters - a rules editor that writes Sieve, or the script by hand, left alone when it was written by hand. Contacts. Storage - the quota, every folder's size, the largest messages, and a clean-up of Trash and Junk by age. Files - the links sent, their downloads, their expiry. Security - app passwords, made only after the account's own password is given again; browser sessions, ended one at a time or all but this one; support access an administrator may be allowed for a while, off unless the user turns it on, with every request of it logged as the administrator acting as the user; and the S/MIME key store.

    The page itself. It installs as an app, keeps the inbox listing and the last thirty opened messages for reading without a connection, and queues a send that fails for want of one. An administrator gives it a name, a logo and an announcement, server-wide or per domain (PUT /api/v1/portal/branding). It imports .eml files and exports a folder as mbox. It is worked with a screen reader: landmarks, a skip link, focus that follows a change of address. It sends the password once, holds nothing in memory and allows no inline script.

    Authentication

    SASL GSSAPI (RFC 4752) on SMTP, IMAP and POP3, on Windows. A Kerberos client presents a ticket for this server's service principal, the server proves itself back, and the client's principal names the account - the one an administrator linked to that directory user, else the one addressed user@realm. Off unless GssapiEnabled=1 in [Settings]; the credentials are the process's own on a domain-joined host, or a service account named by GssapiServiceAccount and GssapiServicePassword. Proven end to end against a Windows Server domain controller: SMTP AUTH GSSAPI accepted and a message queued, IMAP AUTHENTICATE GSSAPI accepted and the message read back.

    An auto-ban reaches the operating system's firewall. Until now a banned address was a row the listeners consulted at accept time: the operating system accepted the connection and the server dropped it. With AutoBanFirewall=1, Windows gets an inbound block rule per banned address in Windows Defender Firewall, in a rule group of its own; Linux gets a packaged hook that keeps an nftables set, and a fail2ban filter and jail. AutoBanCommand runs anything else, and AutoBanNeverBan lists the addresses that are never banned. All three are off as shipped.

    Making an app password proves the person, not the request. The request carries the account's own password, and an app password is never accepted for it.

    A SURBL server's answers are judged the way a DNSBL's are. Each SURBL server has an expected result (SURBLServer.ExpectedResult, the Control Panel's SURBL editor; schema 6038) in the DNSBL syntax - 127.0.1.0-255, 127.0.0.2*, ranges and wildcards - and only an answer it names is a listing. With none set, any answer counts except the codes in 127.255.255.0/24, with which the Spamhaus zones refuse a query - from a public resolver, or too many of them - and which were taken as listings until now: a server resolving through 8.8.8.8 tagged every message carrying a link as spam (discussion #167). The debug log says what the zone answered and what was made of it.

    Linux

    The packages install on the next distribution release. The 6.3.1 .deb depended on the builder's exact Boost sonames and would not install on Ubuntu 26.04. Boost is linked statically now (Boost.Regex header-only, so no ICU dependency comes with it), and the package-install job installs the .deb on Ubuntu 26.04 in a container on every run.

    A container image on GHCR from every tag. The run's own .deb on ubuntu:24.04 pinned by digest, run as its own user with cap_net_bind_service on the binary. The entrypoint writes the database section, the REST listener and the administrator password from the environment (with _FILE variants for the two secrets), creates or upgrades the database, and starts the server. The image is smoke-tested beside a PostgreSQL over TLS before it is pushed.

    The Linux build is green on every push again. Three functions the webmail waves used on Windows only - _wtoi, wide-path file streams, _mkgmtime - have POSIX counterparts now, and the translation-unit census runs before every wave's pull request.

    Database

    The 6029 to 6030 upgrade step sweeps parents before children. 6.3.1 named this as known and unfixed: on a database holding orphaned rows, the step could re-orphan rows it had already cleaned and then refuse its own foreign keys. It is fixed in all four backends, and the reason it could ship at all is gone: a gate now executes a schema upgrade on a seeded database on every push, winding a created database back to 6029 by undoing every later step and upgrading it forward again.

    The fresh-schema create scripts drop dependent tables before hm_accounts in every dialect, so recreating a database that already holds them no longer fails on the foreign keys; and the contacts index is unique on (account, address), so two requests racing to collect the same address cannot store it twice.

    Schema 6032 to 6037: contacts, account preferences, scheduled sends and snoozes, files sent as links, message keywords, S/MIME keys.

    Every statement in a MySQL script is followed by a blank line, and a check keeps it so. The server's script parser splits a script into commands on a blank line and sends each command as one query; MySQL refuses a command holding two statements, where SQL Server and PostgreSQL take a batch. Two of this release's schema steps had written statements on consecutive lines, which the Linux build's database gate caught before any release carried them: a fresh MySQL installation would have had no database. build/check-sql-separators.py now applies the parser's rule to every MySQL script on every push.

    Build and supply chain

    Thirty-seven of the forty committed binaries left git. The manifest (hmailserver/docs/third-party-binaries.json) says how each is obtained; build/get-installer-binaries.ps1 fetches them from this repository's own build-inputs-1 release against the manifest's SHA-256, gathers the MSVC runtime from Visual Studio, and copies the ADO type libraries from Windows. The COM wrapper the .NET tools compile against is generated from the server's type library rather than committed. Fourteen relics went, and the installer's port check is fifteen lines of its own Pascal instead of a DLL of unknown origin.

    Provenance. Beside every cosign bundle a .sigstore.json twin, and SLSA build provenance for the assets as attached, verified with slsa-verifier after upload. The MariaDB Connector/C files in the build inputs are recorded as byte-identical to the vendor's own server archive, with the vendor's checksum file; the LGPL-2.1 text its licence requires accompanies the distribution. A Windows job re-verifies every Authenticode signature the manifest records, on every push that touches it.

    Workflows. Every job of the Linux build runs behind Harden-Runner in audit mode with its service images pinned by digest; the C++ CodeQL job places the Windows type libraries before it builds; Dependabot keeps the Actions and .NET dependencies current in one group each.

    Quality and tests

    Code Quality is back to zero findings, and stays there: the Linux test shims say what they mean.

    Native coverage can now be measured. The /Debug console server can be reached over COM, so a coverage tool that launches and debugs it can drive it with the whole regression suite; until now a client's CreateObject started the service instead, which is why native coverage was never measured.

    A server holding a database it refused answers COM with the refusal, not an access violation. When the database opens but its schema is not the one the build requires (HM5011), the process stays up so that DBUpdater can upgrade it through hMailServer.Database - but hMailServer.Application also handed out Settings, Domains, Rules and the rest over a configuration that was never loaded, and the first read through any of them was an access violation inside the COM call ("An error occurred processing the request" to the client, a record in the crash oracle). Those objects are now refused with the HM5011 text, which names DBUpdater, until the server is initialised; hMailServer.Database is untouched. Reproduced and proven on a domain controller with its database one step behind.

    The test SMTP simulator reads a large message in linear time.

    The regression suite can drive a console server. With HM_CONSOLE_SERVER=1 in the environment, a fixture or test that restarts the service is skipped with the reason instead of stopping a /Debug server that no service command can bring back - which is what the first coverage run did, and why everything after its first restart ran against nothing.

    The pre-flight refuses to pass while WSL is running, because a Linux server or test run left alive there holds the suite's ports invisibly.

    Documentation and governance

    SECURITY.md states the vulnerability management policy the automation enforces - the thresholds, not just the tools - and an OpenVEX statement accompanies it. GOVERNANCE.md names two maintainers. The release checklist says what the 6.3.1 cut proved and that releases are immutable once published. The README describes the webmail as it is, offers the Linux packages on its download line, and carries the OpenSSF badges. The Control Panel's list pages enable a button only while there is a selection, and the settings search index knows the auto-ban firewall settings.

    Verification

    The full regression suite ran on the stamped binary: 2,219 tests, 2,211 passed, 0 failed, 8 skipped. hMailServer.exe 6.3.2.41, SHA-256 4C7D5A2C7FA6537E460EADD6DCC85D5AEDB3559FE349E2DEC9547FA99086D3E8.

    The installer has two hashes, and both are given deliberately. hMailServer-6.3.2-x64.exe as built, 79,683,220 bytes: SHA-256 746F17303D219930AAED03D836F3C2C55E367BCD59620BA60E16EAE7EFB5F589. As published, after Authenticode signing: SHA-256 D10EB643C7D4717655DA19B8FACBD4916D393024BA7D18AC5CCDE492F2140DE9.

    The assertion build, first. A separate build with HM_ASSERT on - every internal invariant checked and any failure fatal - ran the whole suite before the shipping binary was built: 2,219 tests, 2,211 passed, 0 failed, 8 skipped. Nothing asserted.

    Reproducible. Three clean Release builds of the stamped tree - the first two compared for the purpose, the third the one that ships - produced the same hMailServer.exe, SHA-256 4C7D5A2C7FA6537E460EADD6DCC85D5AEDB3559FE349E2DEC9547FA99086D3E8, 13,755,904 bytes; anyone with the same toolchain can build the tag and compare.

    Fuzzing. The three MIME harnesses ran thirty minutes each on this release's source on 13 September 2026: mime_message_fuzzer 106,581 executions, mime_header_fuzzer 120,063, mime_decode_fuzzer 3,199,620 - no crash, no hang, no violated assertion, and every input the earlier campaigns kept under fuzz/regression still survives. The harnesses are the 9 September builds, which is the same parser code: nothing under Common/Mime changed between 6.3.0 and this release. The parsers that did change - the S/MIME entity a page submits, IMAP keywords in STORE, SEARCH and APPEND, the search syntax - are covered by the regression suite and, for the entity path, by the OpenSSL round-trip check on every push, not by a fuzz harness yet.

    Release facts
    Version
    6.3.2
    Signed
    13 September 2026Sigstore transparency log
    Packages
    6Windows and Linux, x86-64 and AArch64
    Database schema
    6038upgrades in place
    Regression suite
    2,219 tests2,211 passed · 0 failed · 8 skipped
    Windows build
    6.3.2.41reproducible
    Installer SHA-256
    d10eb643c7d4…f2140de9hMailServer-6.3.2-x64.exe
    Every file in 6.3.2

    Full SHA-256 of each file as recomputed on this server, with the signature bundles beside it. The SBOMs and provenance are not needed to install; they let anyone audit exactly what was built.

    FileWhat it isSizeSHA-256Signature
    hMailServer-6.3.2-x64.exeWindows installer · x6476.0 MBd10eb643c7d4717655da19b8facbd4916d393024ba7d18ac5ccde492f2140de9 release notesSigstore bundle: .cosign.bundle / .sigstore.json (same bundle, two names)
    hmailserver_6.3.2_amd64.debDebian package · x86-644.0 MB2a937e48df11fba5490c3d6e2efac7114ea0f9754bf116dfa9638995fffc2730 SHA256SUMSSigstore bundle: .cosign.bundle / .sigstore.json (same bundle, two names)
    hmailserver_6.3.2_arm64.debDebian package · AArch643.9 MB7705dd264474a6b37d8815d5827446b52672c6990bcb018d16eee92a8b722878 SHA256SUMSSigstore bundle: .cosign.bundle / .sigstore.json (same bundle, two names)
    hmailserver-6.3.2-1.x86_64.rpmRPM package · x86-644.0 MBffa659a117357a384e02201f70c22eea41d989ccf795c6ea2344b8b75ce0cb80 SHA256SUMSSigstore bundle: .cosign.bundle / .sigstore.json (same bundle, two names)
    hmailserver-6.3.2-1.aarch64.rpmRPM package · AArch643.9 MBb151032d434a269a89656ca00f82c56bd060e311cb6279e97ecdf52f963d341d SHA256SUMSSigstore bundle: .cosign.bundle / .sigstore.json (same bundle, two names)
    hMailServer-6.3.2-x86_64.AppImageAppImage · x86-649.5 MB0354e8ae2dd060ce5a272e2fdc5eab09aac5acf212655b3686c16715b10ef3b9 SHA256SUMSSigstore bundle: .cosign.bundle / .sigstore.json (same bundle, two names)
    hmailserver-linux-6.3.2-SHA256SUMS.txtSHA-256 checksumsOne line per Linux package; feed it to sha256sum -c.584 bytes07269ab451a857dd51b54995aa748ecc8fd24f4e5ef0915c4b5176bffe8855d0Sigstore bundle: .cosign.bundle / .sigstore.json (same bundle, two names)
    hmailserver.spdx.jsonSBOM (SPDX)Every component in the build, in SPDX JSON.279 KBabd9ff9abf50fff8f6420da587bcd7a388472be78546d23f3b778213cc0a6d74Sigstore bundle: .cosign.bundle / .sigstore.json (same bundle, two names)
    hmailserver.cyclonedx.jsonSBOM (CycloneDX)The same inventory in CycloneDX JSON.176 KBd85e7e8f39ef222db607e5b7618dad891f5b488a7fd3f0579b74f791e1953e2cSigstore bundle: .cosign.bundle / .sigstore.json (same bundle, two names)
    hmailserver-v6.3.2.intoto.jsonlSLSA build provenancein-toto attestation of how and where the release was built.24 KB99b44279a514674c8413e3971b33b97ba54a3fb21445a97fd6958a4f22caee05
    Progressiverobot-hmailserver-attestation-47145346.sigstore.jsonBuild attestationGitHub artifact attestation for the release, as a Sigstore bundle.8 KB7b4062773bbadfaf19739990904077e823dd09468695e334e79687db52f0652e
  3. 6.3.1

    Signed 11 September 20267 packages

    This is the first release of this project to carry an Authenticode signature.

    Release notes, checksums and signatures
    Windows

    Windows 10 (version 1607 or later) and Windows 11, or Windows Server 2016 and newer; 64-bit only. Run the installer as an administrator: it upgrades any existing 5.x or 6.x installation in place. The installer is Authenticode-signed by Progressive Robot Ltd.

    • hMailServer-6.3.1-x64.exex64 · 75.7 MB
      Download
      SHA-25668c72a60e7a8…58ac9b70 Verified against release notesSignature: cosign bundle
    Debian and Ubuntu

    A .deb for Debian, Ubuntu and their derivatives on x86-64 and AArch64, built on Ubuntu 24.04: install it on a matching distribution release. It sets up the service, its systemd unit and the configuration under /etc/hmailserver.

    • hmailserver_6.3.1_amd64.debx86-64 · 3.5 MB
      Download
      SHA-25657cab27c70ce…292b7171 Verified against SHA256SUMS
    • hmailserver_6.3.1_arm64.debAArch64 · 3.4 MB
      Download
      SHA-256dcdba7cca4b5…31977cba Verified against SHA256SUMSSignature: cosign bundle

    Installsudo apt install ./hmailserver_6.3.1_amd64.debthen the three first-run commands

    Fedora, RHEL and openSUSE

    An .rpm for Fedora, Red Hat Enterprise Linux, RHEL-compatible distributions and openSUSE on x86-64 and AArch64. dnf resolves its dependencies from your own repositories; match the distribution release the package was built for.

    • hmailserver-6.3.1-1.x86_64.rpmx86-64 · 3.5 MB
      Download
      SHA-2560f97ea33b8bf…057bdfcf Verified against SHA256SUMSSignature: cosign bundle
    • hmailserver-6.3.1-1.aarch64.rpmAArch64 · 3.4 MB
      Download
      SHA-2560da4253e38bb…2194231a Verified against SHA256SUMSSignature: cosign bundle

    Installsudo dnf install ./hmailserver-6.3.1-1.x86_64.rpmthen the three first-run commands

    AppImage (try it out)

    For seeing hMailServer run on a laptop, not for running mail: it runs as whoever starts it, with no service user, no systemd unit and no hardening, and it cannot bind port 25. Install the .deb or the .rpm to run mail.

    • hMailServer-6.3.1-x86_64.AppImagex86-64 · 9.5 MB
      Download
      SHA-25672fd05d3c80d…12f101cb Verified against SHA256SUMSSignature: cosign bundle
    • hMailServer-6.3.1-aarch64.AppImageAArch64 · 9.2 MB
      Download
      SHA-256421996c406f0…7b2fdaf9 Verified against SHA256SUMSSignature: cosign bundle

    Installchmod +x hMailServer-6.3.1-x86_64.AppImage

    What’s new in 6.3.1

    This is the first release of this project to carry an Authenticode signature. The Windows elevation prompt names Progressive Robot Ltd instead of Unknown publisher, and an enterprise policy that refuses unsigned binaries outright stops being a wall.

    Full release notes for 6.3.1 5 sections

    Nothing in the server changes. The compiled server differs from 6.3.0 by its version stamp and one comment line; everything else in this release is the path a release travels - the workflows that sign and verify it, found wanting while preparing to use the certificate for the first time - and three documentation corrections.

    The installer is signed

    The installer is signed with Azure Artifact Signing, against a certificate profile issued to Progressive Robot Ltd after Microsoft's identity validation. The account, the validation and the profile were arranged on 10 September 2026, after 6.3.0 had shipped. A published release here is immutable, so 6.3.0 could not be signed retroactively and never will be.

    What it changes. The elevation prompt reads the publisher's name. And for administrators on managed estates - a great many of this server's users - a policy that refuses unsigned binaries no longer refuses this one.

    What it does not change, said plainly: SmartScreen still warns. Microsoft's own comparison puts a signed installer in the same row as an unsigned one, flagged as unrecognised until reputation accumulates, and reputation attaches to a file that does not change - which a new 80 MB installer every few weeks never is. An EV certificate would not help; Microsoft removed EV's SmartScreen bypass in 2024. Expect the same Windows protected your PC screen, now with the publisher's real name on it.

    Only the Windows installer is Authenticode-signed. There is no Authenticode for a .deb, an .rpm or an AppImage. Sigstore is unchanged and remains the check that means something for every asset on the release: each carries a .cosign.bundle beside it, and cosign verify-blob against that bundle verifies it.

    One setting becomes usable. UpdateRequireAuthenticode=1 has existed since 6.2.28 and, until now, made the server's own update path refuse every release of this project, because none carried a signature: the check is WinVerifyTrust on the downloaded installer, and an unsigned file is refused with the file carries no Authenticode signature. From 6.3.1 the installer it downloads carries one. The check is Windows-only: on Linux there is no Authenticode to verify, the server says so rather than reporting a pass, and updating is the package manager's job.

    The release path

    Releases here are immutable, so each of these could have put out a permanently wrong release.

    The Authenticode gate was checking the wrong four of the six settings it guards. It asked for the three Azure secrets and ARTIFACT_SIGNING_ACCOUNT - the one input the signing action marks optional - and never asked about ARTIFACT_SIGNING_ENDPOINT or ARTIFACT_SIGNING_PROFILE, the two it marks required. Five half-configured states got through, each a state somebody passes through while filling in three repository variables one at a time; the action throws on an empty endpoint, and the cosign job declares a dependency on this one, so a release that tripped it would have got no Authenticode signature and no Sigstore bundle on any of its twenty assets. There are three outcomes now: none of the six set is the ordinary case and stays a silent no-op, all six signs, and anything in between stops and names what is missing while the release can still be fixed. The six values, and the tag, reach the shell through the environment rather than being pasted into the script text, because an expression pasted into a script is executed as syntax if it holds a quote. The release is verified as it is attached, not as it was signed. The old check looked only at the job's own copies. A draft stays mutable afterwards, and four things upload over it with --clobber: a re-run of the Linux publish job, the SBOM workflow, a second Authenticode run, and the hand-recovery commands the Linux job prints into its own log. The workflow now ends by re-downloading the release exactly as it stands and verifying every asset against its attached bundle, in both directions - an asset with no bundle, and a bundle whose asset has gone. What a release must carry is asserted before a certificate is spent on it. The only completeness check was "did we sign at least one thing", and the SBOMs were named nowhere but in comments - which is how 6.2.22-pre4 came to be published with an installer and no SBOM at all. Both SBOMs are a hard failure now, in the Authenticode job rather than the signing job, so an incomplete release stops before a code-signing certificate has been spent on it. The Linux sums file and the four package names stay warnings, deliberately: a Linux packaging failure must not hold back the Windows installer every server in the field is waiting for. An RFC3161 countersignature is required, and is checked. These certificates live about seventy-two hours. Get-AuthenticodeSignature answers Valid for a signature carrying no timestamp for as long as the certificate has not expired, which it never has seconds after signing - so an untimestamped installer would look perfect on the day and stop verifying on every machine in the field three days later, on a release that cannot be corrected. The countersignature is proved before the release asset is replaced, so a timestamping failure costs a re-dispatch and nothing else. Verification can no longer pass vacuously. A glob that matched nothing used to verify nothing and succeed. Both verification steps now refuse to pass having checked zero assets, and compare what they verified against what the signing step reported. Runs are serialised per tag. Two dispatches for one tag could interleave, and one run's bundle could end up describing bytes the other had already replaced. They queue now, and are not cancelled: a run stopped between signing and uploading would leave assets with no signature at all. The SBOM workflow refuses to overwrite an SBOM that has already been signed, because cosign signs the bytes attached when it runs and Syft writes a fresh document identifier every time, so a re-upload after signing leaves a signature that verifies against nothing. It also proves the tree it describes is the tag's own commit before Syft runs - a bill of materials describing a tree the release was not built from is the one thing an SBOM must never do. The installer smoke test can read a draft release, which is the only kind it is ever pointed at. It asked for contents: read, and GitHub shows an unpublished release only to a token with push access, so every dispatch against a draft answered "release not found" - which made the documented order, green before publication, impossible rather than merely awkward. It asks for contents: write now, and writes nothing. The Linux package-install check runs under sudo, like every other command in its step. The package makes /etc/hmailserver 0750 root:hmailserver on purpose, because the configuration holds credentials, so a bare stat answered "Permission denied" and under set -e took the job with it. That is why 6.3.0's tagged run attached no Linux packages and they had to be attached by hand.

    All of the above was verified by executing the real run bodies against the live 6.3.0 asset list with gh and cosign stubbed - the twenty-asset release, a re-run over already-attached bundles, a Windows-only release, a pre-release tag, the hand-recovery shape, a release with no SBOMs, an orphaned bundle, an asset excluded from signing by design, and an empty download - and the gate across fifteen configurations, including whitespace-only values and five hostile ones, none of which executed anything. The Authenticode path itself was rehearsed end to end on a throwaway copy of the 6.3.0 installer before this tag was cut: a valid, countersigned signature naming Progressive Robot Ltd, attached to nothing.

    Documentation

    The README offers the Linux packages on its download line, where a reader looks for a download, rather than 140 lines further on in the Linux section. The headline of 6.3.0 was that this server runs on Linux at all. Two files named 6.2.29, a version that does not exist and never will: the [Directories] header comment in the server source, and the relocating guide's note about when a relative path began to resolve against the program folder. Both features shipped in 6.3.0 and both now say so. Comment and prose only. The release checklist gives ARTIFACT_SIGNING_ENDPOINT as a full URI rather than a bare host, which is the form the signing action documents and one of the two values that used to pass the old gate and then fail inside the action, and it says the six settings are required together.

    Known, and not fixed here

    The orphan sweep in the 6029 to 6030 upgrade step runs children before parents. Before that step adds seventeen foreign keys, it deletes rows whose parent is gone. Three of the parent tables are themselves pruned by those deletes, and each one's children are cleaned earlier, against a parent that has not yet lost its rows - so pruning an orphaned account, fetch account or distribution list re-orphans rows nothing revisits, and the constraint that follows is refused. It affects only an upgrade from a schema below 6030 on a database that already holds orphaned rows; a database without them upgrades correctly, a fresh installation is never on this path, and when it does fire it fails loudly with the engine's own words and rolls back rather than doing anything quietly. It is in all four database backends. No installation has reported hitting it. The fix is a reordering of seventeen delete statements and is held for 6.3.2, because a change to a database upgrade script belongs in a release whose gates can execute an upgrade - and this project's cannot yet, which is exactly how the defect was able to ship. That gate is being built first.

    The report that led to this - a Compact Edition upgrade from schema 6011 refused at the same step (#93) - turned out to be #114, already fixed in 6.2.26: the upgrade had succeeded and the verification probe had crashed the database provider afterwards, and the tool then printed a cause it had no way to know. That installation upgrades on 6.3.0 and on this release.

    Verification

    The full regression suite ran on the stamped binary: 2,175 tests, 2,166 passed, 0 failed, 9 skipped (the seven explicit IMAP stress tests and two ignored), in 40 minutes, with no ERROR log written. hMailServer.exe 6.3.1.40, SHA-256 8b0ab1a2d1f3645f1e12c3bca1315023474bb3a63abf0efb44f60ba0b2d83235.

    The installer has two hashes, and both are given deliberately. hMailServer-6.3.1-x64.exe as built, 79,408,192 bytes: SHA-256 3986fbcccda9e46fee29c1e538a79f2423ebc4d5e3f6e59306ffba4cc7bd8cba. As published, 79,423,896 bytes: SHA-256 68c72a60e7a8530bdd32d28b60eaadab5b9c90505e174c03196e5dfa58ac9b70. They are different, and this is the first release for which that is so: the signing job replaces the installer asset with the signed one before cosign runs, so the bytes on the release are not the bytes the build produced. The second is the one to check a download against. Verified after download with Get-AuthenticodeSignature: Valid, signer CN=Progressive Robot Ltd, O=Progressive Robot Ltd, L=Chester, S=Cheshire West and Chester, C=GB, issued by Microsoft ID Verified CS EOC CA 03 for three days from 10 September 2026 19:35 UTC, countersigned by Microsoft Public RSA Time Stamping Authority - which is what keeps the signature valid after the certificate itself has expired. The installer smoke test ran against this signed file on a throwaway runner and passed.

    The assertion build, first. The same source compiled with every HM_ASSERT kept - a violated one is reported as HM6364 in the error log rather than compiled out - and the whole suite run on it: 2,175 tests, 0 failed, 9 skipped (the seven explicit IMAP stress tests and two ignored), no assertion violated anywhere in the run.

    Reproducible. Two clean Release builds of the stamped source produced a byte-identical hMailServer.exe, SHA-256 8b0ab1a2d1f3645f1e12c3bca1315023474bb3a63abf0efb44f60ba0b2d83235.

    Fuzzing. No parser changed between 6.3.0 and this release, so 6.3.0's run stands, as the release checklist allows for a patch release: the three MIME harnesses, thirty minutes each under libFuzzer and ASan, 8,296,354 executions, no crash, hang or assertion.

    Release facts
    Version
    6.3.1
    Signed
    11 September 2026Sigstore transparency log
    Packages
    7Windows and Linux, x86-64 and AArch64
    Regression suite
    2,175 tests2,166 passed · 0 failed · 9 skipped
    Windows build
    6.3.1.40reproducible
    Installer SHA-256
    68c72a60e7a8…58ac9b70hMailServer-6.3.1-x64.exe
    Every file in 6.3.1

    Full SHA-256 of each file as recomputed on this server, with the signature bundles beside it. The SBOMs and provenance are not needed to install; they let anyone audit exactly what was built.

    FileWhat it isSizeSHA-256Signature
    hMailServer-6.3.1-x64.exeWindows installer · x6475.7 MB68c72a60e7a8530bdd32d28b60eaadab5b9c90505e174c03196e5dfa58ac9b70 release notesSignature: cosign bundle
    hmailserver_6.3.1_amd64.debDebian package · x86-643.5 MB57cab27c70ce8a55a730d0b88ea91b712134acd5eda103b88e508cf4292b7171 SHA256SUMS
    hmailserver_6.3.1_arm64.debDebian package · AArch643.4 MBdcdba7cca4b53e99ac8f7fc9772aa244e2abbc78b9954d02dfbff37b31977cba SHA256SUMSSignature: cosign bundle
    hmailserver-6.3.1-1.x86_64.rpmRPM package · x86-643.5 MB0f97ea33b8bf6254c9f6fb2ae9de22b4158ababde8c2bbc0bc29cc9c057bdfcf SHA256SUMSSignature: cosign bundle
    hmailserver-6.3.1-1.aarch64.rpmRPM package · AArch643.4 MB0da4253e38bbb5002fedb6aba4cc638f06ed508399cce9f2d21c753a2194231a SHA256SUMSSignature: cosign bundle
    hMailServer-6.3.1-x86_64.AppImageAppImage · x86-649.5 MB72fd05d3c80dcd548f12de070640d32b642cd11702a1a504f7201b9212f101cb SHA256SUMSSignature: cosign bundle
    hMailServer-6.3.1-aarch64.AppImageAppImage · AArch649.2 MB421996c406f092339c95502f73092178a80c66fc051ee54c059c81af7b2fdaf9 SHA256SUMSSignature: cosign bundle
    hmailserver-linux-6.3.1-SHA256SUMS.txtSHA-256 checksumsOne line per Linux package; feed it to sha256sum -c.584 bytes7e1c261932a336e812bb7f7379599d5219f11e1c493216347bbda42c90ee5badSignature: cosign bundle
    hmailserver.spdx.jsonSBOM (SPDX)Every component in the build, in SPDX JSON.323 KB42144251bcf65463419d3cc0f658afb555865e53bd3f0e55b6815b3fe1acd43cSignature: cosign bundle
    hmailserver.cyclonedx.jsonSBOM (CycloneDX)The same inventory in CycloneDX JSON.198 KBe88343bba16db480325fc16036641d7e4701f1b29e782424e04bd456ba13366aSignature: cosign bundle
    Progressiverobot-hmailserver-attestation-46758583.sigstore.jsonBuild attestationGitHub artifact attestation for the release, as a Sigstore bundle.6 KBf6e9698be816d18ccef8ff623b92d7749b86a4d12a26428e5d427612c6a7f861
  4. 6.3.0

    Signed 10 September 20267 packages · schema 6031

    hMailServer runs on Linux. That is the whole of why this is 6.3 and not 6.2.29: a mail server that has been Windows-only since 2002 now builds, installs…

    Release notes, checksums and signatures
    Windows

    Windows 10 (version 1607 or later) and Windows 11, or Windows Server 2016 and newer; 64-bit only. Run the installer as an administrator: it upgrades any existing 5.x or 6.x installation in place.

    • hMailServer-6.3.0-x64.exex64 · 75.7 MB
      Download
      SHA-256d9f1217375dd…70434fcb Verified against release notesSignature: cosign bundle
    Debian and Ubuntu

    A .deb for Debian, Ubuntu and their derivatives on x86-64 and AArch64, built on Ubuntu 24.04: install it on a matching distribution release. It sets up the service, its systemd unit and the configuration under /etc/hmailserver.

    • hmailserver_6.3.0_amd64.debx86-64 · 3.5 MB
      Download
      SHA-2560ea011824f38…332d32fd Verified against SHA256SUMSSignature: cosign bundle
    • hmailserver_6.3.0_arm64.debAArch64 · 3.4 MB
      Download
      SHA-256e848a9ef05f6…9e868c9c Verified against SHA256SUMSSignature: cosign bundle

    Installsudo apt install ./hmailserver_6.3.0_amd64.debthen the three first-run commands

    Fedora, RHEL and openSUSE

    An .rpm for Fedora, Red Hat Enterprise Linux, RHEL-compatible distributions and openSUSE on x86-64 and AArch64. dnf resolves its dependencies from your own repositories; match the distribution release the package was built for.

    • hmailserver-6.3.0-1.x86_64.rpmx86-64 · 3.5 MB
      Download
      SHA-256ff79d4705be7…51f3be78 Verified against SHA256SUMSSignature: cosign bundle
    • hmailserver-6.3.0-1.aarch64.rpmAArch64 · 3.4 MB
      Download
      SHA-2569b4ab11effd6…8c1c9ac4 Verified against SHA256SUMSSignature: cosign bundle

    Installsudo dnf install ./hmailserver-6.3.0-1.x86_64.rpmthen the three first-run commands

    AppImage (try it out)

    For seeing hMailServer run on a laptop, not for running mail: it runs as whoever starts it, with no service user, no systemd unit and no hardening, and it cannot bind port 25. Install the .deb or the .rpm to run mail.

    • hMailServer-6.3.0-x86_64.AppImagex86-64 · 9.5 MB
      Download
      SHA-256ade1e27e1721…1d8059a1 Verified against SHA256SUMSSignature: cosign bundle
    • hMailServer-6.3.0-aarch64.AppImageAArch64 · 9.2 MB
      Download
      SHA-2563a16b9557db1…2fc824d1 Verified against SHA256SUMSSignature: cosign bundle

    Installchmod +x hMailServer-6.3.0-x86_64.AppImage

    What’s new in 6.3.0

    hMailServer runs on Linux. That is the whole of why this is 6.3 and not 6.2.29: a mail server that has been Windows-only since 2002 now builds, installs, runs and delivers mail on x86-64 and AArch64 Linux, from the same source tree, with the Windows build unchanged in what it compiles and how it behaves.

    Full release notes for 6.3.0 8 sections

    Two other things arrived with it, because a Linux server has no Control Panel and no COM: the REST API gained a large write surface, and the browser drives most of it.

    The server runs on Linux

    One tree, two platforms. Every core translation unit compiles on Linux, counted one file at a time by build/linux-tu-census.sh rather than estimated. The census is a step of the build job: it runs under clang on x86-64 and on AArch64 - a native runner, not a cross-build - and the job fails when a single file stops compiling. A third job builds and links the whole core with GCC on x86-64, which is what stops the port quietly becoming clang-only. The Windows build is the same MSVC project it was; nothing in this release changes what it compiles or how it behaves.

    Installed the way a Linux service is installed. A .deb and an .rpm for both architectures, a PKGBUILD for Arch, and an AppImage: the binary, the schema scripts, a systemd unit that runs the server as its own user, a logrotate rule, the configuration under /etc/hmailserver, and the Control Deck. hmailserver --create-database builds the schema, --upgrade-database moves it forward on a package upgrade, --set-admin-password sets the credential, and --check-config reads the configuration back before anything starts.

    Proven, not assumed. Against PostgreSQL 18 and MariaDB 11.8: the schema is created, a domain and an account are made over the API, a message is submitted over SMTP, delivered, and read back over IMAP. Directory authentication runs against a real slapd over StartTLS and LDAPS, including a refused bind and a self-signed certificate. Stored secrets are AES-256-GCM under a key file the installation owns, which is what DPAPI does on Windows. A crash is recorded before the process dies, as it is on Windows.

    Reproducible on the runner image of the day. No absolute path reaches the binary and the clock every tool would otherwise stamp is the commit's own date, so the same commit built twice on the hosted runners gave byte-identical binaries for x86-64 clang, x86-64 GCC and AArch64 clang. What is not pinned is the image: ubuntu-latest moves, and a compiler or a Boost that moved between two runs changes the binary legitimately. The Windows claim under Verification is the stronger one, and RELEASE.md says why.

    The REST API writes what only COM could write

    PUT /api/v1/settings and its anti-spam and logging groups: 108 settings written, each through the same setter the Control Panel calls, applied only when every key in the request is accepted. The three groups describe 113 keys in all - five of the logging group's are read-only facts about where the log is written rather than settings, and the relayer password is written and never read back. Global rules with their criteria and actions. SMTP routes with their address lists. Aliases. Accounts, including the password and the administrator level. Certificates, and the listeners that bind them. And POST /api/v1/server/reinitialize, which is what the Control Panel's Reinitialize does, so a new listener takes effect without stopping the process.

    Every one of them is refused to a read-only key, refused to a key issued for other domains, and scoped to its own domain where a domain owns it. The OpenAPI document describes all of it, and is now valid JSON - it never was before this release.

    What it still does not write, said plainly: a domain's own properties beyond its active flag and its postmaster. DKIM signing above all - there is a read route and no write route - and with it the per-domain size limits, the domain signature and a per-domain relay host. On Windows the Control Panel sets those. On Linux nothing does yet, so a domain that must sign its outbound mail with DKIM is not one to run on this platform today.

    The Control Deck writes, and signs in properly

    The administration page at / gained editors for most of what the API now writes. Its settings forms are generated from the server's own OpenAPI document, so every setting the API accepts appears with its type, its permitted words, whether it is read-only, and whether it takes effect on restart. Rules, routes, certificates and listeners are created, edited and deleted; an account is created and deleted under a domain; and a button restarts the services in place. Three write surfaces the API has are not on the page yet: a domain, an alias, and changing an account that already exists.

    It no longer keeps the administrator password. POST /api/v1/session exchanges it once for an HttpOnly, SameSite=Strict session cookie that ends when the administrator password changes; a write must carry X-Requested-With; an API key cannot mint a session.

    Webmail

    The self-service portal at /portal is a webmail now rather than a page that reads mail. It is still served by the same listener as the API and still comes out of the binary rather than out of files beside it, so it is there on a machine that has nothing else, and it fetches no font, no image and no stylesheet from anywhere. The markup and its script are two responses, /portal and /portal.js, precisely so that the page's Content-Security-Policy can allow no inline script at all.

    It has addresses. #/f/<id>, #/m/<id>, #/search, #/compose: a folder and a message can be linked to, Back and Forward work, a reload lands where the reader was, and a half-written reply survives one.

    It looks like the Control Deck, which is to say like the Control Panel - the same visual language, applied to a page that had none.

    It notices new mail. GET /api/v1/me/changes hashes the per-folder message and unseen counts into an opaque token and answers changed against the one you hand back. The page polls it every six seconds, stops dead while the tab is hidden, and puts the reader's cursor back by message id rather than by row.

    It manages folders. POST, PUT and DELETE /api/v1/me/folders create, rename and delete, judging a name exactly as IMAP's CREATE, RENAME and DELETE judge it and refusing in IMAP's own sentences. The page does not offer delete for the inbox or for a folder the server has designated for a special use: that route refuses those deliberately, because a page has no undo.

    Inline images work, and the way they work is the interesting part. Attachment entries carry content_type and content_id. The obvious design - point the <img> at the attachment download route - cannot work, and that was measured rather than assumed: the message body is rendered in a frame sandboxed without allow-same-origin, so its document has an opaque origin, so the browser counts its subresource requests as cross-site, so the SameSite=Strict session cookie never travels and the route answers 401. The only thing that would fix that is the attribute the frame exists to withhold. So the page fetches the attachment itself, with its own credentials, and hands the frame the bytes as a data: URL. The frame's policy names no URL at all, and remote images stay off. What decides whether something is inlined is the server's own Content-Type on the download rather than the message's claim, so the types the download route deliberately neuters - HTML, SVG, XML, script, all served as application/octet-stream - are never inlined and stay in the attachment list.

    And its script is executed in CI. build/check-portal-script.py extracts the page and the script out of the C++ literals and runs the script against a small DOM and a stubbed API: 88 checks. What stood there before was a test that asserted certain substrings appeared.

    Under the surface

    A message with a non-ASCII folder name is stored correctly on Linux. The modified UTF-7 encoding IMAP uses was broken in both directions there: the conversion handed iconv the string's terminator, which its UTF-7 decoder refuses, and never made the flush call that ends a stateful encoding. A command line with a non-ASCII byte arrives whole. CStdString converted narrow to wide through the C locale on Linux, which stops at the first byte above 127 - so an IMAP SEARCH CHARSET UTF-8 and a MAIL FROM under SMTPUTF8 were cut short. A TLS key-exchange group list that OpenSSL rejects is reported once, not once for every listener and every outbound delivery. On the OpenSSL that Debian and Ubuntu ship, which is older than the ML-KEM hybrids this server prefers, that was hundreds of medium errors an hour for a condition the fallback already handled. The administrator's user name is no longer translated in the Control Panel's sign-in box, which made a fresh installation in Chinese, German or Swedish refuse the credential it had just asked for. (#156, #177) The installation-paths diagnostic lists every directory the server was configured with and whether it exists. (#158)

    Verification

    The full regression suite ran on the stamped binary: 2,175 tests, 2,166 passed, 0 failed, 9 skipped (2 ignored, 7 explicit stress runs). hMailServer.exe 6.3.0.39, SHA-256 C9E8853B89883B02CF0BB3E5FBCA33E87CC6A6E8DD0666CE61B101F2EB95638B; installer hMailServer-6.3.0-x64.exe, SHA-256 D9F1217375DD70D523FFB27DBDCD668DD525EDC98EDF64F00B4F69D070434FCB.

    The assertion build, first. The same source compiled with every HM_ASSERT kept - a violated one is reported as HM6364 in the error log rather than compiled out - and the whole suite run on it: 2,175 tests, 0 failed, the same 9 skipped, and no assertion violated anywhere in the run.

    Reproducible. Two clean Release builds of the stamped source produced a byte-identical hMailServer.exe, SHA-256 C9E8853B89883B02CF0BB3E5FBCA33E87CC6A6E8DD0666CE61B101F2EB95638B. The Linux binaries keep the same promise by a different mechanism, described in RELEASE.md.

    Fuzzed. Three libFuzzer targets over the MIME parser and its character handling, thirty minutes each on the release source: mime_message_fuzzer 241,817 executions, mime_header_fuzzer 261,618, mime_decode_fuzzer 7,792,919 - 8,296,354 in all, with no crash, no hang, no leak and no violated assertion. Required this release rather than carried over, because Mime.cpp, CodePages.cpp, Charset.cpp, ByteBuffer.cpp, StringParser.cpp and Unicode.cpp all changed.

    And the suite ran against the Linux server. hmailserver/test/LinuxRegressionTests is a net10.0 project that compiles 260 of the suite's own files in place - not copies of them - under a REST-backed fixture layer standing where COM stands on Windows, and runs them against the Linux server on PostgreSQL: 756 passed, 0 failed, 649 skipped of 1,405, in 40 minutes. A test that passes there is the same test that passes here; no fixture is edited and no assertion is weakened. CI runs it on every push, against the binary the same workflow has just built.

    The Linux packages install on a clean machine and the installed server answers.

    Upgrading

    No schema change: 6031, as 6.2.28. The installer's database upgrade runs as usual and has nothing to do on a 6.2.28 database.

    Windows. Run the installer over the existing installation, as always. Nothing this release adds is on by default: the REST API needs RestApiPort, which is what the Control Deck and the portal are served on, and a server upgraded without touching its settings behaves exactly as 6.2.28 did.

    Linux, first installation. apt install ./hmailserver_6.3.0_amd64.deb or dnf install ./hmailserver-6.3.0.x86_64.rpm, then hmailserver --create-database, hmailserver --set-admin-password, systemctl enable --now hmailserver. The configuration is /etc/hmailserver/hMailServer.ini, which carries a paragraph on every key it sets. The wiki's Installing on Linux page is the walk-through.

    Moving a Windows installation to Linux is not a supported migration and this release does not claim it is. The database schema is the same and the message store's on-disk format is the same on both, so the pieces are there; what is missing is a tested path, and the roadmap says so rather than this page implying otherwise.

    Known and unfixed

    The installer is not Authenticode-signed. This project has no code-signing certificate, so Windows shows an unknown publisher and SmartScreen warns before it will run the installer, as it has for every release of this fork. What is signed is Sigstore: every asset on the release carries a .cosign.bundle beside it, and cosign verify-blob against that bundle is the check that means something here. It is also why the server's own updater refuses this project's releases when UpdateRequireAuthenticode=1; the default is 0 and the Sigstore verification cannot be turned off. Per-domain DKIM cannot be configured on Linux. As the API section says: there is a read route and no write route, and a PUT to it answers 404. On Windows the Control Panel sets it; on Linux nothing does. A domain that must sign its outbound mail with DKIM is not one to run on this platform today. The Linux regression run still skips, and the skips are the API's gaps rather than the server's. A fixture that reaches something no route can answer is ignored with a reason naming what is missing - hMailServer.ini itself, the administrator password, the event-handler scripting, the anti-spam address lists, the account groups, the incoming relays, an account's or the administrator's second factor, the directory-sync preview, the live log - rather than being weakened until it passes. Those are covered on Windows, where the suite drives COM, and the count above is what remains.

    Release facts
    Version
    6.3.0
    Signed
    10 September 2026Sigstore transparency log
    Packages
    7Windows and Linux, x86-64 and AArch64
    Database schema
    6031upgrades in place
    Regression suite
    2,175 tests2,166 passed · 0 failed · 9 skipped
    Windows build
    6.3.0.39reproducible
    Installer SHA-256
    d9f1217375dd…70434fcbhMailServer-6.3.0-x64.exe
    Every file in 6.3.0

    Full SHA-256 of each file as recomputed on this server, with the signature bundles beside it. The SBOMs and provenance are not needed to install; they let anyone audit exactly what was built.

    FileWhat it isSizeSHA-256Signature
    hMailServer-6.3.0-x64.exeWindows installer · x6475.7 MBd9f1217375dd70d523ffb27dbdcd668dd525edc98edf64f00b4f69d070434fcb release notesSignature: cosign bundle
    hmailserver_6.3.0_amd64.debDebian package · x86-643.5 MB0ea011824f389978dbc09234b59e715631b561474e9eb58c0afbb369332d32fd SHA256SUMSSignature: cosign bundle
    hmailserver_6.3.0_arm64.debDebian package · AArch643.4 MBe848a9ef05f6699f706a4e19fbd960791d3b68a5b2ff78eb9027a8179e868c9c SHA256SUMSSignature: cosign bundle
    hmailserver-6.3.0-1.x86_64.rpmRPM package · x86-643.5 MBff79d4705be7e5f812ffd46ef7452923ef7db5f613924cbf950a67eb51f3be78 SHA256SUMSSignature: cosign bundle
    hmailserver-6.3.0-1.aarch64.rpmRPM package · AArch643.4 MB9b4ab11effd60ad8046c5d328887ceec3d488c2f151089813f836a098c1c9ac4 SHA256SUMSSignature: cosign bundle
    hMailServer-6.3.0-x86_64.AppImageAppImage · x86-649.5 MBade1e27e1721a1945207f0c89271bfba035228b3096bba8f43c1f1851d8059a1 SHA256SUMSSignature: cosign bundle
    hMailServer-6.3.0-aarch64.AppImageAppImage · AArch649.2 MB3a16b9557db1cf5cb98fdee89912d912c4b11bd8dc0c51a3f138124c2fc824d1 SHA256SUMSSignature: cosign bundle
    hmailserver-linux-6.3.0-SHA256SUMS.txtSHA-256 checksumsOne line per Linux package; feed it to sha256sum -c.584 bytes4407089ccf4a307276237748ee8f505efc8d94c34da576d3c1c011ee1055d86aSignature: cosign bundle
    hmailserver.spdx.jsonSBOM (SPDX)Every component in the build, in SPDX JSON.323 KB254afa133bea1e27c837ce26d92bbcb7d1c4f271fe46fc4578132c954b031afdSignature: cosign bundle
    hmailserver.cyclonedx.jsonSBOM (CycloneDX)The same inventory in CycloneDX JSON.198 KBa279bdbcf6fafd3b66326bcd2b5446d45f266fe48df8073643e4401acbee1e91Signature: cosign bundle
    Progressiverobot-hmailserver-attestation-46575548.sigstore.jsonBuild attestationGitHub artifact attestation for the release, as a Sigstore bundle.6 KB0c75412dcbba8b304dde7488d72d869b95ac28348ffe66dd440f7ae55bf8284c

6.2 series

10 releases + 6 pre-releases · 12 August – 8 September 2026 · Windows
  1. 6.2.28

    Signed 8 September 20261 package · schema 6031

    The largest release of this fork so far, and three of its four headline features are things hMailServer has never had: the server can update itself…

    Release notes, checksums and signatures
    Windows

    Windows 10 (version 1607 or later) and Windows 11, or Windows Server 2016 and newer; 64-bit only. Run the installer as an administrator: it upgrades any existing 5.x or 6.x installation in place.

    • hMailServer-6.2.28-x64.exex64 · 75.6 MB
      Download
      SHA-256fcc791855e9c…078d5054 Verified against release notesSignature: cosign bundle
    What’s new in 6.2.28

    The largest release of this fork so far, and three of its four headline features are things hMailServer has never had: the server can update itself, an account can use its own mailbox in a browser, and the Control Panel speaks seventeen languages. Underneath them is a real HTTP/1.1 server, which is what made the other two possible. Everything new here is off until it is turned on.

    Full release notes for 6.2.28 11 sections
    The server can update itself

    A release is noticed, verified and applied - by a click, or inside a window you choose. UpdateCheckEnabled=0 is the default and nothing happens at all until it is set: no request, no identifier, no configuration, no counts. Turned on, a scheduled task reads this project's release feed every UpdateCheckHours (24 by default) and the Status page reports what it found. Fetching an installer and applying it are two further, equally opt-in steps.

    What is applied is what this project signed. The installer is downloaded with the release's Sigstore bundle and verified before it is run: the certificate chains to Fulcio, the identity and issuer are this project's release workflow, the signature covers the file, and the entry is in the public transparency log. UpdateTrustRootsFile, UpdateSigningIdentity, UpdateSigningIssuer, UpdateSourceRepository and UpdateLogPublicKeyFile point that at a private Sigstore instance instead. This project's releases are not Authenticode-signed, so UpdateRequireAuthenticode=1 refuses every one of them; the Sigstore check is not optional and cannot be turned off.

    The apply survives its own failure. hMailServer.Updater.exe runs the installer with the service stopped, waits UpdateServiceWaitSeconds (180) for it to come back, and reinstalls the previous version if it does not; the outcome is reported at the next start. The database upgrade authenticates with a single-use token, revoked at every service start, rather than with the administrator password. UpdateBackupBeforeApply=1 means an unattended apply does not happen at all without a configured backup destination and a backup that succeeded.

    It works from behind a proxy. HttpProxy=host:port sends every web request this server makes as a client - the feed, its downloads, JWKS, token introspection - through a forward proxy: CONNECT for https with the TLS handshake inside the tunnel and the same certificate verification as a direct connection, the absolute URL for plain http. A proxy that refuses is reported by its own name and status line. No proxy credentials.

    Only from 6.2.28 onwards. The version you are reading about is the first to carry any of this, so the upgrade to it is manual for everybody, as every upgrade has been. Its own Updates card in the Control Panel is where the next one is turned on.

    An account can use its mailbox in a browser

    /portal on the REST listener, and /api/v1/me behind it. It answers to an account's own credentials and to nothing else: no administrator password, no API key. Signing in exchanges the password for a session cookie the server keeps, so the password is sent once. A second factor is asked for where the account has one.

    The page reads and writes the mailbox it belongs to: folders with their counts, a folder's messages with a cursor the keyboard moves, a message shown as it was sent - an HTML message inside a frame with an origin of its own, where nothing can reach the page around it - threads, flags, move, delete, marking in bulk, search over the account's own mail, attachments downloaded decoded under a type a browser will not execute, and shared and public folders alongside its own. It sends: a text or HTML message with attachments added the way the COM API adds one, every address put through the same checks a submitted message meets, drafts kept in the Drafts folder and replaced rather than duplicated, reply, reply-all, forward and Bcc.

    It also carries what a person would otherwise ask an administrator for: the account's own quarantine, with release and delete; its name, forwarding, signature and vacation reply; its active Sieve script; and a password change - which is why the Control Panel's password-expiry text now says an expired password is renewed by an administrator or by the person themselves if they hold an app password.

    A real HTTP server underneath

    The REST API and the web services (autoconfiguration, autodiscover, the mobileconfig profile, CalDAV and CardDAV redirects) ran on a single-threaded HTTP/1.0 loop written for a handful of administrative requests. They now run on HttpServer: HTTP/1.1 on Boost.Asio with its own io_context and four workers, keep-alive, chunked bodies, header and body limits, and separate header and body deadlines. Every API answer carries Cache-Control: no-store. This is what a browser talking to the portal actually needs, and it is why the portal exists.

    IMAP COMPRESS=DEFLATE (RFC 4978)

    A session compresses both directions on request, so headers, bodies and the FETCH traffic of a large mailbox go over the wire deflated. It is advertised until compression is on and refused afterwards, as the RFC requires, and STARTTLS is refused once a session is compressed.

    The Control Panel in seventeen languages

    Czech, Danish, German, Spanish, Finnish, French, Italian, Japanese, Norwegian bokmål, Dutch, Polish, Brazilian Portuguese, Russian, Swedish, Turkish, Ukrainian and Simplified Chinese, beside English: every page, dialog, message, hint and verdict sentence - 3,376 texts, not a subset. The language is chosen in the connect card and remembered per user. Three checkers keep it honest in CI: a new English caption fails the build until it is marked and translated in all seventeen, the catalogues are checked against each other for the server's own words, numbers and page names, and every [Settings] key the server reads is checked to have an editor.

    Administration in the browser

    The Control Deck - the page the REST listener serves at / - reads the server's settings, the log files with any file's last 200 or 2,000 lines, the certificates by name and file, and the global rules with their criteria and actions in evaluation order. Writing is still the desktop Control Panel's and COM's; the roadmap row says so. The page is served as the bytes on disk with a Content-Security-Policy that allows its own inline script and style and nothing else, nosniff and no-referrer.

    Fixed

    A masked password was typed backwards from the second character (#156). In the Control Panel with the password hidden, 12345678 became 18765432; revealed text was fine. The masked box rebuilt its mask on every change and then restored the caret to the index it had read before the keystroke - correct on the ordinary keyboard path, wrong on an IME commit and on some layouts, which is what the reporter had. Every password box in the Control Panel now places the caret from the change itself.

    Saving the SSL/TLS page dropped the post-quantum key exchange groups; saving the Scripting page took every COM object from every script. Two of the settings editors added for this release shipped without the server's absent-key default, and saving a page writes every field, so a blank was written where the server's own list belonged. Both now carry it, and both captions say what an empty value really does.

    Five defects in the live update, found by reading it back against the tree before it had ever shipped: an unvalidated release tag and installer name from the feed, the apply token readable by any account on the machine, old downloads never swept, and the token not revoked at start.

    The certificate renewal arithmetic that makes a 47-day certificate lifetime a non-event is pinned by tests rather than assumed.

    Ten defects in the portal page, found the same way: attachments carried on forward and edit-draft, reply-all addressing, Bcc, thread indentation, the message list's cursor after a refresh, and a sign-in prompt instead of silence when a session expires.

    Also in it

    The four open code-scanning alerts closed on their merits, and the C# tree's LINQ notes rewritten rather than dismissed. The repository's own documents and all 51 wiki pages read against the tree and corrected: routes, settings keys, schema and release stamps, two false paragraphs about the listeners, and two pages that did not render. A roadmap section for Linux and AArch64, written from a measurement of the tree - 603 translation units through one ATL precompiled header, no CMake anywhere, but 466 of 1,237 server sources with no Windows token at all - rather than from hope.

    Upgrading

    No schema change: 6031, as 6.2.27. The installer's database upgrade runs as usual and has nothing to do on a 6.2.27 database.

    Everything this release adds is off by default. UpdateCheckEnabled, RestApiPort (which the portal and the Control Deck need), IMAPCompressionEnabled and HttpProxy are all opt-in, and a server upgraded to 6.2.28 without touching its settings behaves exactly as it did on 6.2.27.

    Verification

    The full regression suite ran on the stamped binary: 2,127 tests, 2,119 passed, 0 failed, 8 skipped (7 explicit stress tests, 1 ignored where STARTTLS is not offered on the plain IMAP port of this bench). hMailServer.exe 6.2.28.38, SHA-256 9E89C1E3ABF4DD36560B0B5F3515BDA3DAB8B96F00EF35DA5376A9B60176BD15; installer hMailServer-6.2.28-x64.exe, SHA-256 FCC791855E9C1C4F3B2D3B8592B4D9CCBAC000DA866BBF8617561415078D5054. The database scripts build a 6031 database from the create script with all 65 schema probes passing, and the upgrade chain is contiguous and forward-only.

    Known and unfixed

    The Control Deck reads but does not write; a browser session for the administrator is still owed, so it holds the administrator password in sessionStorage while it is open. The regression suite runs on Windows only. The Linux and AArch64 section of the roadmap says what that would take.

    Release facts
    Version
    6.2.28
    Signed
    8 September 2026Sigstore transparency log
    Packages
    1Windows and Linux, x86-64 and AArch64
    Database schema
    6031upgrades in place
    Regression suite
    2,127 tests2,119 passed · 0 failed · 8 skipped
    Windows build
    6.2.28.38reproducible
    Installer SHA-256
    fcc791855e9c…078d5054hMailServer-6.2.28-x64.exe
    Every file in 6.2.28

    Full SHA-256 of each file as recomputed on this server, with the signature bundles beside it. The SBOMs and provenance are not needed to install; they let anyone audit exactly what was built.

    FileWhat it isSizeSHA-256Signature
    hMailServer-6.2.28-x64.exeWindows installer · x6475.6 MBfcc791855e9c1c4f3b2d3b8592b4d9ccbac000da866bbf8617561415078d5054 release notesSignature: cosign bundle
    hmailserver.spdx.jsonSBOM (SPDX)Every component in the build, in SPDX JSON.282 KBd4e2c326df4854544f4f41b78bc3e532ce6a108bab50795ec31e89ab69f55545Signature: cosign bundle
    hmailserver.cyclonedx.jsonSBOM (CycloneDX)The same inventory in CycloneDX JSON.172 KBfa8428523e8a272da59839ec74d14914be496ae58c6dc54158414e1fa5ed1841Signature: cosign bundle
    Progressiverobot-hmailserver-attestation-45895090.sigstore.jsonBuild attestationGitHub artifact attestation for the release, as a Sigstore bundle.4 KBc9c7ee1acefb67be330290c6a5b27c4a00c1e7b66bf9d735c36f99f8c512f6ce
  2. 6.2.27

    Signed 7 September 20261 package · schema 6031

    Two roadmap items that had been open since the roadmap was written, and one small schema step…

    Release notes, checksums and signatures
    Windows

    Windows 10 (version 1607 or later) and Windows 11, or Windows Server 2016 and newer; 64-bit only. Run the installer as an administrator: it upgrades any existing 5.x or 6.x installation in place.

    • hMailServer-6.2.27-x64.exex64 · 73.7 MB
      Download
      SHA-256f60d3c30f27a…9227c1a6 Verified against release notesSignature: cosign bundle
    What’s new in 6.2.27

    Two roadmap items that had been open since the roadmap was written, and one small schema step: the validating resolver now proves a missing DS record missing rather than assuming it, and an external IMAP account can mirror every folder of a remote mailbox - the migration route the project never had. The 6.2.26 notes and the 6.2.25 notes still describe everything else.

    Full release notes for 6.2.27 5 sections
    Security

    A missing DS is proved missing, not assumed (RFC 4035 §5.2, RFC 5155 §8). The DNSSEC validator that guards DANE and the SPF, DKIM and DMARC lookups treated a DS query answered with nothing as an unsigned delegation - which is exactly what an attacker who strips the DS from the answer on the way shows a resolver, and a signed zone quietly became an unsigned one, DANE and validated TXT gone with it. The resolver now keeps the authority section of a negative answer and requires the parent's proof there: an NSEC at the delegation name with NS set and DS clear, an NSEC3 whose hashed owner matches with the same bits, or an Opt-Out NSEC3 covering the hash, each signed by the parent's key. A proof that fails to verify, has expired, claims a DS exists, or belongs to another name is no proof, and a delegation without one under a signed parent is Bogus - blocked, as a forged chain is - rather than Insecure. An unsigned parent still makes everything below it Insecure, as it should, and a resolver that cannot be reached still degrades to Insecure rather than lying. Twelve tests run the resolver against a chain the suite signs itself, through the new Diagnostics.DnssecChainStatus.

    Mail flow

    An external IMAP account can mirror every folder (FetchAccount.MirrorFolders, schema 6031; in the Control Panel, Mirror every folder on an IMAP external account). With it on, the server lists the remote mailboxes and collects each into the local folder of the same name: every message byte for byte, its \Seen \Flagged \Answered \Draft \Deleted flags, its internal date, the remote hierarchy delimiter mapped to the local one. Nothing is delivered - no header, rule, anti-spam or anti-virus touches a copy - because it is mail the other server had already accepted. Each folder keeps its own record of what has been collected, so a second poll takes only what is new, and days to keep messages 0 makes it a move, folder by folder. This is the imapsync shape: the way to bring a mailbox from another IMAP server, or from a hosted service that is switching Basic authentication off, into this one.

    The Import Tool reads a Maildir. A second button takes a Maildir - the directory with cur, new and tmp - and imports the INBOX and every Maildir++ folder beside it, with the flags the file names carry (;2, and !2, accepted as well as :2,, since a colon cannot be in a Windows file name) and line endings made CRLF.

    Migration.md is the guide the roadmap asked for: from another IMAP server, from mbox, from a Maildir, from Outlook (through IMAP - PST is deliberately not parsed, and the page says why), from the upstream hMailServer in place, and accounts in bulk; what each route keeps and what it does not.

    A message imported into a folder a client has open appears at once. The Import Tool's import told nobody that the folder had changed, so a session with the folder's message list cached saw the copy only after a restart. The folder is now refreshed, as it is after a delivery.

    Also in it

    Four roadmap rows brought up to date after 6.2.26, and the create-script check and the DBUpdater's message record what #114 taught.

    Upgrading

    Schema 6030 → 6031: one column on hm_fetchaccounts, added by the installer's database upgrade on every backend; the SQL Server Compact verification that 6.2.25 got wrong was fixed in 6.2.26 and is proved on a fresh database before every release. From 6.2.25 or 6.2.26: run the installer. From earlier: the 6.2.25 and 6.2.24 notes describe what changes on the way.

    Verification

    Three clean Release builds of the stamped source produced a byte-identical hMailServer.exe 6.2.27.37 (SHA-256 8CD745C49657A2E02989A8A86E9290C0AA49D317466B860C3749C757C129410F). The full regression suite ran on the assertion-enabled build of this source and again on the shipping binary: 2024 tests, 2017 passed, 0 failed, the 7 explicit skips, both times. The Control Panel's tests passed. hMailServer-6.2.27-x64.exe is 77,237,885 bytes, SHA-256 F60D3C30F27AF9EDC18BEE295DA231C666E826C1436E2214593B33499227C1A6; its Sigstore bundle and the two SBOMs are attached below, signed by this repository's sign-release workflow.

    Release facts
    Version
    6.2.27
    Signed
    7 September 2026Sigstore transparency log
    Packages
    1Windows and Linux, x86-64 and AArch64
    Database schema
    6031upgrades in place
    Windows build
    6.2.27.37reproducible
    Installer SHA-256
    f60d3c30f27a…9227c1a6hMailServer-6.2.27-x64.exe
    Every file in 6.2.27

    Full SHA-256 of each file as recomputed on this server, with the signature bundles beside it. The SBOMs and provenance are not needed to install; they let anyone audit exactly what was built.

    FileWhat it isSizeSHA-256Signature
    hMailServer-6.2.27-x64.exeWindows installer · x6473.7 MBf60d3c30f27af9edc18bee295da231c666e826c1436e2214593b33499227c1a6 release notesSignature: cosign bundle
    hmailserver.spdx.jsonSBOM (SPDX)Every component in the build, in SPDX JSON.282 KB877d2a5235e80ea0147993cd1104c395b924938a99bd735997400c2d42160658Signature: cosign bundle
    hmailserver.cyclonedx.jsonSBOM (CycloneDX)The same inventory in CycloneDX JSON.172 KB83974104008cfda45ddd1f316644354cab6f6b18b9c8bde9779c4a5c5cf901bbSignature: cosign bundle
    Progressiverobot-hmailserver-attestation-45630648.sigstore.jsonBuild attestationGitHub artifact attestation for the release, as a Sigstore bundle.4 KBdedf3edd0687ec7242d109fc284896d4d7f20bb8a49aa0a563e36c1bc1b5fe4c
  3. 6.2.26

    Signed 6 September 20261 package · schema 6030

    The release to take if you run the built-in SQL Server Compact database: the 6.2.25 installer reported every upgrade of one as failed…

    Release notes, checksums and signatures
    Windows

    Windows 10 (version 1607 or later) and Windows 11, or Windows Server 2016 and newer; 64-bit only. Run the installer as an administrator: it upgrades any existing 5.x or 6.x installation in place.

    • hMailServer-6.2.26-x64.exex64 · 73.6 MB
      Download
      SHA-256bfb1e5d606d3…91905f1e Verified against release notesSignature: cosign bundle
    What’s new in 6.2.26

    The release to take if you run the built-in SQL Server Compact database: the 6.2.25 installer reported every upgrade of one as failed, after the upgrade had in fact succeeded, and the check that gave the false verdict took the service down with it (#114). Nothing else in the server changes; the 6.2.25 notes still describe everything it does.

    Full release notes for 6.2.26 4 sections
    The fix

    Every SQL Server Compact upgrade through schema 6030 was reported as failed after it had succeeded (#114). After each upgrade script, the database updater proves the schema changed by running a probe statement, and reads a failed probe as a missing object. The four probes for schema 6030, the foreign keys, were written with case when exists (subquery) in the SET expression - valid on SQL Server, MySQL and PostgreSQL, and on SQL Server Compact an access violation inside the OLE DB provider, on a correct database with every constraint present. The server caught the fault and reported it as HM10045 Unknown error, the updater declared that Upgrade6029to6030MSSQLCE.sql had not created fk_hm_accounts_domain and blamed an [IGNORE-ERRORS] marker the statement does not carry, and the installer said the database could not be upgraded. Two seconds after answering the probe, the server's crash oracle - which treats a memory-safety fault as fatal - ended the service, and service recovery started it again.

    Reproduced from a Compact Edition database created at schema 6011 and upgraded with the shipped scripts: it reaches 6030 with all seventeen foreign keys, and the probe then faults the provider. The probes now read update hm_dbversion set value = value / (value - value) where not exists (select 1 from information_schema.table_constraints where constraint_name = '...' and constraint_type = 'FOREIGN KEY'): with the constraint present no row matches and nothing is evaluated; with it absent the one row matches and the division by zero fails the statement on every backend, leaving hm_dbversion untouched. The updater's message no longer asserts a cause it cannot see; it gives the backend's own words and says how to read them.

    So that a probe the backend cannot run is caught before a release: build/check-db-scripts.ps1 now executes every probe against the database it builds from the create script, through the provider the server uses, with a negative control that must fail; and a regression fixture runs them through the same COM path the updater takes, against the bench database, which is Compact Edition. Both fail on the 6.2.25 statement.

    If the 6.2.25 installer failed on your database, it is at schema 6030 with its foreign keys in place, and this installer finds nothing left to upgrade. If you restored a backup from before, the whole chain runs and the verification passes.

    Also in it

    Two roadmap rows closed on what 6.2.25 did (the fuzz run and the assertion-build gate a release records, and the accessibility audit), and the native-coverage rows now say why they are blocked rather than merely open.

    Upgrading

    From 6.2.25: run the installer. From 6.2.24 or earlier: the 6.2.25 notes and, before them, the 6.2.24 notes describe what changes on the way; everything there still applies.

    Verification

    Two clean Release builds of the stamped tree produced the same hMailServer.exe byte for byte (SHA-256 89E72B5B143FD0B9AFD20A4A7848AB1F746E58582BF015238147B74F352A5329); the full regression suite ran on the assertion build (every HM_ASSERT live: 2004 tests, 1997 passed, 0 failed, the 7 explicit skips, no assertion violated) and again on the shipping binary (2004, 1997 passed, 0 failed, 7 skipped), with the three new probe tests in both runs; the Control Panel's own tests passed; the database checks ran all 64 schema probes against a freshly created database with the negative control failing as it must. Installer hMailServer-6.2.26-x64.exe SHA-256 BFB1E5D606D3FB18B2BBBE26A8704A29033C807E293988265F47E75891905F1E; the SBOM and the cosign signatures attached to this release were produced and verified by the workflows after the tag.

    Release facts
    Version
    6.2.26
    Signed
    6 September 2026Sigstore transparency log
    Packages
    1Windows and Linux, x86-64 and AArch64
    Database schema
    6030upgrades in place
    Installer SHA-256
    bfb1e5d606d3…91905f1ehMailServer-6.2.26-x64.exe
    Every file in 6.2.26

    Full SHA-256 of each file as recomputed on this server, with the signature bundles beside it. The SBOMs and provenance are not needed to install; they let anyone audit exactly what was built.

    FileWhat it isSizeSHA-256Signature
    hMailServer-6.2.26-x64.exeWindows installer · x6473.6 MBbfb1e5d606d3fb18b2bbbe26a8704a29033c807e293988265f47e75891905f1e release notesSignature: cosign bundle
    hmailserver.spdx.jsonSBOM (SPDX)Every component in the build, in SPDX JSON.255 KB8b265676f3e7d6a1b860688d84ffc6470d24574601dcddb880bcf8c8864cec74Signature: cosign bundle
    hmailserver.cyclonedx.jsonSBOM (CycloneDX)The same inventory in CycloneDX JSON.155 KB1f4a4cc60ff256d9341e5f6d52044a41a7be098ea98c5989b2783c2b745c9b26Signature: cosign bundle
    Progressiverobot-hmailserver-attestation-45602849.sigstore.jsonBuild attestationGitHub artifact attestation for the release, as a Sigstore bundle.4 KB36f605c1970d97e6311238d38295048fcafcba26ae6da29e4b4c7408135d6e0a
  4. 6.2.25

    Signed 6 September 20261 package · schema 6030

    Two days after 6.2.24, and the release to take if you use the built-in ACME client: 6.2.24 ends the service the moment a certificate has been issued, and at the start of every renewal (#93).

    Release notes, checksums and signatures
    Windows

    Windows 10 (version 1607 or later) and Windows 11, or Windows Server 2016 and newer; 64-bit only. Run the installer as an administrator: it upgrades any existing 5.x or 6.x installation in place.

    • hMailServer-6.2.25-x64.exex64 · 73.6 MB
      Download
      SHA-256922178fa0715…b4bbadfcSignature: cosign bundle
    What’s new in 6.2.25

    Two days after 6.2.24, and the release to take if you use the built-in ACME client: 6.2.24 ends the service the moment a certificate has been issued, and at the start of every renewal (#93). It also carries the sixty commits that landed in those two days: IMAP fetching of external accounts, message retention, an archive index with legal hold, a week of metric history, SASL EXTERNAL, scrypt, tarpits, a server-enforced second factor on the administrator credential, the REST API reaching what was COM-only, pipelined and chunked delivery, OpenSSL 4.0.2, and a code-scanning page with nothing on it.

    Full release notes for 6.2.25 15 sections

    If you are on 6.2.21 or a 6.2.22/6.2.23 pre-release, read the 6.2.24 notes first; everything there still applies, and the upgrade section below starts from 6.2.24.

    The fix to upgrade for

    ACME issuance and renewal ended the process (#93). Two calls in the ACME client handed the OpenSSL DLL a FILE* opened by the server's own C runtime: the one that computes the DANE TLSA line logged straight after a certificate has been issued, and the one that re-reads the existing private key at the start of every renewal (the default, AcmeReuseKey). On Windows the OpenSSL DLL does not read such a FILE* itself; it routes every stdio call back into the executable through an OPENSSL_Applink export the executable has to provide, and this executable never has. OpenSSL's answer to the missing export is not an error return: it writes "OPENSSL_Uplink(...): no OPENSSL_Applink" to the Windows Application log under the source "OpenSSL" and calls TerminateProcess. That is the whole picture the reporter saw - an OpenSSL event with a message that looks blank, a 7031 from the service control manager in the same second, no crash dump, and no "ACME (automatic)" certificate record because the TLSA line ran before the deployment.

    Both calls now go through OpenSSL's own file I/O, as every other file the ACME client touches already did. The deployment runs before the TLSA line, so nothing optional stands between a renewal and the restart it exists for. A class test in the server's self-test computes the TLSA of a fixture certificate the way the old code would have, so the crash cannot come back unnoticed.

    If 6.2.24 issued you a certificate before it died, the files under Data\ACME are valid, and 6.2.25 deploys them at its first ACME check after start-up: a certificate pair on disk that no "ACME (automatic)" record names is deployed once, the way a fresh issuance is - the record, the assignment to TLS ports that have none, the restart. The application log says "issued but never deployed" when that happens.

    Found on the way, and fixed in the same release: two restarts of the servers at once - one asked for over COM, one started by an ACME deployment or a backup restore - tore down and rebuilt the same queues under each other and could end in an access violation. Restarts now run one after the other.

    What is in it since 6.2.24
    Mail flow

    External accounts over IMAP. FetchAccount.ServerType 1 collects a remote INBOX once by UID - LOGIN or XOAUTH2, STARTTLS or TLS as the account says - and leaves it on the server unless DaysToKeepMessages says otherwise. Everything after the download is the POP3 fetcher's code. The INBOX only; folders are not mirrored. Message retention (schema 6027). Domain.MessageRetentionDays and Account.MessageRetentionDays remove delivered mail older than the policy, in every folder, through the same deletion an EXPUNGE performs, by stored creation time and never by the sender's Date header. Off everywhere until a number is set. The archive has an index (schema 6029): one row per copy, searched over COM and GET /api/v1/archive, a legal hold that survives the retention sweep and an address erasure. ArchiveDomains scopes the archive to the domains it is kept for. Outbound delivery pipelines the envelope and sends the message as one BDAT chunk when the remote advertises PIPELINING and CHUNKING (OutboundPipelining, OutboundChunking, both on; either can be turned off). A binary message is now relayed as it is to a remote that accepts BINARYMIME. Delivery hard links (DeliveryHardLinks, off): a message to several local recipients can be one file with a name in each folder, and every rewrite of a message file is a temporary file renamed into place. SEARCH BODY and TEXT read the text-bearing attachments, and the full-text index tokenises them, so a phrase that lives only inside an attached text, HTML or CSV file is found - the same with the index on and off. ManageSieve advertises envelope, which had shipped without its capability line. A Message-ID is added only for submissions (upstream #552): the client authenticated, or sending as one of our domains from a range that does not require it to. Relayed mail keeps its headers as they are, so SpamAssassin's MISSING_MID and a rule looking for the header see the truth. A stranded fetch account (upstream #603): a fetch that threw, or a database error recording the next try, left the account locked and silently skipped until a restart. The task owns the lock now.

    IMAP

    Every session numbers its selected folder for itself (upstream #602). Another session's expunge no longer renumbers a client's messages under it; a message found gone is answered NO [EXPUNGEISSUED], and QRESYNC sessions hear VANISHED. Sessions stop sharing unguarded state (upstream #580, #566): one lock on the connection, serialised lazy collections, snapshots for cross-thread readers. One strand per connection. A write from another thread - a notification to an idling IMAPS session - can no longer run inside the SSL engine while that connection's own read is mid-decrypt. Default special-use folders at account creation (CreateDefaultSpecialUseFoldersEnabled, off; schema 6026): Drafts, Sent, Trash and Junk with their RFC 6154 designation stored on the row. One authorisation choke point. Every "may this account do this to that folder" is one decision, delivery and rules included, and a CI guard fails any second decision-maker.

    Security and authentication

    SASL EXTERNAL. 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 - on SMTP, IMAP and POP3. Found on the way: the AUTH line limit was 500 octets on POP3 and 510 on SMTP, so a real RS256 bearer token had never fitted; it is 12288 now, RFC 5034's figure. OAuth2: the provider's JWK Set as the signing-key source (OAuth2JwksUrl, rotation handled) and token introspection for revocation (OAuth2IntrospectionUrl). Both off unless configured. scrypt (PreferredHashAlgorithm=7, OWASP parameters), and schemes compared by strength rather than by number, so a preference for scrypt leaves Argon2id accounts alone and a minimum of either accepts both. The password-hash work factor is a setting (PasswordHashIterations, PasswordHashMemoryKB, PasswordHashTimeCost; upstream #554), and a hash derived under a cheaper one is re-derived at the next logon. Upward only. Tarpitting on failed logons and on stranger recipients (LogonTarpitSeconds; SmtpTarpitCount and SmtpTarpitDelaySeconds; all off), as a pause on the connection's own timer, never a thread asleep - which is why the two old COM properties were stubs for years. The administrator's second factor is the server's now. With a TOTP secret enrolled (Settings.EnrolAdministratorTOTP), COM needs AuthenticateWithCode and the REST API needs an X-hMailServer-OTP header; a stolen administrator password is no longer a complete credential. An authenticated session may only send as an address its account owns or has been granted (SmtpAuthenticatedSenderCheck, off), and the post right on a mailbox's INBOX is the Send-As grant. Event scripts can create only the COM classes ScriptAllowedObjects names. The default is *, which is what every earlier version did; set it to a list and CreateObject of anything else fails inside the script with error 429. The Apple configuration profile is served over HTTPS only (see the upgrade section).

    Anti-spam and anti-virus

    spamd is told whose preferences to apply (SpamAssassinUser, SpamAssassinUserFromRecipient; off), and moving a message into the Junk folder teaches SpamAssassin it is spam, out of it teaches ham (SpamAssassinLearnOnMove, off) through spamc's TELL. The ClamAV connection test asks PING and VERSION first, names the daemon it reached, and streams its EICAR sample from memory - the button had failed on every Windows with real-time protection on, because Defender removed the sample file before clamd saw it.

    Administration

    The REST API reaches what was COM-only: IP ranges, distribution lists, certificates (never the private-key password), DKIM, the global rules, the logs, the backup and a settings snapshot, each decided at the same authorisation choke point. Writing settings, rules, certificates and DKIM stays COM-only, on purpose. The server remembers its metrics (schema 6028): one row per metric per minute, kept a week (MetricsHistoryDays), read back over COM and GET /api/v1/metrics/history, shown on the dashboard for a day, a week or a month. Every backup proves its mail can be got back out (BackupVerifyRestore, on): the archive's message store is extracted to a scratch directory through the same code a restore runs, before the backup is called complete. Control Panel: follows Windows High Contrast for every control; every static caption carries an Alt-key mnemonic, checked in CI; page titles are headings and status text is 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". The data directory synchronizer walks the public folders (upstream #601), and the importer refuses a file under #Public the database does not reference rather than guessing where it belongs.

    Databases

    TLS to PostgreSQL and MS SQL is a line in hMailServer.ini: PostgreSQLSslMode and PostgreSQLSslRootCert under [Database], ConnectionStringOptions appended to the OLE DB connection string. An unknown sslmode refuses the connection with the reason rather than falling back to "prefer". A plaintext MySQL is an ini choice rather than a silent downgrade (upstream #559): AllowUnencryptedConnection=1 lets the client fall back from TLS; the default still refuses a server without it. The schema enforces its own parent-child relationships (schema 6030): seventeen FOREIGN KEYs with ON DELETE CASCADE, so a crash mid-delete can no longer leave rows behind.

    Platform and hardening

    OpenSSL 4.0.2 (a security patch release: eleven CVEs, the most severe Moderate) and Boost 1.92.0, built with the tree's own scripts. The fourteen open CodeQL C++ findings are dealt with, each on its merits: a byte buffer copied rather than reinterpreted, the Windows 2000 address workaround retired, the SPF address copy a memcpy, the Apple profile served over HTTPS only and its handler no longer named in a way the private-data heuristic reads as a phone number; and the ten boost::asio TLS-settings findings dismissed as false positives with the reason on record - the query wants every set_options call reachable from a context's constructor to carry the protocol flags as a constant, and Boost's own constructor makes such a call with no_compression, so no program built against a real Boost can satisfy it (verified with the query's own predicates against this tree). Every context is nonetheless built with one constant floor - SSLv2, SSLv3, TLS 1.0 and TLS 1.1 off - and the administrator's protocol toggles act on the mail listeners and the mail client afterwards. The static-analysis backlog is triaged: a repeatable /analyze build, 632 findings read, the defect-class ones in the project's own code fixed - among them a service-dependency buffer sized in bytes for a function that counts characters - and the rest named with the reason they stay. A Release build that keeps its assertions (build.ps1 -Asserts) is the dynamic-analysis build the suite runs against before a release; a violated assertion is reported as HM6364 naming the expression, file and line. It is never the binary that ships. A timed fuzz run is a release step, and the C++ tree is spaces, checked. The fuzz harness had not built since 13 August (the MIME parser had gained two dependencies the harness shim did not supply, and the harness's MSVC-environment check was fooled by Git for Windows' link.exe); it builds again, and this release's run is below.

    Build, scanning and supply chain

    The C++ server is compiled on a GitHub-hosted runner on every push and pull request, with OpenSSL, Boost and libpq built from pinned, digest-verified sources and cached - and the weekly C++ CodeQL analysis runs, which closes the second known limitation of 6.2.24. The Scorecard result is published to the OpenSSF API, so the badge and the viewer it links to answer; the Code Quality page grades the C# tree Excellent; NuGet lock files hold every restore. Twelve upstream ports in all (#550, #552, #554, #559, #566, #580, #592, #601, #602, #603 and two smaller ones).

    Upgrading from 6.2.24

    The database schema moves from 6025 to 6030 in five steps, one way. DBUpdater runs them in order and resumes from wherever a partial upgrade stopped. There is no downgrade.

    StepWhat it does
    6025 → 6026One hm_settings row for the default special-use folders switch.
    6026 → 6027Two retention columns, on domains and accounts. Both 0, no policy.
    6027 → 6028Creates hm_metricsamples. Empty; the sampler fills it a row per metric per minute from the first start.
    6028 → 6029Creates hm_archiveindex. Empty; only copies made from now on are indexed. (The release check found this step's four statements written as one command, which SQL Server Compact and MySQL refuse; they are four commands in this release. Nobody ran the step before 6.2.25, since 6.2.24 was schema 6025.)
    6029 → 6030Adds seventeen FOREIGN KEY constraints and removes the orphan rows they would refuse (the create script had them in a spelling Compact Edition lacks; the release check caught that too, and a fresh Compact Edition install builds a complete schema 6030 database) - rows that name an account, domain, list, route or fetch account that no longer exists, which only a crash between a parent's delete and its children's could have left. On a large database this step reads every child table once; plan for it the way you would for an index build.
    Behaviour that changes on upgrade, without a switch

    The Apple .mobileconfig profile is served over HTTPS only. It tells a device which servers to trust with the user's password, and a profile fetched over plain HTTP can be rewritten on the way by anyone on the path. A TLS-terminating proxy in front of the web-services listener says so with X-Forwarded-Proto: https; without that, a plain-HTTP request is answered with a 301 to the same URL on the WebServicesHttpsPort listener when one is configured, and with a 403 that says why when none is. The Thunderbird autoconfig and Outlook autodiscover documents are unchanged. A Message-ID is no longer added to relayed mail, only to submissions (authenticated, or from a local domain on a range that does not require authentication). Nothing in the server needed the header to be there; a downstream filter that counts on every message having one will now see the ones that arrived without. With IMAP ACL enforcement off, a rule or a delivery into a public folder is allowed - as every IMAP command already allowed it. It used to be refused for want of ACL rows nobody was enforcing. The four HTTPS clients - the ACME client, the OAuth2 token client, the MTA-STS policy fetch and the introspection/JWKS client - require TLS 1.2 or later whatever the mail protocol toggles allow. Mail delivery and the listeners follow the toggles as before. The AUTH command and the SASL responses that follow it accept lines of up to 12288 octets on SMTP and POP3 (they were 510 and 500). Every other line keeps its limit. IMAP sequence numbers are stable within a session (upstream #602). A client that relied on the old behaviour - a message expunged by another session shifting its numbers at once - was relying on a violation of RFC 3501. Account.DeleteMessages empties a designated special-use folder and keeps it, the way it has always kept the inbox; IMAP DELETE and IMAPFolder.Delete still delete one. A DANE TLSA line that cannot be computed after an ACME issuance is logged as such rather than being silently absent; the certificate is deployed first either way.

    New settings, every one of them off or inert until you set it (all in hMailServer.ini unless named otherwise; the Control Panel carries each): DeliveryHardLinks, SpamAssassinLearnOnMove, SpamAssassinUser, SpamAssassinUserFromRecipient, SmtpAuthenticatedSenderCheck, LogonTarpitSeconds, SmtpTarpitCount, SmtpTarpitDelaySeconds, PasswordHashIterations, PasswordHashMemoryKB, PasswordHashTimeCost, OAuth2JwksUrl and its cache seconds, OAuth2IntrospectionUrl and its client credentials, cache seconds and fail-open switch, ArchiveDomains, PostgreSQLSslMode, PostgreSQLSslRootCert, ConnectionStringOptions, AllowUnencryptedConnection, [Security] AdministratorTotpSecret (written by enrolment, cleared to recover), Settings.CreateDefaultSpecialUseFoldersEnabled, Domain.MessageRetentionDays, Account.MessageRetentionDays, FetchAccount.ServerType. Three ship on: OutboundPipelining, OutboundChunking (each used only when the remote advertises the extension) and BackupVerifyRestore. MetricsHistoryDays defaults to 7 and ScriptAllowedObjects to *, which is what earlier versions did without a setting.

    COM clients. The type library gained members (Utilities.SearchArchive, SetArchiveHold, GetMetricHistory, SampleMetricsNow, RunMessageRetention; Settings.EnrolAdministratorTOTP and DisableAdministratorTOTP; Application.AdministratorTOTPEnabled; COMAuthentication.AuthenticateWithCode; Domain and Account MessageRetentionDays; Diagnostics.AssertionsEnabled). Clients compiled against the 6.2.24 type library keep working; recompile to reach the new members. FetchAccount.ServerType was always there; 1 now means something.

    Known limitations

    Verified by reading, not on a live server: the PostgreSQL and MS SQL TLS keys, and AllowUnencryptedConnection for MySQL. The regression bench runs SQL Server Compact. IMAP external accounts collect the INBOX only. Other folders are not mirrored; a message that leaves the remote INBOX by other means is forgotten, not deleted locally. Statement timeouts were built for PostgreSQL and MySQL and have been exercised only against SQL Server Compact (carried from 6.2.24). SCRAM-SHA-1 is not offered (#49, with the reason); clients without SCRAM-SHA-256 use PLAIN or LOGIN over TLS (carried from 6.2.24). The fuzz harnesses cover the MIME parser and its codecs. The IMAP command parser, the SPF evaluator and the Sieve parser are exercised by the regression suite, not by a fuzzer.

    The second known limitation of 6.2.24 - the weekly C++ CodeQL analysis not running - is resolved: it runs on a GitHub-hosted runner, and the code-scanning page is clear. Downloads and verification

    AssetWhat it is
    hMailServer-6.2.25-x64.exeThe installer (Inno Setup). Not Authenticode-signed.
    *.spdx.json, *.cdx.jsonSBOMs, SPDX and CycloneDX, covering the .NET and native dependencies.
    *.cosign.bundleA Sigstore bundle per asset, keyless, bound to this repository's workflow identity.
    Verify the tag, from any clone

    git -c gpg.ssh.allowedSignersFile=.github/allowed_signers verify-tag v6.2.25

    Verify an asset with cosign

    cosign verify-blob --bundle hMailServer-6.2.25-x64.exe.cosign.bundle \ --certificate-identity-regexp '^https://github\.com/Progressiverobot/hmailserver/' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ hMailServer-6.2.25-x64.exe

    The server binary is reproducible: two clean Release builds of this tag produce a byte-identical hMailServer.exe, SHA-256 4bfae5c7d9721cc4b2408f2512723369c0156ab445848620b854357a439b3345 (three clean builds, one before and one after the assertion build, all identical), with the v145 toolset (MSVC 14.51), Windows SDK 10.0.26100 and the library layout in README.md. The regression suite on that binary: 2001 tests, 1994 passed, 0 failed, the 7 explicit skips, 34 minutes. The same suite on the assertion build (build.ps1 -Asserts, every HM_ASSERT live and reported): 2001 tests, 1994 passed, 0 failed, the 7 explicit skips, no HM6364. Control Panel unit suite: 667/667. Fuzzing on this source (libFuzzer with ASan, LLVM 22.1.8, harnesses rebuilt from this tree): mime_message_fuzzer 265,770 executions, mime_header_fuzzer 274,986, mime_decode_fuzzer 4,793,507, thirty minutes each, no crash, no hang, no out-of-memory, no assertion.

    Release facts
    Version
    6.2.25
    Signed
    6 September 2026Sigstore transparency log
    Packages
    1Windows and Linux, x86-64 and AArch64
    Database schema
    6030upgrades in place
    Installer SHA-256
    922178fa0715…b4bbadfchMailServer-6.2.25-x64.exe
    Every file in 6.2.25

    Full SHA-256 of each file as recomputed on this server, with the signature bundles beside it. The SBOMs and provenance are not needed to install; they let anyone audit exactly what was built.

    FileWhat it isSizeSHA-256Signature
    hMailServer-6.2.25-x64.exeWindows installer · x6473.6 MB922178fa07151725895b6ed3e606f4f06bf44d4014fb7c1fa5b13c8cb4bbadfcSignature: cosign bundle
    hmailserver.spdx.jsonSBOM (SPDX)Every component in the build, in SPDX JSON.255 KB61157c244a0ef6a9f09ef4a5155533ffefed0ab182b7836c90fab2805fdd2fc5Signature: cosign bundle
    hmailserver.cyclonedx.jsonSBOM (CycloneDX)The same inventory in CycloneDX JSON.155 KB8777d07c2a515d5a363c1924c64d3e796e81033279ce0b873a042880c04b6bd4Signature: cosign bundle
    Progressiverobot-hmailserver-attestation-45552086.sigstore.jsonBuild attestationGitHub artifact attestation for the release, as a Sigstore bundle.4 KB9fc028f18f35b1da482167183031b231ca7f64a64c4eb062dc5d098429921f42
  5. 6.2.24

    Signed 4 September 20261 package · schema 6025

    The first stable release since 6.2.21. It is the code of 6.2.23 Alpha 2, cut the same day, plus NuGet lock files for the .NET tools and the CI changes that hold every restore…

    Release notes, checksums and signatures
    Windows

    Windows 10 (version 1607 or later) and Windows 11, or Windows Server 2016 and newer; 64-bit only. Run the installer as an administrator: it upgrades any existing 5.x or 6.x installation in place.

    • hMailServer-6.2.24-x64.exex64 · 73.5 MB
      Download
      SHA-256f1e0d0e5a756…0f92a947Signature: cosign bundle
    What’s new in 6.2.24

    The first stable release since 6.2.21. It is the code of 6.2.23 Alpha 2, cut the same day, plus NuGet lock files for the .NET tools and the CI changes that hold every restore to them. Nothing that ships changed between the two; the alpha's regression gate stands, and this release ran the full gate again on its own stamped binary. Schema 6025, unchanged since Alpha 1.

    Full release notes for 6.2.24 7 sections

    If you are on 6.2.21, everything the two pre-release lines introduced arrives at once. The section after next is written for you.

    What is in it since 6.2.21

    Two pre-release lines, 6.2.22 (pre1 to pre6) and 6.2.23 (Alpha 1 and 2), carried the work. Their notes are the detailed record - 6.2.22 pre-releases, Alpha 1, Alpha 2 - and this is the shape of it:

    Mail that was silently lost is not any more. A delegated APPEND, COPY or MOVE filed bytes under the wrong account; a MOVE destroyed the only readable copy. Thunderbird's every Sent copy was refused once UTF8=ACCEPT was enabled (#53). A kept-open log was never flushed (#33). The ACME client had never worked against real Let's Encrypt (#34). Anti-spam bypasses closed. A sender could steer a per-account spam override into un-marking their own mail by forging a header; a sender could choose which address the DNSBL, SPF and HELO tests ran against through the HELO literal in the Received header; RSET before EHLO or STARTTLS without a fresh EHLO opened a transaction with the HELO host empty, skipping the HELO-host test and the script events. Features. Shared and delegated IMAP mailboxes, full-text search, RFC 3464 machine-readable bounces, BINARYMIME, per-account spam thresholds, a sender blacklist, domain-wide out-of-office, distribution-list moderation, Windows Event Log integration, OTLP metrics and logs, a reverse-proxy PROXY-protocol front for SMTP, an external HTTP filter hook, eleven IMAP extensions (APPENDLIMIT, LITERAL-, LIST-STATUS, UNAUTHENTICATE, QUOTA, PREVIEW, MULTIAPPEND, REPLACE, SAVEDATE, METADATA, BINARY), Microsoft 365 XOAUTH2 in both directions, app passwords, TOTP for the Control Panel, a quarantine, password history, message trace, per-domain outbound relay, a disk-space floor, and a substantial Control Panel overhaul. Hardening. Control Flow Guard on the server; a reproducible build (two clean builds give the same bytes); signed release tags; SBOMs and Sigstore bundles on every release; a COM vtable layout repaired to be binary-compatible with 6.2.21; an unattended install that fails its database step now exits non-zero; an unattended upgrade can no longer hang on a password dialog.

    Upgrading from 6.2.21

    The database schema moves from 6011 to 6025 in fourteen steps, one way. DBUpdater runs them in order and resumes from wherever a partial upgrade stopped. There is no downgrade: an older server refuses a newer database rather than misreading it, so take a database backup and a data-directory backup before you install, with your backend's own tools. Rollback is only as good as that snapshot.

    Two of the steps are worth planning for:

    StepWhat it does
    6024 → 6025Rewrites hm_messages.messageflags from tinyint to smallint on MS SQL, SQL Server Compact and MySQL/MariaDB - a table-rewriting operation on the largest table you have, holding locks for its duration. Size the maintenance window by your message count. PostgreSQL is unaffected (already smallint).
    6022 → 6023Creates the full-text index tables. Empty and inert until you enable indexing; costs nothing on upgrade.
    Behaviour that changes on upgrade, without a switch

    A client that sends MAIL FROM or AUTH straight after the STARTTLS handshake, without saying EHLO again, now receives 503 Bad sequence of commands. RFC 3207 has required the second EHLO all along and every mainstream client sends it; a home-grown submission script that skips it will find out here. For mail arriving through an incoming relay or fetched from an external account, the anti-spam tests run against the address the relay actually observed, not the first bracketed value in the Received header (which was the client's HELO literal). If you tuned scores around the old behaviour, expect verdicts to move. Mail is refused with a temporary error when the data volume falls below 100 MB free (MinimumFreeDiskSpaceMB, Control Panel: Server settings). Set it to 0 to restore the old behaviour of writing until the disk is full. Critical and high-severity errors also go to the Windows Event Log (source hMailServer, throttled). A service account that cannot create the event source logs a single line saying so and continues. Existing per-account out-of-office replies now honour the RFC 3834 suppressions (no reply to bulk, list, auto-submitted or bounce mail) that previously applied only to the domain-wide one. A bulk prune of old hm_imapexpunged rows runs once at first start (IMAPExpungeRetentionRecords, default 5000; set 0 to defer). Early-bound COM clients compiled against a 6.2.22 pre-release interop assembly must be recompiled against this release's type library; the pre-release layout was the broken one. Late-bound scripts (VBScript, PowerShell, PHP) are unaffected, and so is anything compiled against 6.2.21.

    Everything else keeps its previous default. The full list of the 23 new INI settings, all shipped off or inert, is in the Alpha 1 notes and in README.md.

    What changed since Alpha 2

    NuGet lock files beside all nine .NET tool projects, and every restore in CI held to them (--locked-mode, RestoreLockedMode), so a package bump without its lock-file update fails the build rather than building against a graph nobody reviewed. This was the last OpenSSF Scorecard finding that code could fix; the other four are dismissed on the security page with the reason on each, and eight required status checks were added to the master branch ruleset. The version stamp. Nothing compiled into the server, the tools or the installer changed otherwise.

    Known limitations

    The Alpha 1 and Alpha 2 lists still apply. The three worth repeating for a stable release:

    Statement timeouts were built for PostgreSQL and MySQL and have been exercised only against SQL Server Compact, the bench backend. The weekly C++ CodeQL analysis is not running: it targets a self-hosted runner that is no longer registered. The C# analysis runs on every push and pull request. SCRAM-SHA-1 is not offered (#49, with the reason); clients without SCRAM-SHA-256 use PLAIN or LOGIN over TLS.

    Downloads and verification

    AssetWhat it is
    hMailServer-6.2.24-x64.exeThe installer (Inno Setup). Not Authenticode-signed.
    *.spdx.json, *.cdx.jsonSBOMs, SPDX and CycloneDX, covering the .NET and native dependencies.
    *.cosign.bundleA Sigstore bundle per asset, keyless, bound to this repository's workflow identity.
    Verify the tag, from any clone

    git -c gpg.ssh.allowedSignersFile=.github/allowed_signers verify-tag v6.2.24

    Verify an asset with cosign

    cosign verify-blob --bundle hMailServer-6.2.24-x64.exe.cosign.bundle \ --certificate-identity-regexp '^https://github\.com/Progressiverobot/hmailserver/' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ hMailServer-6.2.24-x64.exe

    The server binary is reproducible: two clean Release builds of this tag produce a byte-identical hMailServer.exe, SHA-256 64406163b1a3a8a865e34d74883b5e2a05a5a1c476b500b26745cde67d8a989b, with the v145 toolset (MSVC 14.51), Windows SDK 10.0.26100 and the library layout in README.md. The regression suite on that binary: 1838/1838, nothing skipped, 31 minutes. Control Panel unit suite: 645/645.

    Release facts
    Version
    6.2.24
    Signed
    4 September 2026Sigstore transparency log
    Packages
    1Windows and Linux, x86-64 and AArch64
    Database schema
    6025upgrades in place
    Installer SHA-256
    f1e0d0e5a756…0f92a947hMailServer-6.2.24-x64.exe
    Every file in 6.2.24

    Full SHA-256 of each file as recomputed on this server, with the signature bundles beside it. The SBOMs and provenance are not needed to install; they let anyone audit exactly what was built.

    FileWhat it isSizeSHA-256Signature
    hMailServer-6.2.24-x64.exeWindows installer · x6473.5 MBf1e0d0e5a756bdff99de2059bbeacbcdd69979eae04312015536589a0f92a947Signature: cosign bundle
    hmailserver.spdx.jsonSBOM (SPDX)Every component in the build, in SPDX JSON.243 KBe290e76da3a076045eddfd09b01bbb9391940081ccc4fb4128b2f5065d985b15Signature: cosign bundle
    hmailserver.cyclonedx.jsonSBOM (CycloneDX)The same inventory in CycloneDX JSON.149 KBbee99ced55c6194b81b46079a407b9ba4800302b249f39f621fc375c59dd520eSignature: cosign bundle
    Progressiverobot-hmailserver-attestation-45322913.sigstore.jsonBuild attestationGitHub artifact attestation for the release, as a Sigstore bundle.4 KB6e9ddae83461fb6cb055d2a6844b3d6cb877c165405232a862677eb9c724d2cb
  6. 6.2.23-alpha2

    Pre-releaseSigned 4 September 20261 package

    Pre-release. Not for production.

    Release notes, checksums and signatures
    Windows

    Windows 10 (version 1607 or later) and Windows 11, or Windows Server 2016 and newer; 64-bit only. Run the installer as an administrator: it upgrades any existing 5.x or 6.x installation in place.

    • hMailServer-6.2.23-alpha2-x64.exex64 · 73.5 MB
      Download
      SHA-256c680ac6c61be…8a23d74bSignature: cosign bundle
    What’s new in 6.2.23-alpha2

    Pre-release. Not for production. 30 commits since v6.2.23-alpha1; 1,873 files touched, of which 1,825 are the licence-header pass from 22 August and 72 are the formatting commit, leaving roughly 80 files of actual change (+1,600 / −200 lines). The database schema is unchanged at 6025, the COM interfaces are unchanged, and no checked-in binary changed. Everything in Alpha 1's notes - the schema move from 6.2.21, the one-way upgrade, the known limitations - still applies to an installation coming from 6.2.21 or earlier.

    Full release notes for 6.2.23-alpha2 10 sections
    What this release is

    A small, deliberate release: one reported defect fixed, seven fixes taken from the original project - which has become active again after years - and the hardening that was written down as missing. It exists because a Thunderbird user could not save a single Sent copy, and because the upstream comparison turned up two ways a sender could steer the anti-spam tests. Alpha rather than beta because the STARTTLS change below alters what a non-conforming client sees, and that deserves a cycle in the open before it is called stable.

    Fixed: Thunderbird's Sent copies were refused (#53)

    Once a client enables UTF8=ACCEPT, RFC 6855 lets it send an APPEND's message as UTF8 (~{n} with the closing ) following the octets. Thunderbird 128 and later does this for every Sent copy. The server's command parser counts parentheses across the whole line before it tokenizes anything, saw one ( too many, and refused the line with BAD APPEND Command requires at least 2 parameter before reading a byte - so mail went out over SMTP and no Sent copy was ever stored, on every message, with only a per-machine client setting as a workaround.

    The wrapper is now recognised and stripped ahead of the parser, and the ) is taken from the text that follows the octets. It works for the non-synchronizing ~{n+} form and for later messages of a MULTIAPPEND. A wrapper that is never closed is refused and nothing is stored - the ) is protocol, not message, and a server that guessed where the message ended would be worse than one that refused. Five fixtures, including the exact line from the report; six of their assertions fail against the previous binary.

    Two anti-spam bypasses, ported from upstream

    The original project has resumed development (seventy-one commits since this fork last compared itself against it). Every one was read against the function that does the same job here; the full record, verdict by verdict, is in .github/upstream-sync. Two of the ports matter for security: The sender could choose which address the IP-based spam tests ran against

    For mail arriving through an incoming relay or fetched from an external account, the server parses the topmost Received header to find the originating address. It took the first bracketed value after from - which is the HELO literal the client chose to present - rather than the address the receiving server actually observed, which comes last. And a header whose host name was not a valid domain name (my_pc, say) was skipped entirely, so parsing fell through into headers the sender wrote, or found no address at all and quietly skipped DNSBL, SPF and the HELO-host test.

    The parser now takes the last observed address before by, ignores values marked as HELO-supplied, and keeps a header without a usable host name. Two assert(0) on sender-reachable input are gone with it. Upstream's own test vectors were taken along.

    This changes which address is tested for relayed and fetched mail. If you run behind an incoming relay and have tuned scores around the old behaviour, expect verdicts to move - towards the address that actually connected. RSET before EHLO, or STARTTLS without a fresh EHLO, opened a transaction with no greeting

    RSET is valid before EHLO and used to move the session into the transaction state on its own; and the TLS handshake discarded the greeting as RFC 3207 requires but left the state machine where it was. Either way MAIL FROM was accepted with the HELO host empty, which skipped the HELO-host spam test and the OnHELO/OnEHLO script events for that session.

    The greeting state is now only left by an actual EHLO or HELO. A client that sends MAIL FROM straight after the TLS handshake without saying EHLO again now receives 503 Bad sequence of commands - which is what RFC 3207 section 4.2 has required of it all along, and what every mainstream client does. If you have a home-grown submission script that skips the second EHLO, this is the release that tells you.

    Hardening

    Control Flow Guard is on (#45). hMailServer.exe is compiled and linked with /guard:cf in both configurations, so an indirect call through a corrupted function pointer or vtable - the natural target of a memory-safety defect in the SMTP, IMAP, POP3 or MIME parsers - terminates the process instead of transferring control. dumpbin /headers shows Guard in the DLL characteristics. The full regression gate on the CFG build ran in 31 minutes against about 55 for the previous release's gates on the same machine - a faster run, not a slower one, so the cost is below the suite's run-to-run variance. The release tag is signed (#46). v6.2.23-alpha2 is the first annotated, SSH-signed tag; every earlier one was a lightweight ref with nothing to verify. The signing workflow now refuses to sign a single asset for a tag that is lightweight or does not verify against the allow list in the repository. Verification is in the last section. An unattended install that fails its database step now fails. The installer discarded the result of the post-install tasks, so a failed database create or upgrade showed a suppressible message box and setup still exited 0. It raises now; scripted deployments can tell. COM IMAPFolder.Delete reports a refused deletion instead of answering S_OK while the row stayed behind.

    Other fixes

    A reverse-DNS answer whose owner name carries the trailing dot (1.0.0.127.in-addr.arpa.) no longer fails the resolver's equality filter and empties the PTR result, which had been failing open in the HELO and reverse-DNS spam tests on affected hosts. The script host dereferenced a null engine when engine creation had failed (a missing or policy-blocked VBScript engine) and a script reload asked whether a procedure existed. The IMAP notification client dereferenced the current folder on the notifying session's thread while the owning session could be closing it. Two CodeQL alerts on the mbox importer's buffer handling, and the warning-level findings the managed quality scan raised in the test suite and the header script.

    Dependencies

    Ten NuGet updates (Azure.Core 1.62, MSAL 4.88, NLog 6.2, OpenTelemetry 1.18, NUnit3TestAdapter 6.3, xunit.runner.visualstudio 4.0) and three CodeQL action updates, both from Dependabot. GitHub's automatic dependency submission - red on every push since it was enabled, because it restores Windows-targeting projects on a Linux runner - now completes, so the dependency graph carries the transitive packages. Getting it there also meant bumping the headers of ten Visual Studio 2005/2008-era solutions (legacy tools and test harnesses) that the .NET 10 solution parser refuses outright. The native dependencies are unchanged: OpenSSL 4.0.1, Boost 1.91, PostgreSQL 18.3, MariaDB Connector/C 3.4.9.

    Repository and process

    hmailserver/docs/RegressionEnvironment.md - how to set a machine up so the 1,800-test suite runs, which CONTRIBUTING.md had admitted was unpublished (#48). A Developer Certificate of Origin check on pull requests (#47), and the C# tree brought to dotnet format clean with the verify job restored (#44). Two CI-only changes landed after the regression gate had run and are the only things in this range that did: the editorconfig-checker version pinned beside its action (the checker's 4.0.0 release renamed its download and every run since 3 September had failed before checking a file), and the ten solution headers above. Neither is compiled into anything shipped, which is why the gate stands. Issue #49 (SCRAM-SHA-1) is closed as not planned, with the reason: the stored PBKDF2-SHA256 output is the SCRAM-SHA-256 salted password, and SHA-1 would need a second stored verifier per account - a change to what is stored, not to the transport. Ten further upstream changes are recorded as roadmap rows with their size and what they wait for, the largest being upstream's way of building the C++ server on GitHub-hosted runners, which this fork lacks.

    Known limitations

    Everything listed under Alpha 1 still stands. New or newly written down:

    The weekly C++ CodeQL analysis has not run since late August. It targets a self-hosted runner that is no longer registered, and is cancelled after 24 hours in the queue each Monday. The C# analysis runs on every push. Restoring the runner, or porting the hosted build, is the roadmap's top build item. SCRAM-SHA-1 is not offered, for the reason above; clients without SCRAM-SHA-256 use PLAIN or LOGIN over TLS. Received-header parsing is stricter about host names but not about brackets. An address given without brackets ((HELO host) (203.0.113.99)) is still not recognised, as before.

    Downloads and verification

    AssetWhat it is
    hMailServer-6.2.23-alpha2-x64.exeThe installer (Inno Setup). Not Authenticode-signed.
    *.spdx.json, *.cdx.jsonSBOMs, SPDX and CycloneDX, covering the .NET and native dependencies.
    *.cosign.bundleA Sigstore bundle per asset, keyless, bound to this repository's workflow identity.
    Verify the tag, from any clone

    git -c gpg.ssh.allowedSignersFile=.github/allowed_signers verify-tag v6.2.23-alpha2

    Verify an asset with cosign

    cosign verify-blob --bundle hMailServer-6.2.23-alpha2-x64.exe.cosign.bundle \ --certificate-identity-regexp '^https://github\.com/Progressiverobot/hmailserver/' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ hMailServer-6.2.23-alpha2-x64.exe

    The server binary is reproducible: two clean Release builds of this tag produce a byte-identical hMailServer.exe, SHA-256 cff066d39af42dac3532cbc091b6294a69a73b7d323666c16bfa0fc045c8fe23, with the v145 toolset (MSVC 14.51), Windows SDK 10.0.26100 and the library layout in README.md. The regression suite on that binary: 1838/1838, nothing skipped, 31 minutes. Control Panel unit suite: 645/645.

    Release facts
    Version
    6.2.23-alpha2pre-release
    Signed
    4 September 2026Sigstore transparency log
    Packages
    1Windows and Linux, x86-64 and AArch64
    Installer SHA-256
    c680ac6c61be…8a23d74bhMailServer-6.2.23-alpha2-x64.exe
    Every file in 6.2.23-alpha2

    Full SHA-256 of each file as recomputed on this server, with the signature bundles beside it. The SBOMs and provenance are not needed to install; they let anyone audit exactly what was built.

    FileWhat it isSizeSHA-256Signature
    hMailServer-6.2.23-alpha2-x64.exeWindows installer · x6473.5 MBc680ac6c61be8ec5c2f1e3fafb16a747c444e41ddeb6fec9a85efa5c8a23d74bSignature: cosign bundle
    hmailserver.spdx.jsonSBOM (SPDX)Every component in the build, in SPDX JSON.165 KB134dacbddada704851da404638c70d641af180b925f4cb7a97f67a0fe33adda5Signature: cosign bundle
    hmailserver.cyclonedx.jsonSBOM (CycloneDX)The same inventory in CycloneDX JSON.98 KBc2ccd6b9ab3ff2eaa904533d765134e8ed22c8439317adaedb1853aedc504893Signature: cosign bundle
    Progressiverobot-hmailserver-attestation-45313040.sigstore.jsonBuild attestationGitHub artifact attestation for the release, as a Sigstore bundle.4 KB5b3a5e266c4c75fd1fc99edfa0288ce7a0f93c2d579a49f34bc7cbb4e6a09ed9
  7. 6.2.23-alpha1

    Pre-releaseSigned 21 August 20261 package · schema 6025

    Pre-release. Not for production. 83 commits, 345 files, roughly +27,000 / −4,100 lines since v6.2.22-pre6.

    Release notes, checksums and signatures
    Windows

    Windows 10 (version 1607 or later) and Windows 11, or Windows Server 2016 and newer; 64-bit only. Run the installer as an administrator: it upgrades any existing 5.x or 6.x installation in place.

    • hMailServer-6.2.23-alpha1-x64.exex64 · 73.4 MB
      Download
      SHA-2560c7cbb5a160b…c91ca7a5Signature: cosign bundle
    What’s new in 6.2.23-alpha1

    Pre-release. Not for production. 83 commits, 345 files, roughly +27,000 / −4,100 lines since v6.2.22-pre6. The database schema moves from 6022 to 6025 in three steps — and if you are coming from 6.2.21, the last stable release, it moves from 6011 to 6025 in fourteen, picking up everything the 6.2.22 pre-release line introduced (app passwords, TOTP, the quarantine, password history, message trace, per-domain relay) on the way. One of the new steps rewrites a column on the largest table in the database.

    Full release notes for 6.2.23-alpha1 35 sections
    What this release is

    A large feature release cut as an alpha, staying in the 6.2 line — the alpha marker carries the caution, not the version number. It adds reverse-proxy client-address handling in front of SMTP, an external HTTP filtering hook, shared/delegated IMAP mailboxes, a full-text search index, RFC 3464 machine-readable bounces, RFC 3030 BINARYMIME, per-account spam thresholds, a sender blacklist, domain-wide out-of-office, distribution-list moderation, Windows Event Log integration, two more OTLP signals, IPv6 on the management listeners, a disk-space floor, and a substantial Control Panel overhaul. It also fixes one path that was silently losing mail.

    What "alpha" means here, in plain terms

    Do not put this on a production mail server. Seventy-two of the commits in this range landed on a single day, and the adversarial pre-release review of that work found six defects — one that filed delegated mail under the wrong mailbox and, on MOVE, destroyed the only readable copy; a COM interface layout that would have silently broken early-bound clients; a checked-in file that broke the build on any machine but the one it came from; and an unattended upgrade that would have hung forever at an invisible password dialog. All six are fixed and each fix carries a test that fails against the code before it — but a feature found to be broken hours before a cut has not had time to be wrong in a second way. One feature is known to be untested on the backends it was built for. Statement timeouts were built for PostgreSQL and MySQL and have only been exercised against SQL Server Compact, the bench backend. This is written down in Roadmap.md rather than glossed. If you install it, install it on a test system with mail you can afford to lose, and read Known limitations first.

    The database upgrade is one-way

    Upgrading.md states this as the one genuine sharp edge in the process, and it applies here with more force than usual because at least three schema steps land at once:

    There is no downgrade path... The schema upgrade is one-way: an older server will refuse to run against a newer dbversion rather than misinterpret it, which is the correct behaviour but means "just reinstall the old version" does not work on its own. To roll back you need the pre-upgrade database backup.

    So: take a database backup and a data-directory backup before you install, with your backend's own tools. Rollback is only as good as the snapshot you took first, and if you restore an older database against a newer data directory, any mail that arrived in between will exist on disk with no metadata row and will be invisible. Breaking changes and upgrade notes 1. Schema 6022 → 6025, in three registered steps

    REQUIRED_DB_VERSION is now 6025. The chain is 76 registered steps for MySQL/MariaDB and MS SQL Server (reaching back to schema 0), and 49 for PostgreSQL and the internal SQL Server Compact database (reaching back to 5001). Fresh installations are stamped 6025 by the CreateTables scripts. An installation on 6.2.21 (schema 6011) upgrades through every intermediate step automatically; DBUpdater resumes from whatever version the database reports, so a retry after a partial upgrade continues rather than restarting.

    StepWhat it does
    6022 → 6023hm_messageindexterms (full-text posting list) and hm_messageindexstate (backfill cursor). Both empty until full-text search is enabled.
    6023 → 6024Six hm_domains columns for the domain-wide out-of-office reply; new hm_blocked_senders table.
    6024 → 6025hm_messages.messageflags widened tinyint → smallint; three hm_accounts columns for per-account spam settings; two hm_distributionlists columns for moderation and per-list bounce address.
    2. The 6024 → 6025 step alters the largest table you have

    ALTER TABLE hm_messages ALTER COLUMN messageflags smallint (MS SQL, SQL CE) and alter table hm_messages modify column messageflags smallint unsigned (MySQL/MariaDB) are table-rewriting operations on the message metadata table. On an installation with millions of message rows this can take a long time and will hold locks for the duration. Plan a maintenance window sized to your hm_messages row count, and do not interrupt it.

    PostgreSQL is unaffected by this one — its messageflags was already smallint, so the 6024 → 6025 script for PostgreSQL adds columns only.

    The widening exists because FlagSeen through FlagSpam had taken all eight bits a tinyint can hold, and RFC 3030 BINARYMIME needs a ninth. 3. Unattended upgrades used to be able to hang forever; from this release they cannot

    If you script installs with /VERYSILENT, read this one. DBSetupQuick forwarded the administrator password to DBUpdater on the create path but never on the upgrade path, and DBUpdater authenticates before it can move the schema — so on any installation with a non-empty administrator password, a silent upgrade fell through to a modal password dialog nobody was there to answer, and the installer waited on it indefinitely. The defect was latent because DBUpdater exits before authenticating when the schema does not move; a release that moves the schema is exactly what arms it, and this is the first release since the fix's own introduction that moves it for every existing installation.

    The password is now forwarded on both paths, and the shared authenticator fails instead of prompting under /silent, so the worst case is an exit code the installer reports out loud rather than a wait with no end. Pass the password with /adminpassword=... as before. 4. COM vtable layout repaired — recompile anything early-bound against a 6.2.22 pre-release

    Five COM properties added during the 6.2.22 pre-release line and this branch were declared mid-interface instead of appended. A dual interface's vtable follows declaration order, not id(), so four interfaces — AntiSpam, Account, Application, GlobalObjects — had every slot beneath the insertion silently shifted while their IIDs stayed the same. Late-bound scripts (VBScript, PowerShell, PHP COM — the way virtually everyone drives hMailServer) were never affected, because dispatch by id() was always correct.

    This release moves all five declarations to the end of their interfaces, restoring exact binary compatibility with v6.2.21, the last stable release. The cost lands in one narrow place: a client early-bound against a 6.2.22 pre-release interop assembly was compiled against the shifted layout and must be recompiled against this release's type library. Since 6.2.22 shipped only as pre-releases, this is the last cheap moment the repair will ever have; the alternative was carrying the silent 6.2.21 break forever. 5. New default behaviour: mail is refused when the disk gets low

    MinimumFreeDiskSpaceMB defaults to 100. Below that floor on the volume holding the message store, new mail is refused with a temporary failure — 452 4.3.1 at MAIL FROM, NO [UNAVAILABLE] at IMAP APPEND — and the external POP3 fetcher pauses its whole sweep rather than downloading onto a full disk. DiskSpaceWarningThresholdMB defaults to 1024 and writes one application-log line when the volume crosses into the warning band.

    Set MinimumFreeDiskSpaceMB=0 to switch the floor off entirely and keep the previous behaviour. If you deliberately run a server with under 100 MB free, you must set this or mail will start being deferred on upgrade. 6. New default behaviour: hMailServer writes to the Windows Application event log

    WindowsEventLogEnabled defaults to 1, at WindowsEventLogLevel=2 (Critical + High). Errors are forwarded from ErrorManager — the one chokepoint every error passes through — to the Windows Application log under the source name hMailServer.

    A healthy server writes zero events. Curated ids give the conditions operators alert on their own number: 2010 database unavailable, 2011 listener would not start, 2012 crash, 2013 backup failed, 2014 brute-force protection impaired, 2015 disk floor reached. Unrecognised codes fall into a severity catch-all (2000–2003). A throttle of five per id per ten minutes bounds a flood, and the first event after a throttled window says how many it swallowed. Event id 0 remains reserved for the pre-existing log-write-failure event.

    Two operational notes. First, if you have log-collection tooling that alerts on anything new in the Application log, it will now see hMailServer; set WindowsEventLogEnabled=0 to keep the previous silence. Second, registering the event source is a privileged HKLM write no prior version made. The service performs it under LocalSystem, where it succeeds; a service reconfigured to run under a lesser account will fail the registration silently and events simply do not appear.

    Message-file caveat, stated rather than discovered: hMailServer has no message DLL. The sink best-effort registers .NET's EventLogMessages.dll as its EventMessageFile. Registered, Event Viewer shows the text cleanly; unregistered, the entry carries the "description cannot be found" boilerplate with the full text underneath — ugly, but complete. 7. New default behaviour: database statements now time out on PostgreSQL and MySQL

    DatabaseStatementTimeout defaults to 30 seconds. PostgreSQL and MySQL previously had no statement timeout at all, so a statement blocked on a lock held its pooled connection until the network gave up, and the pool is fixed size.

    The precise reach of the setting is worth stating exactly, because it is uneven. MS SQL and SQL CE do not read it at connect time — they keep ADO's own 30-second default, which happens to match. But the script runner applies the configured value to any pooled connection a maintenance script has passed through, so a non-default value does take effect on those two backends after a DBUpdater step or a maintenance script has run on that connection. On PostgreSQL and MySQL it is applied at connect and governs everything.

    Raising it is the safe direction. Lowering it below the time a legitimate maintenance statement needs — a domain removal, a backup sweep — makes that statement fail rather than merely be slow. 0 means no limit.

    Untested on this bench for MySQL and PostgreSQL — the two backends it was built for. The MySQL half in particular is fiddly: the variable is max_statement_time in seconds on MariaDB 10.1.1+, max_execution_time in milliseconds on MySQL 5.7.8+, and max_statement_time in milliseconds on MySQL 5.7.4–5.7.7. The fork is read from VERSION() and the variable is proved to exist by setting it to 0 before anything relies on it; a failure there never fails the connection. But this is an alpha and this specific code has not been run against a real MySQL or PostgreSQL server. 8. New default behaviour: QRESYNC tombstones are pruned — with one bulk delete at first start

    IMAPExpungeRetentionRecords defaults to 5000 per mailbox. hm_imapexpunged previously held one row per message ever expunged and nothing removed them except deleting the folder, so the table grew for the life of the installation.

    Capping it is safe because of the RFC 7162 §3.2.6 fallback that had to be built first: a client asking about a mod-sequence older than the oldest record still held is told about every UID in the requested set the mailbox no longer holds, rather than about nothing. So the cost of a low value is a larger VANISHED response for a client that has been away a long time — never a client left believing a deleted message is still there. 0 keeps every record.

    On a long-lived installation, the first prune is a bulk delete. A run-once pass fires at the first service start after the upgrade, alongside the recurring 12-hourly one — in the same window the schema move just used. If you want to separate the two, set IMAPExpungeRetentionRecords=0 before the first start and pick a number later. 9. Shared mailboxes become reachable wherever IMAP ACL is enabled — which is the default

    enableimapacl ships as 1 on a fresh database and has for many versions. With it on, this release makes another account's folders reachable at #[email protected] when, and only when, the owner's folder carries an RFC 4314 ACL granting the caller rights.

    The practical consequence for an existing installation: after upgrading, any account holder whose IMAP client speaks SETACL can grant another local account access to their own folders, with no administrator step. Nothing is shared until someone grants it, and there is no way to grant access to a folder you do not own.

    There is deliberately no separate switch. Turning IMAP ACL enforcement off removes the #Users namespace entirely (NAMESPACE answers NIL, #Users... paths do not resolve) — but it also restores the historical meaning of "ACL disabled", which is that public folders are open to everyone. Those are the only two positions available; weigh them before flipping the switch as a way to disable delegation.

    A small per-command cost lands on every installation: with the Other Users namespace enabled — which it is by default — every IMAP LIST now runs one additional uncached query to discover folders shared to the caller. On a default install with no shares that query returns nothing, but it runs.

    10. Existing out-of-office replies change behaviour on upgrade

    The new RFC 3834 suppression checks apply to auto-replies administrators already had configured, not only to the new domain-wide reply — the checks live at the top of the shared path every vacation message goes through. After the upgrade, an existing per-account vacation message no longer answers bounces, list traffic, or anything carrying Auto-Submitted, Precedence: bulk/list/junk, List-* headers, X-Auto-Response-Suppress, or a mailer-daemon/postmaster sender. There is no switch; the suppressed reply is recorded at LOG_DEBUG only. This is almost certainly the behaviour you always wanted — an auto-reply answering a mailing list is how mail loops start — but it is a change to a configured feature, so it is stated here rather than filed under new features.

    11. Bounces change shape

    Every non-delivery report is now multipart/report; report-type=delivery-status with the three parts RFC 3464 describes. The human-readable server message is unchanged and still first, so a mail client that has never heard of multipart/report shows what it always showed. If you have monitoring, log parsing or a mailing-list manager that matches on the whole bounce body rather than its first part, re-check those patterns.

    12. PreferredHashAlgorithm below SHA256 is now refused

    Of the setting's six legal values, 0 is no hashing, 1 is reversible and 2 is MD5 — and two of the five places that read it hand the value straight to Crypt as the scheme to store an account password or app password under. Anything below 3 (or above 5) is now refused at the point the setting is read, 4 (PBKDF2) is used instead, and the substitution is reported as HM5528 rather than silently applied.

    The default is unchanged at 4, so this affects only installations that explicitly set 0, 1 or 2. Existing stored hashes are untouched; this governs only what a new secret is stored under.

    13. New COM interfaces — re-registration is required

    Schema 6024/6025 came with new IDL: two BlockedSender interfaces with their coclasses, BlockedSenders on the anti-spam interface, six Domain properties, three Account properties and two DistributionList properties. The installer runs hMailServer.exe /RegisterTypeLib and handles this. A hand-copied or developer install must re-register with elevation, because an out-of-process COM server marshals by IID and both sides read HKEY_CLASSES_ROOT\Interface under HKLM — a per-user registration is not sufficient, and the symptom is REGDB_E_IIDNOTREG rather than anything that names the cause. 14. Fourteen new settings are absent from README.md's settings reference

    Nine new keys are INI-only — no Control Panel surface, no entry in the settings search index, no README entry: IndexerFullText and its three companions, MinimumFreeDiskSpaceMB, DiskSpaceWarningThresholdMB, DatabaseStatementTimeout, IMAPExpungeRetentionRecords and DmarcRptSchemaVersion. Five more — the FilterHook* keys — have Control Panel surfaces but no README entry either. The full table is in New settings below; for the nine, edit hMailServer.INI directly. (Documentation coverage is being reviewed separately; noted here so administrators can find the keys.) 15. If you are coming from 6.2.18 or earlier — the .NET runtime is now load-bearing on upgrade

    The Control Panel and the database setup tools require the .NET 10 Desktop Runtime, which the installer bundles and installs when missing. The server itself is native code with no .NET dependency — but DBSetup, DBSetupQuick and DBUpdater are .NET applications, this release must move your schema, and the server refuses to start against an old schema. So on an upgrade, a failed runtime install does not leave you with "a working mail server minus the GUI"; it leaves you with no running mail server until the schema is moved. If the runtime install fails, install it by hand from https://dotnet.microsoft.com/download/dotnet/10.0 (Desktop Runtime, x64), then run DBUpdater.exe from the installation's Bin folder and start the service.

    New features

    Each entry says what it does and what it does not do.

    Mail filtering and anti-spam

    External HTTP filtering hook. FilterHookUrl names an engine — rspamd, most likely — and the message is POSTed to it, with the envelope, connecting address and HELO travelling as request headers in the names rspamd's own check endpoint already reads. The verdict arrives as a score, which lands alongside SPF, DKIM and DMARC so your existing thresholds decide what happens; "reject" is worth FilterHookRejectScore (default 100) rather than being the one verdict that ignores thresholds. Everything is bounded because it runs while a client waits for its answer to DATA: one deadline covers connect, write and read (FilterHookTimeoutSeconds, default 10), and a message above FilterHookMaxMessageSizeKB (default 10240) is passed without being sent at all. It does not speak HTTPS to the engine, so the engine belongs on this machine or a trusted network; and it cannot express a hard 5xx at DATA independent of thresholds, which a score-based framework does not have a way to say. FilterHookFailClosed is off by default: a filter outage that lets spam through is recoverable, one that defers every message while nobody is watching ends in bounces. Empty URL disables it, which is the default.

    Sender/domain blacklist. BlockedSenders expresses "I never want mail from this address" through the existing spam-test pipeline, so thresholds, quarantine fallback, headers and logging all apply unchanged. An entry with an @ is an exact address; without one it is a domain and covers subdomains, anchored at a label boundary — example.com matches mail.example.com and never notexample.com or example.com.attacker.net. No wildcards (deliberately narrower than the white list: a deny list has to be predictable). Each entry carries its own score, defaulting to 100, which crosses the shipped delete threshold. The null sender is never blocked, so bounces stay deliverable. It matches the SMTP envelope sender, which the sender controls completely — it stops a correspondent who keeps using one address and stops nothing that rotates. It is not an anti-spoofing tool. Managed from a Blocked senders page beside the white list. It registers as spam check number one because it genuinely runs first — which means the existing checks each move down one position in the spam overview and in the Spam test: log lines, and every message now logs one more such line. Re-check log parsers that match on test position rather than name.

    Per-account spam settings. An account can override the mark and delete thresholds, and can opt out of having its own copy marked. The opt-out strips spam classification from the account's copy at delivery — the SMTP conversation is unaffected: the message was already accepted or refused by the server-wide checks before per-account settings exist, so "opt out" cannot admit mail the server would have refused. -1 means no override on either threshold; a delete threshold of 0 additionally means "never delete". The overrides act only on a spam score this server itself recorded, which means the anti-spam "add reason to header" setting must be on — it is on by default. With no recorded score, neither override acts and the account keeps server-wide behaviour. This is not a hedge: inbound mail is not stripped of X-hMailServer-* fields, so with that setting off the header is whatever the sender put there, and a spammer attaching X-hMailServer-Reason-Score: 1 could otherwise steer a recipient's own override into un-marking their spam. The earlier "the flag proves the score reached the global mark threshold" fallback was withdrawn entirely, because that threshold is read at delivery while the message was judged at reception.

    Distribution lists

    Moderation. A list can name a ModeratorAddress. A sender the list's mode would refuse is no longer bounced — the posting is accepted and forwarded to the moderator, stamped with an X-hMailServer-Moderation header naming the list, and the moderator approves it by resending it to the list from an authenticated session. The authentication requirement is unconditional and does not follow the list's own RequireAuth, because MAIL FROM is free text. A forged unauthenticated approval does not merely fail: it lands, labelled, in front of the real moderator. Empty moderator address means moderation is off and behaviour is byte-for-byte what it was.

    Per-list bounce address. BounceAddress is the envelope sender given to every copy the list sends. Empty keeps the previous behaviour, where distributed copies carried the original poster's envelope sender and a dead subscriber's bounces hammered whoever happened to post last. Setting it also makes receivers evaluate SPF against the list's domain rather than the author's.

    Self-subscribe is not included — it needs a confirmation round-trip and a token store, and is tracked as its own item.

    Out-of-office

    A domain-wide reply, which answers for accounts that have no vacation message of their own. Optional separate text for senders whose address belongs to this server, and a VacationExternalOverride that sends the domain's generic text to outsiders even when the account has its own message. The account's own voice always wins over the domain's, and a suppressed account reply does not fall through to the domain. One stated exception to "one delivered message, one reply": an account with both a stored vacation message and a Sieve vacation action sends two replies, because the stored responder and the Sieve responder run at different points of delivery and do not consult each other — see Known limitations.

    "Internal" means the envelope sender resolves to a local account or active alias. That is the sender naming a local mailbox, not the message having come from its owner, because an envelope sender is forgeable — the queued message carries no authentication state, so authentication cannot factor in without a schema change. Installations that require SMTP AUTH for local sender addresses close the hole at the door; elsewhere the internal text is a courtesy gradient and never access control. The classifier errs toward external.

    Loop prevention was checked rather than assumed. Already correct: null return-path refusal, Auto-Submitted: auto-replied on the reply, the reply's own null envelope sender, and the once-per-sender rate limit. Added: incoming Auto-Submitted other than no, Precedence: bulk/list/junk, RFC 2369/2919 List-* headers, X-Auto-Response-Suppress, and mailer-daemon/postmaster senders — all checked before the rate-limit slot is spent, and applying to pre-existing per-account replies as well (upgrade note 10). There is no per-account internal/external split; that needs hm_accounts columns and is deferred.

    IMAP

    Shared and delegated mailboxes (RFC 2342 Other Users namespace). Another account's folders appear under #[email protected] when the owner's folder carries an ACL granting the caller rights. One access decision in one place: path resolution refuses to return a delegated folder without the lookup right, so SELECT, EXAMINE, STATUS, APPEND and the rest answer exactly "Folder could not be found" for a folder you may not know exists — the commands cannot be used to probe which accounts or mailboxes are real. Per-right enforcement (r read, i append, s \Seen, t/e deletion and expunge) comes through the existing permission checks. LIST enumerates only what you hold the l right on. Quota charges the destination folder's owner.

    \Seen in a shared mailbox is shared state, not per-user. Flags live on the message row; per-user \Seen needs a per-account flag table joined on every FETCH/STORE/STATUS/SELECT — a schema change and a message-store rework, deliberately not smuggled in. The s right arbitrates: without it a delegate can neither STORE \Seen nor set it implicitly via FETCH BODY[], and SELECT's PERMANENTFLAGS advertises exactly what the delegate's rights allow. The owner cannot be locked out — their rights are implicit and therefore not removable state.

    Full-text search index. SEARCH BODY/TEXT used to load and MIME-parse every message in the mailbox. A term index now answers the only thing a term index can soundly say about a substring search: this message cannot contain that text, skip reading it. Every message the index cannot vouch for is loaded and scanned by the same code as before, so results are identical with the index on and off — including a substring inside a word, which an index-only implementation silently gets wrong. Only the cost changes. Off by default, behind two settings that both ship off, and those defaults are load-bearing: IndexerFullText=1 in the INI turns the term index on, and the message indexer itself must also be enabled — that one has a Control Panel checkbox ("Enable message indexing") and is Settings.MessageIndexing over COM. IndexerFullText=1 alone indexes nothing. Enabling both costs a few kilobytes of terms per message and starts a backfill over the whole store, so it is an administrator's decision and never an upgrade's. The backfill is bounded and resumable, with a persisted high-water mark and an hourly reconciliation pass. Errors report as HM6260.

    QRESYNC tombstone retention — see upgrade note 8.

    SMTP

    BINARYMIME (RFC 3030). BODY=BINARYMIME is advertised and accepted; DATA in such a transaction is refused 503 per the RFC, since binary content is not line-oriented and only BDAT may carry it. A payload containing bare CR, bare LF, NUL and all 256 octet values is delivered byte-for-byte — proven over a raw-socket IMAP BODY[] literal, because POP3 is line-oriented and cannot prove fidelity.

    Relay of a binary message is refused, not converted. This server's delivery client speaks DATA only, so a binary message with a recipient requiring onward relay is refused at RCPT with the RFC's own 554 5.6.3 (conversion required but not supported), and a delivery-time backstop catches the indirect paths — a distribution list's external member, a forward, a rule — generating a DSN the sender can read plus an HM6340 for the administrator. Locally delivered binary mail, which is the actual use case, just works.

    Two findings from building it: the bare-LF rejection fired on binary content, refusing the exact content the extension exists to carry, and now exempts negotiated-binary transactions only; and the binary mark is now persisted as flag bit 256, which is why messageflags had to be widened in schema 6025.

    Reverse-proxy client addresses: PROXY protocol v1/v2 and XCLIENT. Put HAProxy, a TLS terminator or a Postfix relay in front of port 25 and every connection previously appeared to come from it — DNSBL, SPF, greylisting, auto-ban and the IP-range rules all quietly evaluated the wrong address, all still reported success, and the Received header recorded the lie. Both mechanisms now exist: PROXY protocol consumed before the TLS handshake and before the greeting, and Postfix XCLIENT answered with a fresh 220 so the upstream re-issues EHLO. The rewrite lands before anything reads the address, and the security range is re-read for the asserted client, so an auto-ban range still bites through the proxy.

    The security rule is the feature. Both ship off, both trusted lists ship empty, and every trust decision is made against the real TCP peer — never against an address a header just supplied. A list entry that does not parse matches nothing rather than everything. XCLIENT is not advertised at all to an untrusted peer. A PROXY header from an untrusted peer drops the connection rather than being ignored.

    Two consequences stated rather than hidden: trust is per-source, not per-port, so a listed proxy must send the header on every connection to this server; and the server cannot check that your upstream is configured to send one — HAProxy needs send-proxy, a Postfix relay needs XCLIENT enabled towards this host. Listing an address makes the header required from it, so a proxy that does not send one will have its connections dropped.

    Machine-readable bounces (RFC 3464). See upgrade note 11. The distinctions are the point: a full mailbox is 5.2.2 and not 5.1.1, because the address is good and must not be struck off a list; a local database write failure is 4.3.0 and the disk floor is 4.3.1, both class 4, because "permanent" would be a false statement about a recipient whose mailbox was never the problem; a Sieve reject is 5.7.1; a remote refusal uses the remote's own enhanced code when it sent one. Diagnostic-Code and Remote-MTA are filled only from what a remote server actually said and the host that said it. Arrival-Date and Original-Recipient are omitted rather than invented — one is stored as a database timestamp with no converter, the other needs an ORCPT this server validates but does not retain.

    Operations, monitoring and the API

    Windows Event Log integration — see upgrade note 6. Deliberately absent, with reasons: service start/stop (the SCM already records every transition in the System log, and the interesting gap — service up, servers dead — is covered by the listener and database ids), database restored (no unconditional signal exists for it), certificate expiry (nothing currently detects approaching expiry as an event), and all protocol chatter (a mail server logging every session to the Application log gets itself uninstalled).

    OTLP metrics and logs signals, each gated by its own endpoint URL and empty — therefore off — by default. The metrics are the same counters /metrics already serves under the same names, deliberately, because two independent tallies of one event drift and then two dashboards disagree with no way to tell which is lying. The push exporter carries the event counters only — nine gauges on /metrics are not exported: queue depth and age, the database probe, certificate expiry, work-queue depth and blocked tasks, connection-pool size, the up/down state, build_info and start_time_seconds. Most are computed by the metrics listener from database and file reads it owns; a few are in-process values that simply have not been wired into the push path yet. Either way the operational answer is the same: scrape /metrics for gauges; the OTLP signal is the event stream. Logs come from the single Logger chokepoint, with trace and span ids attached where a span was active, and the exporter's own thread is excluded from its own feed so a dead collector cannot feed itself.

    W3C trace context ingestion. Inbound traceparent is read from HTTP headers and — the interesting half — from message headers on SMTP, where W3C context travels as a header field. Reception prepends its own the way Received is prepended, so the first occurrence is authoritative; the original is not deleted, because a sender's DKIM signature may cover it and prepending is signature-safe where deletion is not. Validation is treated as a security boundary: the version and format are exact, an all-zero id is rejected, tracestate is confined to printable ASCII so a CR/LF cannot ride into a message header, and a malformed value is never propagated. It never refuses anything — observability must not be able to reject mail — and the inbound sampled bit feeds no decision in either direction, so it can neither force expensive sampling on nor be used as an attacker's opt-out from the audit trail.

    IPv6 for the REST, metrics and web-services listeners. All three previously built AF_INET sockets and parsed the bind address with inet_pton(AF_INET), so an IPv6 literal was rejected as invalid. All three now accept both families. A bind to :: is made dual-stack (IPV6_V6ONLY cleared), because each listener has exactly one bind-address setting and :: is the only way to serve both; every specific address binds exactly what it names. Loopback-keyed security checks follow: ::1 satisfies the REST API's TLS-mandatory-unless-loopback gate and the metrics credential gate, while :: is not loopback and still demands a credential. On dual-stack accepts a v4-mapped peer is unmapped before it reaches the auto-ban exclusion, AllowedFrom matching and the security ranges.

    Five new REST routes plus a self-description. GET /api/v1/quarantine (newest thousand, through the same store call the administration surface uses), POST /api/v1/quarantine/{id}/release, DELETE /api/v1/quarantine/{id} — both mutating verbs 404 for an unknown id rather than reporting success for a typo — GET /api/v1/domains/{domain}/aliases, and GET /api/v1/srv, which emits RFC 6186/8314 client-discovery SRV records per active domain, built from the ports actually configured and enabled (no record for a disabled service, a loopback-bound port, or port 25 as submission). The SRV route generates records; it publishes nothing. Someone still has to put them in DNS — the route exists so what you publish matches what the server actually listens on. GET /api/v1/openapi.json describes every route from a document that lives beside the router, and its test carries its own list of every path and fails the moment an endpoint exists the document does not mention. For domain-restricted keys the quarantine takes the queue's answer — refused — because one entry names a sender and recipients in any number of domains and releasing one delivers mail; release and delete are declared mutating by kind, so a read-only key is refused however the request is spelled.

    DMARCbis / RFC 9990 aggregate reports, behind a setting that defaults old. DmarcRptSchemaVersion=2 emits the RFC 9990 form: the dmarc-2.0 namespace, pct gone, np/testing/discovery_method added, the DKIM selector mandatory, and a pass disposition that finally separates a message that passed DMARC from one that failed under p=none. The RFC 7489 form stays the default and stays byte-for-byte what it was, because a report the receiving domain cannot parse is indistinguishable from no report, and essentially nothing in the field parses 9990 yet. An invalid value is refused at the INI read, reported as HM6210, and treated as 1. (The version element stays 1.0; the 2.0 is the namespace's.)

    Privacy and data-subject tooling

    Two COM methods, deliberately two operations, both server-admin only and both COM-only — there is no Control Panel surface for either.

    Account.ExportMessages walks the folder tree and copies every message to .eml files mirroring the structure. It fails rather than reporting a partial export as success, because a partial export handed to a person exercising a data-access right is exactly the wrong artefact to produce. Export is all-or-nothing; there is no selective export.

    Utilities.EraseAddressTraces removes what account deletion deliberately leaves behind: quarantine entries via the store so the file goes with the row, message-trace rows, greylisting triplets, distribution-list memberships, aliases naming the address as either half, and optionally the per-user archive tree. It takes an address, not an account, because the request usually arrives after the mailbox is already gone — and it is not part of deleting an account, because an archive can be under a legal hold that outranks a routine deletion. Sweeps continue past a failed store and report what survived (HM6250).

    Documentation

    docs/WarmStandby.md is new, with every claim verified against the code first. Three constraints decide its whole shape, and none is a preference: exactly one service may run against the database (both UnlockAll calls at startup release everything a live twin is holding mid-flight — a second server is actively destructive, not redundant, and delivers every in-flight message twice); DPAPI secrets do not travel (CRYPTPROTECT_LOCAL_MACHINE means route, fetch and per-domain-relay passwords fail to decrypt on the standby and must be re-entered after every failover, in both directions); and the message store is replicated rather than shared. The roadmap row stays underway rather than shipped, because nothing has rehearsed this on a real two-machine pair.

    docs/DiagnosingStalledMail.md was corrected: it told administrators that the custom DNS-server setting was broken and unfixed, citing issue #25. That was true in 6.2.17 and 6.2.18 and has not been true since 6.2.19, so a document written to get somebody out of trouble was telling them to switch off a working feature.

    Control Panel

    Beyond the fixes listed below, the Control Panel got a substantial visual and accessibility pass: the type ramp re-based onto the Fluent 12/14/20 scale (214 C# literal sites and 69 XAML literals converted to tokens), Segoe UI Variable requested for the first time (no FontFamily had ever been set anywhere, so the whole app rendered classic Segoe UI), 168 Win32 MessageBox call sites replaced with a themed equivalent that exposes the same static overloads so each file moved by one using-alias line, eighteen dialogs re-based onto a common Fluent window so they stop opening with a stock Win32 title bar, sidebar group glyphs, and roughly 55 previously-anonymous form fields given accessible names so they no longer announce to a screen reader as "edit". New pages for Blocked senders, an Out of office tab on the domain dialog, a Windows Event Log card, and a PROXY protocol / XCLIENT card that leads with the trust list rather than the on/off switch.

    Fixes

    Only items with a reproduction or a negative-control test are called fixed. Everything else is under Hardening and diagnostics.

    Data loss and correctness

    Local delivery could lose a message silently. When the account-level copy could not be written, LocalDelivery reported HM5209 and returned — while Perform() deleted the recipient row regardless and DeliverMessage, seeing no error messages and nothing rescheduled, deleted the queued message. The mail was gone, the sender had been told 250, and the recipient never heard of it. The sender is now told, as the save failure below it already did. Evidence: identified by audit of the disk-full paths and corrected in code; the disk-full precondition around it has four regression tests, but this specific loss path does not have its own negative control. Treat it as the most important single change in this release and the one most worth exercising on your test system.

    Two database backends could hang forever. DALConnection::SetTimeout was an empty {} on PostgreSQL and MySQL. Fixed by DatabaseStatementTimeout — see upgrade note 7, including its untested-backend caveat.

    Unattended upgrades could hang forever at an invisible dialog. See upgrade note 3. Negative-controlled: a source-shape test asserts both halves — the password forwarding and the fail-instead-of-prompt guard — and fails against the code before either.

    The white list cache never cached, and deleting an entry never invalidated it. WhiteListCache::Refresh() never cleared its own dirty flag, so every message re-read hm_whitelist from the database — exactly the per-message round trip the cache exists to avoid. That accidental reload was masking a second bug: deleting a white list entry never invalidated the cache while saving one did, and Whitelisting.TestEnabled only passed because of the masking. Both are fixed, and they had to be fixed together — either alone breaks the other.

    Path traversal in the new archive eraser, and silent message loss in the new exporter. Found by an adversarial re-check of the privacy tooling after it was written and after the gate was green. An address of "..@.." resolved to <ArchiveDir>\..\.. and a recursive delete took two levels above the archive with it; any segment that is empty, all dots, or carrying a separator is now refused before a path is built (HM6251). Separately, SanitizeFolderNameForExport mapped every character Windows forbids to the same underscore, so sibling folders Team:A and Team|A both became Team_A — and since UIDs are unique per folder but repeat across folders, the second folder's 1.eml overwrote the first's while the returned count cheerfully counted both. Colliding siblings now get a (2) suffix. Defects found and fixed inside this release's own new code

    Listed separately because these never shipped — but they are the best available evidence of how new this code is.

    Delegated APPEND, COPY and MOVE filed mail in the wrong account. Opening the #Users namespace corrected the read paths; the write paths were missed. IMAPCommandAppend stamped the row with the folder owner's account id and then built the file path from the caller's. CopyToIMAPFolder took one source account and used it for both ends. So a delegate's APPEND answered OK, the row landed in the owner's folder, and the bytes went under the delegate's mailbox — no read path could resolve them, the owner's included. MOVE was worse: the copy stranded, then the source was expunged, destroying the only readable copy. Fixed structurally rather than at the three call sites — CopyToIMAPFolder now derives both accounts from the objects it already has, so a mismatch is not representable — and quota now charges the destination owner. Negative-controlled: two tests fetch the body back as both the delegate and the owner and assert the placeholder is absent; against the pre-fix binary both fail, and the failure names the exact directory the server looked in. The green gate had not caught it because the only delegated-write test asserted A01 OK and a folder row count, both of which were true with the file in the wrong directory.

    COM interface members were inserted rather than appended — see upgrade note 4. Verified by a declaration-order comparison against the v6.2.21 IDL: four interfaces shifted before the repair, zero after.

    A delegate was reading their own directory for somebody else's mail. Seven call sites turned a Message into a file path using the logged-in account, which was correct for as long as the only folders a session could open were its own. FETCH (three sites), SEARCH, SORT and THREAD now all ask GetAccountOwningCurrentFolder, which answers nothing at all when the owner cannot be loaded, since falling back to the reader would build a path under the wrong mailbox. Caught by the shared-mailbox fixture on its first run.

    RENAME, DELETE, SETACL, DELETEACL, GETACL and LISTRIGHTS were unsafe against delegated folders — a delegate holding the delete right could rename an owner's folder into their own tree; DELETE looked the parent up in the caller's tree and dereferenced the result unchecked (a null dereference for a delegated subfolder, a silently-pruned wrong tree for a root-level one); both INBOX guards compared path text against the word INBOX and walked straight past #[email protected], so RENAME could rename an inbox away from the account every message is delivered into and DELETE would empty it of every message and subfolder; SETACL read through a deliberately unloaded collection and inserted a duplicate hm_acl row every time, for the same reason DELETEACL answered OK while deleting nothing; and LISTRIGHTS had no rights check at all. All closed before the grant surface was opened. Eight tests, including a blind probe: seven commands against a real folder, a missing folder and a missing account, asserting byte-identical answers each time, then proving the probes destroyed nothing.

    A recursive folder walk that went 29 levels deep. IMAPFolders::Refresh loads the account's entire tree into whichever collection it is called on, so calling it inside the export recursion turned every subfolder into a second copy of the whole tree. Caught by its own test on the first run.

    Generated #import output was committed, with one machine's absolute path inside it. The generated .tlh includes its own .tli by full path, so a developer's home directory shipped inside the AGPL source archive, and a build on any machine without that exact path failed with C1083. The three tracked copies are gone and *.tlh/*.tli are ignored, with a test pinning the ignore rules. This is a source-tree fix; no shipped binary was affected.

    Protocol correctness

    VANISHED (EARLIER) was sent after the FETCH responses, not before. RFC 7162 §3.2.6 makes the order a MUST and says why: the client shrinks its model by the vanished set first, so a FETCH arriving before it renumbers a mailbox the client still believes is larger. The new fixture builds a mailbox where both responses occur and asserts their order, which the old code fails by construction.

    Remote-MTA was omitted from the delivery-status report for a session that plainly happened, when a route or MX target is configured as an IP literal and therefore has no hostname. SMTPClientConnection now falls back to the address the connection actually went to. Found by the new DSN fixture on the gate's first run.

    One WriteReported return value in the accept path was discarded. The failure was already reported; what was missing was the consequence, which is what an administrator would actually be diagnosing — the message is delivered without its spam-score headers, so a rule matching those headers silently does not fire.

    Control Panel

    Several of these were verified by re-rendering the running window and reading the pixels or the visual tree, not by reasoning about the layout — which is how one wrong fix (raising MaxWidth when the star fraction was what bound) got caught instead of shipped. The rest were verified by test or by walking the visual tree. Three defects in the sidebar came from exactly such a screenshot pass — a search button reading "SearchCtrl+K" as one word, a group label ending mid-word with no ellipsis, and a pane too narrow for its own longest label — none of which could ever fail a build, a test or a review.

    The sign-in screen could stop responding. Connecting is three blocking COM calls and all three ran on the UI thread behind a single 50 ms yield. An unreachable host now fails in about two seconds with a sentence that names the firewall case, via an off-thread reachability probe that gives every address the name resolves to a turn (a host reachable over IPv4 but not IPv6 was otherwise reported as down). Negative control: point it at TEST-NET-1, which swallows packets rather than refusing them; without the timeout it sits there for RPC's own patience. This narrows the window rather than closing it — see Known limitations. Four of AccountDialog's eleven tabs could not be clicked. Its headers extend 1321 pixels inside a 372-pixel viewport, focusable with arrow keys, unreachable with a pointer, with nothing on screen to suggest anything had been cut off. The wheel now scrolls the strip horizontally, chevrons appear only while there is somewhere to go and disable at their own end, and keyboard selection scrolls the selected tab into view. Fixed in the TabControl template, so DomainDialog, RouteDialog and IPRangeDialog are covered too. The anti-spam page had two tabs called "General" and two called "SpamAssassin", and the only route to the quarantine settings was to notice that the second identically named tab was not the same as the first. Tab() is get-or-create now, pinned by a source-scanning test that asserts the helper looks for an existing tab. Both chart cards on the dashboard rendered pure white in the dark theme, along with their titles, buttons, legend and summary line — a whole row of controls nobody could see, on the page an administrator looks at first. A Fluent control fill is an overlay (#0FFFFFFF), and the code was discarding the alpha and keeping the RGB. The alpha is now composited over the theme's own background. Five tests pin the arithmetic with measured numbers, including a negative control and the light-theme mirror image. Light-theme accent buttons rendered near-black on blue — the forum thread's one complaint about the look. An app-level implicit TextBlock style also reaches the TextBlocks that ContentPresenter generates inside control templates, and a style setter outranks property inheritance, so it overrode WPF-UI's on-accent foreground (and the tab and sidebar hover/selected swaps). The default now arrives through inheritance, which templates can override. The Quarantine grid was missing two of its five columns (SizeToCells measures the cells, so on an empty grid Held and Score collapsed to zero width and their headers vanished with them), and the Quarantine page loaded once in its constructor and was cached forever, showing a stale list on the one page whose list is the only copy of what it shows. Three pages drew tables out of padded spaces — message trace, quarantine, app passwords — with no headers, no sorting, no per-field copy, and a screen reader reading each row as one long line with the alignment spaces in it. All three are DataGrids now. Destructive confirmations were painted as the safe choice. The crash dialog's Restart got the danger appearance while the quarantine delete — the only copy of a message — got the benign accent. Breadcrumb group segments silently did nothing after group headers became icon-plus-text panels while the reveal still matched Header as string. The checked-in generated settings index had not compiled since a label containing an escaped quote landed — the generator's naive [^"]* capture stopped early and emitted a string ending in a lone backslash.

    Hardening and diagnostics

    Described as hardening because they were not reproduced as failures or do not have a negative control.

    Custom virus-scanner timeouts were reported as "Unable to launch executable". ProcessLauncher::Launch returned false for two opposite reasons — CreateProcess failed, or the process ran past ExternalProcessTimeout and was terminated — and both scanner call sites reported either as a path problem, sending an operator whose scanner was merely slow to check an executable path that was perfectly correct. Both paths now name the timeout as a timeout and quote the command line and the setting to change. PreferredHashAlgorithm floor — see upgrade note 12. Its test asserts the part that would be easy to get wrong: not that the value is refused, but that a password set while it was configured still authenticates afterwards. An audit of the remaining credential stores — route relayer passwords, fetch-account passwords, SSL private-key passwords, per-domain relay password — found them all going through Crypt::ProtectSecret, with no Blowfish and no plaintext remaining. CodeQL sweep of the Control Panel (Services and Views layers), every change behaviour-preserving, with the loops that genuinely could not be converted left alone and the reason recorded: running-max/min accumulators, stateful dedup loops, and a late-bound COM call whose exception is already caught. Open security alerts triaged from 50 to 5, by reading each one. Three missed-where findings fixed; three shown to be unfixable because their conditions depend on state the loop mutates; 27 useless-upcast dismissed as false positives on dynamic COM operands — and after the same pattern re-fired on an edited file, excluded durably in the CodeQL configuration with the reasoning recorded (an implicit conversion from dynamic makes the enclosing expression dynamic, moving overload resolution to runtime); 16 findings in generated WPF obj/ output excluded by a config filter, verified working before the residue was dismissed; and the test Dockerfile pinned by digest with the honest other half written down — a pinned digest does not pick up base-image security updates. The five that remain are repository-settings findings (OpenSSF Scorecard), not code. The Code quality page's 1,482 findings were resolved at the source. That page was fed by a GitHub-managed buildless scan enabled alongside the repository's own CodeQL workflow — and it ignored the repository's analysis configuration entirely, re-reporting without type information the exact rule categories this project had excluded with measured reasoning (412 findings against the documented COM-exception policy, 311 of a rule earlier verified false-positive here, ~97 flags on the WPF bool? IsChecked == true idiom that is not simplifiable). Nineteen of its findings were genuine enough to triage properly: six produced code fixes or verified behaviour-identical simplifications in the Control Panel, one produced a defensive guard on an async void handler, and the rest were confirmed false positives with the reasoning recorded. The managed scan was then switched off in favour of the configured analysis — which was itself extended to build and scan every shipped .NET tool (DBUpdater, DBSetup, DBSetupQuick, DataDirectorySynchronizer, ImportTool, Shared), because the triage revealed those had only ever been scanned by the scanner being switched off. A second real find from the same triage: the config's obj/ exclusion never worked for traced builds — CodeQL path filters only restrict extraction for interpreted languages — so generated XAML-compiler output is now dropped from the SARIF between the analyse and upload steps, which is the documented mechanism. A permanently-red CI check removed. prisma.yml had failed on every push since the day it was added and could never have done anything else: it is GitHub's sample Prisma Cloud workflow, wanting three secrets this repository does not have, still carrying the template's literal my-asset-name, scanning for CloudFormation templates in a Windows C++ mail server. A check that is always red is worse than no check, because it trains everyone to ignore the colour. Roadmap and capability-matrix corrections. Two read-only audits found roughly fifty stale or false entries — including four rows citing schema 6006 over 57 upgrade steps against a tree then at 6022 over 73, four rows that opened by declaring a shipped feature "Not implemented", and one row whose supporting citation said the opposite of the row. Upgrading.md's backend-reach table had drifted a fourth time and now states which steps ship without transcribing counts that go stale.

    Known limitations

    Named explicitly, in the project's own house style.

    Trust and forgery

    MAIL FROM is not bound to the authenticated account anywhere in this fork. Authentication proves possession of valid credentials on this server, not ownership of the address in the envelope. This bounds distribution-list moderation directly: any valid credential holder on this server could forge a moderator approval. That is the trust model the existing require-auth machinery has always had; the moderation bypass extends it rather than weakening it, and a forged unauthenticated approval lands in front of the real moderator rather than going out to the list. The out-of-office "internal sender" classification is by envelope sender, which is forgeable. The queued message carries no authentication state, so authentication cannot factor in without a schema change. Installations that require SMTP AUTH for local sender addresses close the hole at the door; elsewhere the internal text is a courtesy gradient and never access control. The sender blacklist matches the envelope sender, which the sender controls completely. It is not an anti-spoofing tool and must not be sold as one. Per-account spam thresholds do not act without a score this server recorded, which requires the anti-spam "add reason to header" setting to be on. With it off, no override acts and the account keeps server-wide behaviour.

    Out-of-office

    An account with both a stored vacation message and a Sieve vacation action sends two replies to one message. The stored responder and the Sieve responder run at different points of delivery and do not consult each other. Each is individually loop-safe; the pair is redundant. Use one mechanism per account until they are reconciled — reconciling them properly needs delivery-order rework and was deliberately not rushed into this release.

    Byte fidelity

    Relay of a binary message is refused, never converted — 554 5.6.3 at RCPT, plus a delivery-time backstop DSN for the indirect paths. There is no client-side CHUNKING in this server's delivery client to convert to. Forwarding is not byte-safe. SMTPForwarding round-trips every forwarded copy through MIME parse and re-serialise rather than copying bytes. SpamAssassin rewrites and signature appending are likewise not byte-safe. All three fire only in those configurations; the default external-inbound-to-local-mailbox path is fully byte-preserving.

    Shared mailboxes

    \Seen in a shared mailbox is shared state, not per-user. Marking a message seen marks it seen for the owner and every other delegate. There is no separate switch for the #Users namespace — it is the ACL enforcement switch, and turning that off restores "public folders are open to everyone". There is no Control Panel surface for ACL grants. Delegation is granted from an IMAP client that speaks SETACL.

    Coverage and reach

    Fourteen new settings have no README entry, nine of them no GUI either — see upgrade note 14. The privacy tooling is COM-only and server-admin only; export is all-or-nothing. OTLP metrics carry the event counters only. Nine gauges — queue depth and age, database probe, certificate expiry, work-queue depth and blocked tasks, connection pool, state, build_info, start_time_seconds — are only available by scraping /metrics. The full-text index never answers a search, it only narrows the scan. Enabling it changes cost, never results. It requires both IndexerFullText=1 and message indexing to be enabled — the latter has a Control Panel checkbox. The filtering hook speaks plain HTTP only, so the engine belongs on this machine or a trusted network; and it cannot express a hard 5xx at DATA independent of your thresholds. Messages between FilterHookMaxMessageSizeKB (default 10 MB) and the server's own size cap pass unfiltered, traced only at debug level. Proxy trust is per-source, not per-port, so a listed proxy must send its header on every connection; and the server cannot verify that your upstream is configured to send one. The Windows Event Log has no message file of its own. Without the best-effort EventLogMessages.dll registration, Event Viewer shows "description cannot be found" boilerplate above the full text. The Control Panel sign-in freeze is narrowed, not closed. A host that accepts TCP on port 135 and then stalls inside DCOM will still freeze the UI. The correct fix is a long-lived STA thread owning the session with every COM call marshalled onto it — a real change to how the whole application talks to the server, not something to smuggle in next to a progress ring. DatabaseStatementTimeout is untested on MySQL and PostgreSQL, which are the two backends it was built for. On MS SQL and SQL CE it takes effect only via the script runner's connections, not at connect time. Warm standby has never been rehearsed on a real two-machine pair. docs/WarmStandby.md is verified against the code, not against a failover. Distribution-list self-subscribe is not included. AUTH=SCRAM-SHA-256 remains unreachable over IMAP on a stock install — EnableImapSASLPlain ships as 0 and gates the whole IMAP AUTHENTICATE command rather than the PLAIN mechanism its name implies. SMTP and POP3 advertise and accept SCRAM-SHA-256 (and -PLUS under TLS) whenever authentication is on. Pre-existing; recorded because the IMAP half is exactly the "exists but inert by default" shape this project's capability audit exists to catch.

    New settings

    All in [Settings] in hMailServer.INI. Bold = reachable from the Control Panel; the rest are INI-only.

    KeyDefaultWhat it does
    IndexerFullText0Full-text term index behind IMAP SEARCH BODY/TEXT. Also requires message indexing (Control Panel checkbox / Settings.MessageIndexing).
    IndexerFullTextBatchSize250Message-id range width per backfill pass.
    IndexerFullTextMinTokenLength3Shortest search run the index may answer for. Lowering it later stays sound without a re-index.
    IndexerFullTextMaxTokensPerMessage2048Distinct terms per message before it is marked always-scanned.
    MinimumFreeDiskSpaceMB100Free-space floor; below it new mail is refused temporarily. 0 disables.
    DiskSpaceWarningThresholdMB1024One application-log line when the volume crosses into the warning band. 0 disables.
    DatabaseStatementTimeout30Seconds a single statement may run. 0 = no limit. Applied at connect on PostgreSQL/MySQL; reaches MS SQL/SQL CE only via maintenance-script connections.
    IMAPExpungeRetentionRecords5000QRESYNC tombstones kept per mailbox. 0 keeps every one — and defers the first-start bulk prune.
    DmarcRptSchemaVersion11 = RFC 7489; 2 = RFC 9990. Invalid values reported as HM6210 and treated as 1.
    FilterHookUrl(empty)External filtering engine. Empty disables.
    FilterHookTimeoutSeconds10Whole exchange: connect, write and read.
    FilterHookFailClosed0Off = a filter outage lets mail through.
    FilterHookRejectScore100What the engine's reject is worth, in the same units as every other spam test.
    FilterHookMaxMessageSizeKB10240Messages above this are passed without being sent. 0 = no ceiling.
    SMTPProxyProtocolEnabled0HAProxy PROXY protocol v1/v2 on the SMTP listener.
    SMTPProxyProtocolTrustedIPs(empty)Comma-separated addresses/CIDRs, matched against the real TCP peer. Listing an address makes the header required from it.
    SMTPXClientEnabled0Postfix XCLIENT. Not advertised to untrusted peers.
    SMTPXClientTrustedIPs(empty)As above.
    WindowsEventLogEnabled1Forward ErrorManager errors to the Windows Application log.
    WindowsEventLogLevel21 Critical, 2 +High, 3 +Medium, 4 +Low.
    OtelMetricsEndpoint(empty)OTLP metrics. Empty disables.
    OtelLogsEndpoint(empty)OTLP logs. Empty disables.
    OtelMetricsInterval60Seconds between metric pushes; clamped 5–3600.
    New error codes

    Twenty new codes report through ErrorManager, which matters more than usual because the Windows Event Log sink is on by default and forwards Critical and High severities. All twenty, so nothing reaches your Event Viewer or application log without a published meaning:

    CodeWhereMeaning
    HM5528settings loadPreferredHashAlgorithm below SHA256 refused; PBKDF2 substituted.
    HM5540filter hookFilterHookUrl is not an http:// URL; nothing can be sent to the engine.
    HM5541filter hookEngine did not answer and FilterHookFailClosed is on; mail is being refused until it responds.
    HM6210settings loadInvalid DmarcRptSchemaVersion; treated as 1.
    HM6230disk monitorFree space crossed the warning band or the floor. High severity — this one reaches the event log.
    HM6250privacy toolingAddress-trace erasure finished incomplete; the report lists what survived.
    HM6251privacy toolingUnsafe path segment refused before any filesystem path was built.
    HM6252shared mailboxesEnumerating folder shares failed — shared folders will be missing from LIST, which otherwise looks exactly like a permissions mistake.
    HM6260full-text indexerBackfill or indexing error; the index degrades to scanning, never to wrong results.
    HM6280PROXY protocolInvalid PROXY header from a trusted source; the connection is closed, as the specification requires.
    HM6281PROXY/XCLIENTMalformed trusted-list entry. It fails closed — trusts nobody — which from the administrator's chair looks like the feature not working, so it is reported once rather than per connection.
    HM6300shared mailboxesDelegated RENAME failed partway; the next refresh reads the true tree back from the database. High severity.
    HM6320out-of-officeClearing a domain's reply-rate-limit slots failed when the domain reply was turned off. High severity.
    HM6340BINARYMIMEBinary message refused onward relay; a DSN went to the sender, this code goes to you.
    HM6350per-account spamThe delete-threshold override could not act; the message was kept, because deleting on a failure would be silent loss.
    HM6351per-account spamSpam mark removed but the header rewrite failed; behaviour is correct, the copy's headers are stale.
    HM6360list moderationForwarding to the moderator failed; the posting was refused rather than distributed unapproved.
    HM6361list moderationThe moderator address itself does not resolve — moderation cannot work and would otherwise look like it was working.
    HM6362list bounce addressThe configured BounceAddress is invalid; refused loudly, envelope left as it arrived, because the misconfiguration invites a mail loop.
    HM6363list moderationNested-list moderation depth refused once, with the reason, instead of failing at every level on the way back up.
    Downloads and verification

    Every asset on this release is signed, and the SBOMs describe what is inside:

    hMailServer-6.2.23-alpha1-x64.exe — the installer. hmailserver.spdx.json and hmailserver.cyclonedx.json — SBOMs (Syft, with the native C++ dependencies merged in, which dotnet-only scanners miss). One *.cosign.bundle per asset — keyless Sigstore signatures tied to this repository's GitHub Actions identity.

    Verify a download with

    cosign verify-blob \ --bundle hMailServer-6.2.23-alpha1-x64.exe.cosign.bundle \ --certificate-identity-regexp '^https://github\.com/Progressiverobot/hmailserver/' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ hMailServer-6.2.23-alpha1-x64.exe

    A release with no bundles, or a bundle that fails verification, is not a release this project published.

    Verification status

    Regression suite: 1831/1831 on the exact release tree — every test, nothing skipped. Two full runs on 21 August: one on the stamped tree, then — because quality fixes landed after it and a run is void the moment anything changes — a second complete run on this exact tree, also 1831/1831. Control Panel suite: 645/645 on the stamped tree. Schema chain: check-schema-versions.ps1 (76 steps, chain 0 → 6025, 54 probes) and check-db-scripts.ps1 (a real throwaway SQL CE database built from CreateTablesMSSQL.sql, 274 statements) both pass at the release tree. The reviewed concern that hm_domains' declared row size exceeds SQL CE's documented 8,060-byte limit was tested rather than argued: a row with every variable-length column filled to its declared maximum — 9,260 in-row bytes — inserts successfully. SQL CE does not enforce the documented limit against declared sizes of variable-length columns. COM compatibility: declaration order of every dual interface compared against the v6.2.21 IDL — zero shifted interfaces after the repair, four before. Version stamp: Version.h 6.2.23 / build 32; installer and all seven tool projects stamped; the release grep for the previous version returns only historical records. Not run: the installer has not been executed on the development machine (a standing project rule — CI's installer smoke test covers install on a clean runner). The end-to-end upgrade path across 6011 → 6025 on a populated production-scale database has not been exercised anywhere; that is part of what "alpha" means.

    If something goes wrong

    The server refuses to run against a schema version it does not match, in either direction, with one of two messages worth searching for:

    The database is too old for this version of hMailServer. Please run hMailServer Database updater (DBUpdater.exe) to upgrade it. — run DBUpdater.exe from the installation's Bin folder. It picks up from whichever version the database actually reports, so a retry after a partial upgrade resumes rather than restarting, and the log names the step it stopped on. The database is too new for this version of hMailServer. Please upgrade hMailServer. — you are running an older server against a newer schema. Install the matching server rather than trying to downgrade the database.

    Note that the installer reports a failed schema upgrade in a dialog and then carries on to start the service and finish the wizard. If you script installs, treat that dialog's appearance — or an hm_dbversion still on the old value afterwards — as the failure. Do not treat "the installer finished" as success.

    Release facts
    Version
    6.2.23-alpha1pre-release
    Signed
    21 August 2026Sigstore transparency log
    Packages
    1Windows and Linux, x86-64 and AArch64
    Database schema
    6025upgrades in place
    Installer SHA-256
    0c7cbb5a160b…c91ca7a5hMailServer-6.2.23-alpha1-x64.exe
    Every file in 6.2.23-alpha1

    Full SHA-256 of each file as recomputed on this server, with the signature bundles beside it. The SBOMs and provenance are not needed to install; they let anyone audit exactly what was built.

    FileWhat it isSizeSHA-256Signature
    hMailServer-6.2.23-alpha1-x64.exeWindows installer · x6473.4 MB0c7cbb5a160b2ecc2866378819fc587f0c8f3fd481cecb785036b00fc91ca7a5Signature: cosign bundle
    hmailserver.spdx.jsonSBOM (SPDX)Every component in the build, in SPDX JSON.152 KB4368df02ff452338715fa9793cbe7eb86b80deba1d17283f32aaf266535ad78eSignature: cosign bundle
    hmailserver.cyclonedx.jsonSBOM (CycloneDX)The same inventory in CycloneDX JSON.90 KB8a4c8ac506e6d9bd67a2fd8b34a65fc7fbe6aa8e76eae5c27a86b12a3624eccbSignature: cosign bundle
    Progressiverobot-hmailserver-attestation-42219651.sigstore.jsonBuild attestationGitHub artifact attestation for the release, as a Sigstore bundle.4 KBdebc24f5fb95611533e328f3608f1ad05b23e71595786c6c82e87990a257772a
  8. 6.2.22-pre6

    Pre-releaseSigned 20 August 20261 package · schema 6022

    Same build as pre5, republished with its supply-chain assets.

    Release notes, checksums and signatures
    Windows

    Windows 10 (version 1607 or later) and Windows 11, or Windows Server 2016 and newer; 64-bit only. Run the installer as an administrator: it upgrades any existing 5.x or 6.x installation in place.

    • hMailServer-6.2.22-x64.exex64 · 73.3 MB
      Download
      SHA-256eb565d4b604b…b384cdcdSignature: cosign bundle
    What’s new in 6.2.22-pre6

    Same build as pre5, republished with its supply-chain assets. pre5 went out carrying only the installer: this repository has immutable releases enabled, so the SBOM and signing workflows could not attach anything after publication. That is fixed — assets are now attached while the release is still a draft — but a tag used by an immutable release can never be reused, so the corrected build had to come back under a new one. The code is identical to pre5 (build 30, commit bf0d38a); what is new is the SPDX and CycloneDX SBOMs and a cosign bundle for every asset, the installer included.

    Full release notes for 6.2.22-pre6 8 sections

    This is pre-alpha. It is gated — 1721 regression tests, nothing skipped — and the installer is smoke-tested on a throwaway runner, but it has not been run in anger on a production server by anyone. Treat it as something to try on a spare box, not as an upgrade for a mail server people depend on.

    pre4 was withdrawn. Its installer could not create its own database: two SQL statements were written on consecutive lines, and the script parser splits commands on a blank line, so SQL Server Compact — what a default install uses — rejected the pair as one command. The install succeeded, the service started, and it listened on nothing. Nothing local caught it, because this project's test database is upgraded out of band and no test had ever taken the create path a fresh install takes; the installer smoke test found it after publication. There is now a check-db-scripts step that builds a throwaway database from the real create script, and it is part of the release checklist. The same defect was found and fixed in the 6020 upgrade step, which only ever shipped in pre4. The v6.2.22-pre4 tag still exists (tag deletion is blocked by a repository rule) but has no release attached; do not build from it.

    Sixteen commits on from pre3.

    Upgrading from pre3 requires DBUpdater — the schema moves from 6019 to 6022 in three steps.

    The theme is standards catching up with reality: DMARCbis and the RFCs around it were published while this fork was being written, certificate lifetimes are falling on a schedule, and several things this server did were correct when they were written and are not any more.

    DMARC, brought up to DMARCbis

    The DNS tree walk (RFC 9989 §4.10) replaces the Public Suffix List for deciding whether two names belong to the same organization — which is what DMARC's default relaxed alignment compares. This is not tidying: where a sender relies on tree-walk semantics, a list-based answer is a wrong policy decision rather than a soft failure, in both directions. The list is kept as the DmarcTreeWalkEnabled=0 escape hatch and as the answer whenever a lookup fails transiently — a resolver outage must not silently turn relaxed alignment into strict. Bounded at eight queries per domain and cached for five minutes, because DMARC needs an organizational domain for the author domain and every identifier aligned against it: uncached, one message could cost two dozen lookups. The np= tag (RFC 9989 §5.5.4) — the policy for subdomains that do not exist. p= covers the domain and sp= its subdomains, and between them they left the cheapest forgery there is uncovered: a phisher does not need a subdomain delegated to them, only a From header that says accounts.thebank.test, and no domain owner can publish a record for a name they have never heard of. The whole tag turns on NXDOMAIN versus NODATA, so the resolver now reports the RCODE. A subdomain holding only an MX or a TXT record exists, and treating those as absent would apply np=reject to real parts of a sender's own estate. Aggregate reports name the selector. RFC 7489 has a <selector> inside auth_results/dkim and it was never emitted, so a domain owner reading their own report could see that something of theirs signed a message but not which key — the one thing they need during a rotation or after a compromise. The SPF <scope> is now emitted for the same reason.

    Algorithms that stopped being safe

    DKIM rsa-sha1 is refused (RFC 8301). This server both signed and verified with it. A DKIM signature is an assertion of identity, DMARC alignment is built on it, and SHA-1 chosen-prefix collisions have been practical since 2020 — a forgeable signature is worse than an absent one, because it carries the signer's domain past an aligned DMARC check. A domain configured to sign with it is now signed rsa-sha256 instead and told so. DkimAcceptSha1=1 restores both halves for a correspondent who has not moved. DKIM keys below 1024 bits are refused (RFC 8301 §3.2). Nothing checked, so a domain publishing a 512-bit key — several did for years, because a smaller key fits more comfortably in a TXT record — had its signatures verified as normal. A 512-bit key is factorable in hours on rented hardware. SPF void-lookup limit (RFC 7208 §4.6.4). The ten-term limit bounds what a policy can demand; it does not bound what a policy can waste. Ten terms naming hosts that do not exist cost a full resolution each for a record that can never match. Capped at two, SpfVoidLookupLimit to change or disable.

    Certificates, before the lifetimes drop

    The ACME renewal window is now a fraction of the certificate's lifetime. Thirty days was right while certificates lasted ninety and stops being right the moment they do not — Let's Encrypt defaults to 64 days from February 2027, the maximum falls to 100 days in March 2027 and to 47 in March 2029. A fixed 30-day window against a 47-day certificate starts renewing seventeen days after issuance and never stops, which is how an operator gets rate-limited by their own CA. Renewal now begins two thirds through whatever lifetime the certificate has, with a one-day floor. ARI (RFC 9773). The CA's renewalInfo endpoint is consulted hourly and its suggested window wins when offered, so a CA can pull renewals forward during a mass revocation. A point is chosen inside the window rather than at its start — renewing at start moves the thundering herd rather than removing it — derived from the certificate's own identifier so it is stable across the hourly re-checks. Whatever the CA says is clamped to a day before expiry: ARI is advisory and the certificate is not. A CA without ARI, a failed request or an unreadable answer all mean "decide for yourself", never "do not renew".

    Mail that used to be accepted and then bounced

    A full mailbox is refused during the SMTP conversation with 452 4.2.2. Quotas were checked only at delivery, so a message for an over-quota mailbox was accepted and then answered with a non-delivery report addressed to the envelope sender — which the traffic that fills mailboxes fastest has forged. That report went from this server to somebody who sent nothing: backscatter, with this server's reputation paying for the forgery. Temporary rather than permanent, so a legitimate sender's mail waits in its queue. RejectFullMailboxAtRcpt=0 restores the old behaviour. Quota warnings (schema 6022). Nothing told an account holder their mailbox was filling up, so the first they heard of it was mail no longer arriving. QuotaWarningPercent (default 90, 0 disables) sends one notice — crossing a threshold is an event, not a state, so nothing has to be remembered and emptying the mailbox and filling it again warns again.

    Asked for

    Per-domain outbound relay (schema 6021) — discussion #31. Each domain can have its own relay host, port, credentials and transport security, matched by the sending domain. Deliberately a different question from a route: a route is about where mail is going, this is about where it leaves from. Both can apply, so the order is fixed and tested — route, then the sending domain's relay, then the server-wide relayer. An empty relay host means the domain has no opinion, which is what every existing domain has.

    Operations

    A queryable message trace (schema 6020). "What happened to the message Jane sent at 14:20" used to mean grepping several log files and hoping the relevant one had not rotated. Off by default, because it records who corresponds with whom. Archive retention. ArchiveDir is a raw copy of every message that passes through the server and nothing ever removed anything from it. ArchiveRetentionDays prunes it; 0 by default, because an archive is usually kept for a legal reason and a server that started deleting from one on upgrade would be destroying what it was told to keep. Only ever removes .eml files, and does not follow symlinks or junctions. Per-domain metric labels. Every counter was server-wide, which is what stopped metrics becoming reporting. MetricsPerDomainEnabled adds a domain= label to message counters. Off by default — every label value is a separate time series — and the label set is bounded by construction: only domains this server hosts are ever labelled, so no amount of mail from strangers can create series.

    Corrections to the record

    THREAD (RFC 5256) was listed as not implemented and has been implemented all along — both algorithms, the capability, UID THREAD, thirteen tests. The roadmap row was written from an assumption and is corrected rather than the work repeated. The per-domain metrics row claimed every counter was global; protocol=, queue= and build-info labels already existed.

    Known limitations

    The DMARCbis 2.0 aggregate-report namespace (RFC 9990) is not implemented; reports are still the RFC 7489 form, which every consumer understands. Per-account metric labels are not implemented and are not planned in this shape: accounts are unbounded in a way domains are not. Archive retention has no per-domain scope, index, immutability or legal hold. This release has not been installed by the maintainer on a production server. The installer is smoke-tested on a throwaway CI runner only.

    Release facts
    Version
    6.2.22-pre6pre-release
    Signed
    20 August 2026Sigstore transparency log
    Packages
    1Windows and Linux, x86-64 and AArch64
    Database schema
    6022upgrades in place
    Installer SHA-256
    eb565d4b604b…b384cdcdhMailServer-6.2.22-x64.exe
    Every file in 6.2.22-pre6

    Full SHA-256 of each file as recomputed on this server, with the signature bundles beside it. The SBOMs and provenance are not needed to install; they let anyone audit exactly what was built.

    FileWhat it isSizeSHA-256Signature
    hMailServer-6.2.22-x64.exeWindows installer · x6473.3 MBeb565d4b604b64c78e513a97e48e54716f2a059107990badc7d16b98b384cdcdSignature: cosign bundle
    hmailserver.spdx.jsonSBOM (SPDX)Every component in the build, in SPDX JSON.160 KB6f2f2b774cc6b2affa11e33505d7a96f86dde57b5ed56d48c1eb44abb5c60f3bSignature: cosign bundle
    hmailserver.cyclonedx.jsonSBOM (CycloneDX)The same inventory in CycloneDX JSON.96 KB52edf8838e670be0eb59ff34055276e09a0bbf608031ac956d08a43bdfe18928Signature: cosign bundle
    Progressiverobot-hmailserver-attestation-42002219.sigstore.jsonBuild attestationGitHub artifact attestation for the release, as a Sigstore bundle.4 KB29af2ae1d3b84209484918113a71584508ed2df579897c2d8e4cfbce8f60b343
  9. 6.2.22-pre3

    Pre-releaseSigned 19 August 20261 package · schema 6019

    A third pre-release of 6.2.22, fifteen commits on from pre2. Still a pre-release: it is gated but not yet run in anger anywhere.

    Release notes, checksums and signatures
    Windows

    Windows 10 (version 1607 or later) and Windows 11, or Windows Server 2016 and newer; 64-bit only. Run the installer as an administrator: it upgrades any existing 5.x or 6.x installation in place.

    • hMailServer-6.2.22-x64.exex64 · 73.2 MB
      Download
      SHA-25624e7e6c83ffb…5fe0f014Signature: cosign bundle
    What’s new in 6.2.22-pre3

    A third pre-release of 6.2.22, fifteen commits on from pre2. Still a pre-release: it is gated but not yet run in anger anywhere.

    Full release notes for 6.2.22-pre3 5 sections

    Upgrading from pre2 requires DBUpdater — the database schema moves from 6015 to 6019 in four steps.

    The theme of this one is authentication. The roadmap has said for months that per-account two-factor authentication was impossible here and named the reason; that chain is now complete, and three of the four schema steps below exist to support it. Two-factor authentication, and the chain that had to come first

    App passwords (schema 6016). A per-account credential, revocable on its own, that authenticates over IMAP, POP3 and SMTP alongside the account's own password. It is a prerequisite rather than a convenience: a mail client has nowhere to type a code, so an account required to present one could not be opened by any client at all. The secret exists once — 20 symbols from a 30-character alphabet, about 98 bits, from OpenSSL's CSPRNG with rejection sampling. Deliberately not PasswordGenerator::Generate, which returns the first twelve hex characters of a GUID: 48 bits, for a credential typed into a client once and then left for years. App passwords are tried only after the account's own password has failed — checking five of them first would add half a second of Argon2id to every ordinary logon — and on a server that has never issued one, a cached existence flag answers without touching the database at all. Per-account 2FA (schema 6017). TOTP has been in this product for years and protected exactly one thing: the admin tool's own logon, implemented in C# in the Control Panel and checked after the server had already accepted the password. The server had no idea the feature existed. It does now. Once a secret is enrolled, the account password stops being a mailbox credential and an app password becomes the only one, which is precisely what app passwords were built to make survivable. RFC 6238 with the parameters authenticator apps actually implement, and the RFC's published vectors are pinned in the server's self-tests — including the 20000000000 row, which also proves the counter stays 64-bit past 2038. The regression fixture then computes its codes with .NET's own HMACSHA1 rather than anything the server owns, because an implementation that agrees only with itself will agree with itself about the wrong answer. A password policy the server actually enforces. IsStrongPassword has existed for years, carries a hard-coded deny-list, is reachable over COM — and nothing in the server has ever called it. Every mailbox on every installation could be test. Five settings now decide, plus one rule that is always on: a password may not contain the account name. Enforced where a password is chosen and nowhere that verifies or re-hashes an existing one, because a policy applied to verification locks people out of mailboxes they can open today. Password expiry and history (schema 6019). Shipped as a pair, because expiry without history teaches people to alternate between two passwords — worse than not expiring at all. Expiry is the half with a caveat, and it is written into the setting itself: this server has no self-service password change, so an expired password can only be reset by an administrator. Existing app passwords keep working, Active Directory accounts are exempt, and an unreadable stamp is never treated as expired — the failure that would otherwise lock out every account on any server that upgrades.

    Anti-spam

    Admin-reviewable quarantine (schema 6018). Messages the server would otherwise have refused are held in a store an administrator can list, read, release or delete, with a review queue in the Control Panel. The design turns on one fact: refusing happens during the SMTP conversation with a 550, so the message is never accepted and there is nothing to review. Quarantining answers 250 — the sender believes it was delivered and will not retry, which is what makes a false positive recoverable without bouncing to a return path that is probably forged, and also means this store is the only place that message now exists. Post-transmission verdicts only, because a verdict reached before DATA has no message to hold. A quarantine that fails to store falls through to refusing: accepting mail that was not actually stored would turn a spam refusal into silent deletion. Releasing re-enters at the delivery queue rather than at SMTP, so the filters that quarantined it cannot undo the administrator's decision. Off by default.

    IMAP and POP3

    FETCH BINARY on a composite section returned an empty literal — the whole message, a multipart part and a message/rfc822 part all came back as nothing, and BINARY.SIZE agreed with them, so the answer was internally consistent and completely wrong. Shipped as a documented limitation in pre2 and fixed here. BINARY now also announces content with literal8 (~{n}) as RFC 3516 requires, because decoded bytes may contain NUL and a plain literal cannot carry one. POP3 EXPIRE and LOGIN-DELAY (RFC 2449). EXPIRE is answered NEVER, which is this server's actual retention policy rather than a placeholder. LOGIN-DELAY declares and enforces a minimum interval between logins, refusing an early one with -ERR [LOGIN-DELAY] — the code a conforming client backs off from, rather than a reply indistinguishable from a wrong password. Off by default.

    OAuth2

    ES256 tokens now verify. The obstacle was a format mismatch rather than a missing algorithm: JWS carries an ECDSA signature as the raw R||S pair while OpenSSL verifies X9.62 DER. The transcode is pinned against OpenSSL's own encoder, case by case, because one checked by round-tripping through its own decoder proves only that it agrees with itself. The configured public key is now also required to be the kind the algorithm names — an EC key against RS256, or RSA against ES256, is refused on the key type rather than surfacing as a signature failure. And implementing an algorithm does not enable it: a valid ES256 token is still refused while the allow-list omits it.

    The test suite stopped answering to somebody else's DNS

    Not a server change, but the reason four gate runs failed for reasons unrelated to the code under test, and worth recording because it cost most of a night.

    Ten tests resolved live third-party names. Each red gate exposed a fresh set — six, then one, then two, then four — and the timing was the tell rather than anything in the messages: three DKIM verification tests took 24-35 seconds each instead of one, because a DKIM key that cannot be fetched is a temporary error, so the message is accepted and a test asserting it is rejected fails while reporting nothing whatsoever about DKIM.

    Ten are now served from a local zone. In three cases the assertions got stronger rather than merely faster: two fixtures used to compare the server's answer against a lookup the test process made of the same name — two live lookups that merely had to agree with each other — and now check a value the fixture chose.

    A separate defect underneath cost 653 failures in one run: an error provoked by a test that has already failed goes on to fail every test after it. Teardown now prints the ERROR log with the failure that caused it and clears it until it stays clear, and drains the delivery queue as well — a queued message is a delivery that has not happened yet, and the error it eventually raises belongs to a test that has not started.

    The resolver turned out to be the real answer. Making a fast resolver the machine's primary took the suite straight to green; as a secondary it made things worse, because Windows falls through only when the first server does not answer at all, never merely because it is slow.

    Provenance

    Gated at 1700/1700 on the stamped binary (build 28, commit 5f7098f), with 630/630 Control Panel tests. SBOMs and cosign signatures are attached by CI after publication; the installer is smoke-tested by CI against this tag. The installer is deliberately not run on the build machine.

    Release facts
    Version
    6.2.22-pre3pre-release
    Signed
    19 August 2026Sigstore transparency log
    Packages
    1Windows and Linux, x86-64 and AArch64
    Database schema
    6019upgrades in place
    Installer SHA-256
    24e7e6c83ffb…5fe0f014hMailServer-6.2.22-x64.exe
    Every file in 6.2.22-pre3

    Full SHA-256 of each file as recomputed on this server, with the signature bundles beside it. The SBOMs and provenance are not needed to install; they let anyone audit exactly what was built.

    FileWhat it isSizeSHA-256Signature
    hMailServer-6.2.22-x64.exeWindows installer · x6473.2 MB24e7e6c83ffbbd852703d629257418c756072053acd2d7c7ee5ff4995fe0f014Signature: cosign bundle
    hmailserver.spdx.jsonSBOM (SPDX)Every component in the build, in SPDX JSON.148 KB027d9005c57116aca92ef7b648fb63987b4111553da151b2f5c394b1cb17e435
    hmailserver.cyclonedx.jsonSBOM (CycloneDX)The same inventory in CycloneDX JSON.88 KBf4cf722aaac5c21b5e682fcb18f3c2064a08b0daea5b0a1e1133453d5e767966
  10. 6.2.22-pre2

    Pre-releaseSigned 18 August 20261 package · schema 6015

    A second pre-release of 6.2.22, fifteen commits on from pre1. Still a pre-release: it is gated but not yet run in anger anywhere.

    Release notes, checksums and signatures
    Windows

    Windows 10 (version 1607 or later) and Windows 11, or Windows Server 2016 and newer; 64-bit only. Run the installer as an administrator: it upgrades any existing 5.x or 6.x installation in place.

    • hMailServer-6.2.22-x64.exex64 · 73.2 MB
      Download
      SHA-25608b1d7a45a84…e2bcd9f7Signature: cosign bundle
    What’s new in 6.2.22-pre2

    A second pre-release of 6.2.22, fifteen commits on from pre1. Still a pre-release: it is gated but not yet run in anger anywhere.

    Full release notes for 6.2.22-pre2 8 sections

    Upgrading from pre1 requires DBUpdater — the database schema moves to 6015. Reporting: this server now sends the reports it has always asked others for

    DMARC aggregate reports (rua, RFC 7489). hMailServer has consumed DMARC policy for years and produced nothing for the domains whose mail it receives. It now aggregates every evaluation per UTC day and per policy domain — passes included, because a report carrying only failures tells a domain owner nothing about whether their legitimate mail aligns — and mails the Appendix C XML to the addresses a domain asks for with rua=. Inert until DmarcRptFromAddress is set. RFC 7489 §7.1's external-destination verification is enforced: a rua target outside the policy domain's own organizational domain is only used when <policy-domain>._report._dmarc.<target-domain> says it wants the reports. Without that check, anyone could publish a rua naming somebody else's mailbox and point every reporter on the internet at it. Forensic (ruf) reports are deliberately not implemented: a failure report carries the whole message, and the large receivers stopped sending them years ago for that reason. TLS-RPT reporting became verifiable. The machinery existed, but the task only mails days that are over, so an administrator who had just configured it had nothing to look at until tomorrow — and nothing could test it. Utilities.SendTlsRptReports(IncludeCurrentDay) sends what has been collected now. It refuses, with the statistics preserved, when TlsRptFromAddress is unset, because the send pass pops each day destructively and a diagnostic must not destroy the data it was asked to show. SendDmarcReports is its twin.

    Authentication

    Per-account lockout. The per-IP auto-ban cannot see a distributed attack: a botnet spending one guess per address per account never crosses any single address's threshold. AccountLockoutThreshold counts by the name being guessed at — the one thing such an attack cannot vary — and locks it. It is enforced on every path a password can be guessed through, SCRAM included, and every mechanism that authenticates clears the counters on success. Off by default. A locked name is refused with the ordinary invalid-credentials reply, and that refusal is deliberately kept out of the per-IP auto-ban: the person most likely to retry a locked name is its owner, with the correct password, and charging those attempts would ban an innocent office NAT for everyone behind it. POP3 AUTH-RESP-CODE (RFC 3206). Credential failures now carry [AUTH] and a failed inbox load carries [SYS/TEMP], so a client can tell "your password is wrong" (reprompt) from "the server is having trouble" (retry quietly). Without it, a transient database problem made scheduled clients nag their users for a password that was never wrong. POP3 PIPELINING and IMPLEMENTATION (RFC 2449). PIPELINING was advertised only after batched commands were proved to work across single TCP segments and after confirming the STLS injection defence still holds; IMPLEMENTATION answers the product name and deliberately no version. APOP and the CRAM-MD5 family are now documented, at the code, as decisions rather than a backlog: each needs a cleartext-equivalent stored secret that this server's Argon2id/SCRAM store rightly cannot produce.

    Anti-virus

    A scan that could not run is no longer the same as a clean message. Previously such a message was delivered with one line in the error log, which is indistinguishable from having been scanned and found clean. AVFailAction names that posture: 0 (the default) keeps today's behaviour exactly, 1 holds the message, re-attempts it, and returns it to the sender rather than ever delivering it unscanned.

    IMAP

    BINARY (RFC 3516) — FETCH BINARY[…], BINARY.PEEK[…] and BINARY.SIZE[…] decode a part's Content-Transfer-Encoding server-side, so a client saving an attachment stops downloading base64 it must immediately decode itself. Partial ranges apply to the decoded bytes. APPEND accepts the literal8 (~{n}) form. OBJECTID (RFC 8474) — EMAILID follows a message through copies, so a client can recognise mail it has already downloaded when it reappears in another folder; MAILBOXID survives RENAME. THREADID is answered NIL, which the RFC provides for, rather than inventing one. Schema 6015.

    Fixes

    An idle server now stops in about half a second rather than up to two: the work-queue join retried on a 250ms interval after a 1ms wait, so whether a shutdown cost nothing or two seconds turned on scheduler timing the server does not control. hMailServer.exe /Test no longer terminates on an unhandled exception when the machine has no network, or when a third party's DNS breaks: the SPF self-test evaluated a live policy for a domain that now answers SERVFAIL, and signalled failure with a bare throw; — which outside a catch block calls std::terminate.

    Control Panel

    Nine settings the server acts on had no field at all and were reachable only by hand-editing the ini: the virus-scanner failure policy, the per-name lockout, DMARC report settings, and the POP3 fetcher's OAuth2 host list. Each now sits on the page where the question is asked — the scanner policy beside the scanners, the lockout beside auto-ban — and carries the caveat, not just a label.

    Known limitations in this pre-release

    FETCH BINARY on a composite section returns an empty literal. The whole message (BINARY[]), a section naming a multipart part, and a section naming a message/rfc822 part all come back empty, because the decode path serves the entity's raw text and a multipart entity's text stops at the first boundary. Leaf parts — the attachment case BINARY exists for, and the one the tests cover — are correct. Found by the pre-release review; the fix belongs with the partial-fetch code rather than in a release cut. DMARC report data lives in memory, so a restart before the daily send loses that day's statistics, and a resolver outage during the hourly run discards the day it was about to report. Aggregate reporting is best-effort observability, not a ledger.

    What the pre-release review changed

    The diff since pre1 was reviewed adversarially before this was cut, and three of the four things it found were in code or text written in the previous two days:

    DMARC reports could be schema-invalid — p=None or a missing p= reached the XML verbatim, and RFC 7489 types those as a closed lowercase enumeration, so validating receivers discarded the whole report while the log said it was sent. Policy tags are now mapped onto the enumerations. Report ids collided: the id (and the Message-ID built from it) was day + second + submitter with no mention of the domain being reported on, so two domains reported in the same second shared them. Fixed in both reporters. The new Control Panel field for FetchOAuth2Hosts said "empty = none" and rendered blank while the server defaults it to outlook.office365.com — and saving that page would have written the blank back, silently stopping Microsoft 365 fetching. Both OAuth2 host fields now show the real default.

    Provenance

    Built from 6f01f5f, gated at 1650/1650 on the stamped binary, with 630/630 Control Panel tests. SBOMs and cosign signatures are attached by CI after publication; the installer is smoke-tested by CI against this tag. The installer is deliberately not run on the build machine.

    Release facts
    Version
    6.2.22-pre2pre-release
    Signed
    18 August 2026Sigstore transparency log
    Packages
    1Windows and Linux, x86-64 and AArch64
    Database schema
    6015upgrades in place
    Installer SHA-256
    08b1d7a45a84…e2bcd9f7hMailServer-6.2.22-x64.exe
    Every file in 6.2.22-pre2

    Full SHA-256 of each file as recomputed on this server, with the signature bundles beside it. The SBOMs and provenance are not needed to install; they let anyone audit exactly what was built.

    FileWhat it isSizeSHA-256Signature
    hMailServer-6.2.22-x64.exeWindows installer · x6473.2 MB08b1d7a45a840eea648365cd979924dcc5bbad0b8d0427f10969b013e2bcd9f7Signature: cosign bundle
    hmailserver.spdx.jsonSBOM (SPDX)Every component in the build, in SPDX JSON.148 KB8154aa4ad8b52c97ab4caa03f9f00a020d84fd06948d3eee5aaeacee15c4212fSignature: cosign bundle
    hmailserver.cyclonedx.jsonSBOM (CycloneDX)The same inventory in CycloneDX JSON.88 KB4f69ce616cacd61e420e8ad50eea0670ba8352469401be59a1b5d76b4f2739d2Signature: cosign bundle
  11. 6.2.22-pre1

    Pre-releaseSigned 16 August 20261 package · schema 6014

    This is a pre-release. It carries everything merged since 6.2.21 — two database schema steps, a Sieve filtering wave, the Microsoft 365 OAuth switch-over…

    Release notes, checksums and signatures
    Windows

    Windows 10 (version 1607 or later) and Windows 11, or Windows Server 2016 and newer; 64-bit only. Run the installer as an administrator: it upgrades any existing 5.x or 6.x installation in place.

    • hMailServer-6.2.22-x64.exex64 · 73.2 MB
      Download
      SHA-256a9555de111cd…f713dfffSignature: cosign bundle
    What’s new in 6.2.22-pre1

    This is a pre-release. It carries everything merged since 6.2.21 — two database schema steps, a Sieve filtering wave, the Microsoft 365 OAuth switch-over, eleven IMAP extensions, and two reported bugs fixed, one of which broke certificate issuance entirely. The full 1,600-test regression suite passes on this exact build, but the changes are broad and recent; run it where you can afford to report a problem rather than where you cannot afford one.

    Full release notes for 6.2.22-pre1 4 sections

    Databases upgrade automatically (schema 6012 → 6014) via the bundled DBUpdater. Take a backup before installing a pre-release. The built-in ACME client never worked against a real Let's Encrypt — fixed (#34)

    Issue #34 reported certificate issuance failing every time against Let's Encrypt production with "Authorization offers no http-01 challenge", while an independent client succeeded within seconds — an excellent report that had already ruled out DNS, CAA, firewalling and stale authorizations.

    The cause was ours, and it was one line: boulder — the software behind Let's Encrypt staging and production — pretty-prints its JSON, "type": "http-01" with a space after the colon. The challenge locator searched for the compact form, "type":"http-01", which can never match a real Let's Encrypt response. Every issuance failed at the first authorization with a message that blamed the CA for a parse miss on our side. The rest of the client's JSON handling was already whitespace-tolerant; this one search was the only compact-form assumption — and the one an issuance cannot get past.

    The locator now tolerates any whitespace and checks context (the string must actually be a "type" key's value), and the real CA's response shape is pinned by self-tests so it cannot regress. The two diagnosability gaps the reporter named are also fixed: every failure message now says which domain it is about — three CNAME'd hostnames were indistinguishable in the log — and the no-challenge refusal now includes the CA's actual response, the only evidence of what was really offered. Log lines stopped going missing for forty minutes at a time (#33)

    With Keep log files open enabled, each log line sat in a ~4 KB write buffer until enough later lines arrived to push it out — the close-per-line that the setting skips was the only flush. On a quiet server the tail of a session stayed invisible, cut off mid-line, for as long as nothing else happened; issue #33's reporter measured forty minutes. The logger now hands every line to the operating system as it is written — a visibility flush, deliberately not a physical disk sync — so the file is complete up to the last event the moment it happened. The setting keeps its actual saving, which is the open/close per line. (Installations without the setting were never affected, which is also why 1,589 regression tests never noticed.) Microsoft 365: ready for the Basic-auth shutdown, four months early

    Microsoft shuts off Basic authentication for SMTP relay in December 2026. This build implements the XOAUTH2 bearer login Microsoft requires, in both directions:

    Outbound relay: when a route's destination is on the configured OAuth host list, AUTH presents a bearer token in Microsoft's exact one-round-trip XOAUTH2 shape instead of LOGIN with a password. Tokens are fetched with the client-credentials flow, cached to 80% of their lifetime, and invalidated on refusal. The host list is the safety: a token presented to the wrong relay is a leaked token, so unlisted relays keep their password login unchanged. POP3 collection: external accounts fetching from Microsoft 365 authenticate the same way. The password is deliberately not a fallback — a provider configured for OAuth has Basic auth off, and a quiet downgrade would send a password to a server that no longer wants one. The Control Panel's Relayer page carries the new settings with an explanation of each.

    Sieve filtering grows up

    The Sieve engine now implements and advertises, end to end, with delivery-side tests asserting where messages were actually filed: variables (RFC 5229), editheader (RFC 5293) with the protected-header rule, duplicate (RFC 7352), reject/ereject (RFC 5429) generating real DSNs, include (RFC 6609) with per-script variable scopes, enotify (RFC 5435, mailto), date/currentdate with :index and :zone (RFC 5260), spamtest (RFC 5235) reading the server's own verdict — never a header a sender could forge, ihave/environment (RFC 5463/5183), mailboxexists, and :regex — wearing the same runaway-pattern circuit breaker as the rules engine, because a hostile pattern should suspend itself, not the server.

    ManageSieve refusals now carry machine-readable response codes alongside the prose, and RENAMESCRIPT — promised by the capability line all along — is implemented.

    Eleven IMAP extensions, one theme: stop making clients guess

    APPENDLIMIT (RFC 7889): the upload size limit was always enforced but undiscoverable — clients learned it by uploading and failing. Now advertised exactly (per-account, in bytes), answered via STATUS, and oversize refusals carry TOOBIG. LITERAL- (RFC 7888): {n+} literals stop being answered with continuations the client never reads; a LOGIN or small APPEND now needs one round trip. LIST-STATUS (RFC 5819): a client syncing N mailboxes at startup issues one LIST instead of one STATUS per mailbox. UNAUTHENTICATE (RFC 8437): a session can hand the user back and be reused. The per-connection failed-login counter deliberately survives, so the command cannot reset the brute-force cap. QUOTA modernised (RFC 9208): QUOTA=RES-STORAGE advertised, quota refusals carry OVERQUOTA, SETQUOTA is recognised and refused with an explanation (quotas are administrative), and the reply for an account without a quota is now grammatically valid. PREVIEW (RFC 8970): the server hands clients a body snippet, so a message list renders without fetching bodies. HTML-only messages are stripped to text; the RFC's 256-octet ceiling is enforced on a UTF-8 boundary. MULTIAPPEND (RFC 3502): several messages in one APPEND, atomically — refusing any message means storing none, which is the entire difference from N separate APPENDs. Composes with LITERAL- into a single round trip; APPENDUID carries the whole uid-set. REPLACE (RFC 8508): saving a draft stops being APPEND + STORE \Deleted + EXPUNGE. The replacement is stored first, so a failed append leaves the original untouched. SAVEDATE (RFC 8514, schema 6013): when a message was saved into its mailbox, as its own column — INTERNALDATE must survive COPY, the save date must not, and one field cannot do both. FETCH item and all four search keys. METADATA (RFC 5464, schema 6014): annotations on mailboxes and the server, with DEPTH/MAXSIZE options, atomic SETMETADATA, and shared entries visible to every session with access to the folder. Outbound SIZE (RFC 1870) on the SMTP client: MAIL FROM declares the message size, and a message above the remote's advertised limit is refused locally before the upload — some servers only enforce their limit after DATA. Building this exposed that the client only spoke EHLO on routes needing TLS or authentication, so ordinary MX-to-MX deliveries never learned the remote's capabilities at all; EHLO is now the opener on every delivery, with the HELO fallback kept.

    Also in this build

    Schema 6012: an auto-reply can be given a start date as well as an end date, and rule criteria accept patterns up to 2,000 characters. A COM interface that reported bare HRESULTs for every error it ever raised now reports the actual message. Vacation-message begin dates, the rule-criteria length limit and the M365 relay settings are all editable in the Control Panel.

    Known issues and honest gaps

    BINARYMIME, IMAP BINARY, CATENATE/URLAUTH, COMPRESS=DEFLATE and OBJECTID remain unimplemented; the roadmap describes each honestly. Outbound PIPELINING/CHUNKING: the delivery client is strictly one-command-one-reply. Round-trip optimisations, not correctness gaps. Equal-preference MX records are still not randomised (RFC 5321 5.1).

    Release facts
    Version
    6.2.22-pre1pre-release
    Signed
    16 August 2026Sigstore transparency log
    Packages
    1Windows and Linux, x86-64 and AArch64
    Database schema
    6014upgrades in place
    Installer SHA-256
    a9555de111cd…f713dfffhMailServer-6.2.22-x64.exe
    Every file in 6.2.22-pre1

    Full SHA-256 of each file as recomputed on this server, with the signature bundles beside it. The SBOMs and provenance are not needed to install; they let anyone audit exactly what was built.

    FileWhat it isSizeSHA-256Signature
    hMailServer-6.2.22-x64.exeWindows installer · x6473.2 MBa9555de111cd77cd3c4f01a3abc80a3c1323da64355e4898995191d1f713dfffSignature: cosign bundle
    hmailserver.spdx.jsonSBOM (SPDX)Every component in the build, in SPDX JSON.148 KBa944159e06be5833152855e7394e9ab6eaf5c381c35c925005be1ac9c4ecf82aSignature: cosign bundle
    hmailserver.cyclonedx.jsonSBOM (CycloneDX)The same inventory in CycloneDX JSON.88 KB0c569011a43be4da3fb0466ca97035daaf3a2b75b64fd3c5efc8b015176e0037Signature: cosign bundle
  12. 6.2.21

    Signed 16 August 20261 package

    Inbound mail from a Postfix relay could hang forever. If mail from a front-end MTA has ever stopped arriving with nothing in the log after 354, this release is the fix.

    Release notes, checksums and signatures
    Windows

    Windows 10 (version 1607 or later) and Windows 11, or Windows Server 2016 and newer; 64-bit only. Run the installer as an administrator: it upgrades any existing 5.x or 6.x installation in place.

    • hMailServer-6.2.21-x64.exex64 · 73.1 MB
      Download
      SHA-2569fdb851a1892…a223c620Signature: cosign bundle
    What’s new in 6.2.21

    Inbound mail from a Postfix relay could hang forever. If mail from a front-end MTA has ever stopped arriving with nothing in the log after 354, this release is the fix.

    Full release notes for 6.2.21 6 sections

    Everything in the 6.2.20 notes is in this release too.

    The Postfix hang, found by running a real Postfix

    Discussion #18 reported inbound mail from a Proxmox Mail Gateway never being received: the data arrives, the log stops dead after 354 OK, send., and the sending server eventually gives up with "timed out while sending end of data", leaving a zero-byte file in the Data folder. Three previous releases claimed to fix it. The reporter tested each one and was right every time that it was not fixed.

    What settled it was standing up an actual Postfix 3.10 against this server and reading the bytes on the wire. Two facts came out of that:

    When Postfix has nothing further for a connection, it sends the end of the body, the terminating dot and QUIT in one TCP segment — ...\r\n.\r\nQUIT\r\n. With more mail queued it pipelines the next MAIL FROM there instead. Every end-of-data check this server has ever shipped — including the one added in 6.2.18 for this very report — examined only the end of the received buffer.

    With QUIT sitting behind the dot, the terminator is in the middle of the buffer, no tail check can match, and the session waits forever for something that already arrived. This also explains the two things that never added up: disabling PIPELINING "fixed" it because Postfix then waits for the 250 before sending QUIT, putting the terminator back at the end of the buffer; and whether any given sender triggers it is TCP segmentation luck, which a same-host relay wins essentially every time while internet senders almost never do.

    The receive path now searches for the standard <CRLF>.<CRLF> anywhere in what has arrived and hands anything behind it back to the command parser, so a pipelined QUIT or a whole following transaction is executed rather than lost. Verified against the real Postfix: four deliveries that hung before now complete in under 20 ms with the QUIT answered and the message byte-identical.

    The bare-LF spellings of the terminator, which this server accepts only when Allow incorrect line endings is on, are deliberately still recognised only at the very end of the buffer, and anything behind one is discarded — that is the CVE-2023-51764 SMTP-smuggling rule, and it is pinned by its own test.

    A correction to the 6.2.18 notes: the bare-LF work that release described was real and is retained, but it was not this reporter's defect. Stock Postfix normalises bare LFs to CRLF before transmitting, so a bare-LF terminator cannot reach this server through Postfix at all — and that normalisation is also why their log showed 7,984 bytes against a declared SIZE=7975.

    The Postfix test rig is now part of the repository (hmailserver/test/PostfixBench), so future builds are checked against a real MTA rather than only against a test helper that writes well-formed bytes.

    SMTP dot transparency across buffer boundaries

    A body line beginning with a dot is sent doubled and must be stored singled (RFC 5321 4.5.2). The test for "does this dot start a line" could not see across a buffer boundary, so a line-leading dot landing one or two bytes into a chunk was sent unprotected. The receiving server then un-stuffs a dot that was never stuffed: .X silently becomes X, and a body line consisting of a single . becomes <CRLF>.<CRLF> — the end-of-data marker — truncating the message there and handing whatever followed to the receiver's command parser. The receive path had the mirror-image defect, and after a forced mid-line split both could act on a dot that did not start a line at all.

    Both directions now carry the line-start context across the boundary. Because this class of defect depends on exactly where a chunk boundary falls — something a socket cannot be made to do on demand — it is covered by in-process tests that drive the transform with exact boundaries, plus a black-box test that sends every dot shape through SMTP and reads it back over POP3.

    Sieve scripts were left behind by renames and deletes

    Sieve filters live in their own directory tree, separate from the mailbox. Nothing moved or removed them:

    Renaming a domain moved every mailbox and left every account's filter behind. Delivery then found no script and delivered to INBOX everything the user had arranged to file, discard or redirect — with no error and no log line. Renaming an account did the same for that user. Deleting a domain or an account left the scripts on disk. Recreating that address later — a returning customer, a rebuilt test domain — silently reactivated the previous holder's filter for the new owner's mail, including any redirect it contained.

    Renames now move the Sieve tree with the mailbox, and deletes remove it.

    Related, in the Control Panel: the account editor wrote the Sieve script before asking the server to accept a rename. Because that property is written to disk immediately and keyed on the address, a rename the server then refused — a duplicate address, or an installation that still needs Data Directory Synchronizer — had already overwritten (or, with an empty editor, deleted) the script belonging to whoever holds the target address. The dialog only reported that the account could not be saved. The script is now written after the save succeeds, and only when it actually changed. Sieve filters can now read the message, not just its headers

    The body test (RFC 5173) is implemented and advertised over ManageSieve, with all three transforms:

    :text (the default) — the decoded text of every text part, which is what "if the message mentions X" means. Transfer encoding and charset are decoded before matching, so a filter written against plain text keeps working when the sender's client base64-encodes the part. :content "type" — the decoded parts of the named MIME types, so a rule can look inside the HTML alternative without matching the plain-text one. A bare top-level type such as image matches every subtype. :raw — the body exactly as it arrived, no decoding, MIME structure ignored.

    Seven end-to-end tests assert where a delivered message was actually filed — never what the evaluator reported — including one whose search phrase appears nowhere in the transmitted bytes and can only match if the base64 part was decoded first.

    While confirming what this section could claim, six roadmap rows were found describing the opposite of the current behaviour: they said unimplemented Sieve constructs such as reject parse as valid and silently do nothing, so "the author believes mail is being refused while it is in fact being kept". That was true once and is not now — anything this server does not implement is refused when the script is uploaded, with an error naming the construct — and a sweep test now pins that refusal for every unimplemented command and test by name, so none of them can quietly slip back to being accepted-and-ignored. An IMAP command that could hang a connection thread for good

    SORT () US-ASCII ALL, and a bare SORT (REVERSE) ..., produce an empty sort-criteria list. The code that skipped to the last criterion then walked an iterator past the end of an empty vector, which in a release build is not a crash but an infinite loop: the connection thread spins at 100% of a core, never answers, and is never returned to the pool. Any authenticated user could send it, and SORT is advertised by default. The command is now rejected as the protocol error it is (RFC 5256 requires at least one criterion), with a guard inside the sort itself as well. The message cache could never evict, and only counted upwards

    Folder message-lists are cached with a 512 MB ceiling. Entries are added before their first load, so each was recorded as occupying nothing — and the eviction routine stopped at the first zero-sized entry, which was always the oldest. The ceiling therefore never evicted anything; memory was bounded only by the 30-minute expiry, so folders never revisited stayed cached for the life of the process. Separately, every removal subtracted a stale figure (or, on expiry, nothing at all), so the running total only ever grew.

    Eviction now measures entries as they are, and the total is recomputed from the entries themselves before any eviction decision rather than trusted from a running tally — necessary because expunges, moves and folder deletes shrink a cached list without telling the cache. A guard was also added where the cache reads a collection's length concurrently with another session appending to it.

    DNS: a malformed answer no longer ends the lookup

    A user reported DKIM validation failing against a local DNS server that 5.7 handled without complaint, always on the large multi-string TXT record that carries a DKIM public key. Reproduced against a deliberately misbehaving server: exactly two malformations — a record length that runs past the end of the packet, and a compression pointer that points outside it — make Windows reject the whole response as unparseable, and both are answered correctly by the same server over TCP. Such a lookup is now retried once over TCP. A timeout is deliberately not retried.

    The Control Panel's MX-query tool (#29) asked the operating system's resolver via nslookup, so on any server with a custom DNS server configured it disagreed with the server about where mail goes. It now asks the server itself, through the same resolver SMTP delivery uses, via a new Utilities.ResolveMXRecords COM method. A failed lookup is reported as a failed lookup rather than as "this domain has no mail servers" — the distinction that matters when it is your own DNS server that is unreachable.

    Control Panel

    Seven things the retired Administrator could do, restored: pause and resume the server engine, restore default TCP/IP ports, restore default IP ranges, delete a domain, rename a domain, rename an account, and see at a glance which domains and accounts are inactive. Also fixed: creating a duplicate domain reported the error instead of closing the window; the search boxes no longer match rows on the text of their enabled/disabled flag; the brand colour now follows the light theme; and two settings descriptions were corrected — the DNS cache option described an in-process cache that has never existed (it controls whether Windows' resolver cache may answer), and the mailer-daemon domain setting misdescribed what an empty value does.

    And the Welcome page (#30): each tile centred its icon over the width of its own caption, so the six icons sat at six different offsets — none of them chosen. They are now centred on the tile, uniformly. Thanks to @helptiger for the report and the screenshot.

    Known issues

    Equal-preference MX records are not randomised (RFC 5321 5.1); the server always tries them in the order the resolver returned. A comment and a COM help string previously claimed otherwise and have been corrected. One-click unsubscribe (RFC 8058) is not implemented. It requires a per-recipient token in the message headers, and this server stores one message file shared by every recipient, so it needs the same groundwork as list moderation. RFC 2369 List-* headers, including a List-Unsubscribe that reaches the list owner, are emitted for distribution-list postings.

    Release facts
    Version
    6.2.21
    Signed
    16 August 2026Sigstore transparency log
    Packages
    1Windows and Linux, x86-64 and AArch64
    Installer SHA-256
    9fdb851a1892…a223c620hMailServer-6.2.21-x64.exe
    Every file in 6.2.21

    Full SHA-256 of each file as recomputed on this server, with the signature bundles beside it. The SBOMs and provenance are not needed to install; they let anyone audit exactly what was built.

    FileWhat it isSizeSHA-256Signature
    hMailServer-6.2.21-x64.exeWindows installer · x6473.1 MB9fdb851a1892602f5e374c51164ed2d46043eae29b90133292675c79a223c620Signature: cosign bundle
    hmailserver.spdx.jsonSBOM (SPDX)Every component in the build, in SPDX JSON.148 KBb855477df47f0d96be2e9250c9a84644b9610910f4fedafd7b6d0878b7478e52Signature: cosign bundle
    hmailserver.cyclonedx.jsonSBOM (CycloneDX)The same inventory in CycloneDX JSON.88 KB76a40a875d0bf1921ca07c661853415dc45f04326af737fcf43ea7d9f01649abSignature: cosign bundle
  13. 6.2.20

    Signed 15 August 20261 package

    If you tried to install 6.2.19 on a fresh machine and it failed or hung, this release is the fix.

    Release notes, checksums and signatures
    Windows

    Windows 10 (version 1607 or later) and Windows 11, or Windows Server 2016 and newer; 64-bit only. Run the installer as an administrator: it upgrades any existing 5.x or 6.x installation in place.

    • hMailServer-6.2.20-x64.exex64 · 73.1 MB
      Download
      SHA-256a466b3f5af87…7af1efd3Signature: cosign bundle
    What’s new in 6.2.20

    If you tried to install 6.2.19 on a fresh machine and it failed or hung, this release is the fix. Upgrades from existing installations were never affected. Everything in the 6.2.19 notes is in this release too.

    Full release notes for 6.2.20 3 sections
    The fresh-install failure in 6.2.19

    Two defects stacked, and each is worth a sentence because each is a class.

    The database creation failed. The SQL script runner splits a script into commands on blank lines, and SQL Server Compact — the embedded database a fresh install uses — executes one statement per command and refuses a batch. A recent change added settings to the create scripts with a single newline between inserts: three statements, one command. Full SQL Server accepts that batch, so the development database and the 1,490-test regression suite saw nothing wrong; only a fresh install runs the create script against SQL CE. The same adjacency was in the MySQL script (whose client refuses multi-statements by default too) and the PostgreSQL script. All three are fixed, and a mechanical sweep of all 199 SQL scripts confirms no multi-statement command remains in any create script or any SQL CE script. (Five upgrade scripts from the hMailServer 5.x era carry batches for the full-server backends that accept them; they have a decade of successful upgrades behind them and are deliberately left untouched.)

    The failure hung instead of failing. The error raised a plain message box, and /VERYSILENT /SUPPRESSMSGBOXES suppresses only the suppressible kind — so a silent install did not report the failure, it waited on a dialog with nobody at the keyboard. In CI that was ninety minutes until a person cancelled it; on a real unattended deployment it would have been forever. Every dialog in the installer (28 of them — any one was a potential silent-install hang) now takes its default button under /SUPPRESSMSGBOXES, and the installer proceeds to an exit code a deployment script can actually read. Interactive installs see exactly the dialogs they always saw.

    Who was affected: fresh installations of 6.2.19 using the built-in database (the default), and most likely fresh installations onto MySQL. Who was not: every upgrade of an existing installation — upgrades run the upgrade scripts, which were clean — and existing servers, whose databases already exist.

    How it was caught

    By the installer smoke test, which installs every release on a throwaway machine — and which has itself been fixed to be worth more: the silent-install step is now bounded at ten minutes instead of hanging with the job, writes and keeps the installer's own log, and on a database failure re-runs the setup tool and prints the stderr the real error had been going to all along. The 6.2.19 hang produced no evidence in ninety minutes; the instrumented re-run named the failing dialog and its exit code in ten.

    Also in this release (since 6.2.19)

    IMAP THREAD (RFC 5256), both algorithms — THREAD=ORDEREDSUBJECT THREAD=REFERENCES — with real References-chain threading: conversation trees for the clients that ask for them, bounded by the same per-command ceilings as SEARCH and SORT. An adversarial review of the new code found and fixed a stack overflow reachable through a deep reply chain, an uninterruptible header pass, and a malformed-Date sort inversion, before any of it shipped. The release-signing workflow could not name its own repository and failed on its first run; fixed, and 6.2.19's assets were signed by the re-run. This release's assets are signed the same way.

    Validated by the full regression suite against this binary, and by the installer smoke test against this exact installer before the release was promoted from pre-release.

    Release facts
    Version
    6.2.20
    Signed
    15 August 2026Sigstore transparency log
    Packages
    1Windows and Linux, x86-64 and AArch64
    Installer SHA-256
    a466b3f5af87…7af1efd3hMailServer-6.2.20-x64.exe
    Every file in 6.2.20

    Full SHA-256 of each file as recomputed on this server, with the signature bundles beside it. The SBOMs and provenance are not needed to install; they let anyone audit exactly what was built.

    FileWhat it isSizeSHA-256Signature
    hMailServer-6.2.20-x64.exeWindows installer · x6473.1 MBa466b3f5af87ecb370c3dc0e23134401028113bd666213f630a14fad7af1efd3Signature: cosign bundle
    hmailserver.spdx.jsonSBOM (SPDX)Every component in the build, in SPDX JSON.148 KB357d68b43fc89336ad94f65c98dac2df13d0a983fb643520dbfaa1afd57a2c7cSignature: cosign bundle
    hmailserver.cyclonedx.jsonSBOM (CycloneDX)The same inventory in CycloneDX JSON.88 KBd44cf2a911d4323d978157309525138f1a32b1fd885390a9c890f49daa05743fSignature: cosign bundle
  14. 6.2.19

    Signed 15 August 20261 package · schema 6011

    Fresh installations of 6.2.19 fail - the database creation breaks on the built-in (SQL CE) backend and most likely on MySQL…

    Release notes, checksums and signatures
    Windows

    Windows 10 (version 1607 or later) and Windows 11, or Windows Server 2016 and newer; 64-bit only. Run the installer as an administrator: it upgrades any existing 5.x or 6.x installation in place.

    • hMailServer-6.2.19-x64.exex64 · 73.1 MB
      Download
      SHA-256ef1cfe188ef5…c8acb5d4Signature: cosign bundle
    What’s new in 6.2.19
    Before you upgrade

    Fresh installations of 6.2.19 fail - the database creation breaks on the built-in (SQL CE) backend and most likely on MySQL, and a silent install then hangs on a hidden dialog instead of reporting the failure. Upgrades of existing installations are unaffected. Fresh installs should use 6.2.20, which fixes both defects and was verified by installing the exact released asset on a clean machine before promotion.

    Before you upgrade

    If you configured a custom DNS server and upgraded to 6.2.16, 6.2.17 or 6.2.18, upgrade to this release. Those builds could not resolve anything through it.

    Full release notes for 6.2.19 7 sections
    The DNS regression (#25)

    6.2.16 rewrote name resolution onto the asynchronous DnsQueryEx so that a lookup could be bounded and cancelled instead of holding a pooled thread. In doing so it gave the custom-DNS-server entry a destination port of 53.

    A DNS_ADDR carries a full SOCKADDR, so setting the port looks like the obviously correct thing to do. It is not. The DNS client supplies the port itself; with port 53 every query returns status 87 and no records, and with port 0 it works. The classic DnsQuery this replaced took a PIP4_ARRAY — a bare list of addresses with no port field at all — which is why the mistake was easy to make and impossible to see.

    The effect was not limited to the feature that reported it. Every lookup that went through a configured custom DNS server failed — MX records for outbound delivery, DNSBL, SPF, SURBL — for as long as one was configured. It announced itself loudest through SpamAssassin, as HM5507 The IP address for SpamAssassin could not be resolved, which is how #25 was reported. Installations with no custom DNS server were never affected: the server list is only built when one is set.

    Fixed, with the reasoning written into the source so it is not "corrected" back, and pinned by a regression test built as a negative control: it points DNSServer at TEST-NET-1 (192.0.2.1), which answers nothing, and requires the lookup to TIME OUT. A timeout is the proof that a packet actually left the machine - the broken build returned status 87 in the same millisecond, without sending anything - and the test fails naming that status if it ever comes back.

    The directory as an account source

    Until now an account could be linked to Active Directory but nothing could create one from it. Settings.PreviewDirectorySync and Settings.ApplyDirectorySync now read an LDAP directory and, on request, create and update the mailboxes it says should exist. There is a Control Panel page for it, and an optional unattended schedule.

    The preview is the point. Planning and applying are one piece of code — the planner decides and writes nothing, the applier writes and decides nothing — so a preview cannot describe an action the apply would not take. The Apply button stays disabled until a preview of the same options has been run, and is re-checked after the confirmation dialog.

    What it refuses to do is as important as what it does:

    A domain takes part only if its Active Directory domain name is set. That makes provisioning opt-in per domain, so a search base pointed one level too high cannot provision into unrelated hosted domains. It also guarantees every created account carries a non-empty AD domain — which matters, because LogonUser with an empty domain validates against the local machine's own Windows accounts. Nothing is ever deleted. The most it does is clear Active, and only when asked. Not on a truncated enumeration, not on an empty one, and not for a domain in which nothing was seen — in each of those an account that still exists is indistinguishable from one that was removed. An account that is not directory-linked, or is linked to a different AD domain, is reported rather than taken over. Either would move who decides that mailbox's password.

    The enumeration pages, because Active Directory caps a search at MaxPageSize and answers a larger directory with the first page and a success status. The service bind honours BindMethod, so Negotiate works where no LDAPS certificate exists — the unjoined-DMZ case the feature exists for. A multi-valued attribute is reduced deterministically, because LDAP guarantees no ordering and taking whichever value arrived first let a re-run pick a different address for the same person.

    Verified end to end against a live Windows Server 2025 domain controller.

    Sieve imap4flags now reaches the message

    setflag, addflag, removeflag and the :flags tag were parsed, accepted by require, evaluated — and then discarded at delivery. A script that marked its own automated mail \Seen was accepted, ran, reported success, and changed nothing.

    The flags are now applied to the stored message, and imap4flags has moved into the ManageSieve capability line. Only the five system flags can be stored (\Seen \Answered \Flagged \Deleted \Draft); a keyword is reported in the application log rather than dropped in silence.

    Also fixed

    GetUniqueMessageID could hand out the same UID twice, which makes a client show one message in place of another. A colon in an account address was accepted and could never work: the local part becomes a directory name in the message store, and Windows cannot create a directory containing one. Such an account saved, appeared in every list, accepted mail at RCPT TO, and failed when the message was filed. A session could outlive its io_context, and shutdown waited in the wrong place. A status card could announce failing backups on a healthy server, because an interval read from the INI was doubled as an integer and could overflow negative. A certificate handle leaked on every visit to the certificates page whenever a key file threw.

    Administration

    The settings that had no interface now have one, and the pages that showed what was configured now also show what is happening — including where a configuration looks enabled and is inert. The [Settings] INI values move into the database so they can be administered remotely, with the file still winning where both carry a value, so nothing changes for an installation that edits the INI by hand.

    Upgrading

    The database schema moves from 6005 to 6011. DBUpdater applies it; take a backup first, as always. Settings are preserved.

    Known issues

    #26 — partial FETCH BODY[]<offset.length> reported against 6.2.18 — does not reproduce here, and this release adds tests that walk a whole message in chunks over both FETCH and UID FETCH and compare the reassembly byte for byte, including the short final chunk. That is not the same as saying the reporter is wrong: the report is against a build this release is well ahead of, so "already fixed" and "never broken here" cannot be told apart without more detail. An IMAP protocol log showing the exact command sequence would settle it.

    Validated by the full regression suite against this binary.

    Release facts
    Version
    6.2.19
    Signed
    15 August 2026Sigstore transparency log
    Packages
    1Windows and Linux, x86-64 and AArch64
    Database schema
    6011upgrades in place
    Installer SHA-256
    ef1cfe188ef5…c8acb5d4hMailServer-6.2.19-x64.exe
    Every file in 6.2.19

    Full SHA-256 of each file as recomputed on this server, with the signature bundles beside it. The SBOMs and provenance are not needed to install; they let anyone audit exactly what was built.

    FileWhat it isSizeSHA-256Signature
    hMailServer-6.2.19-x64.exeWindows installer · x6473.1 MBef1cfe188ef58d2fa8799bb71482b50813cd87dd176d183e7673de87c8acb5d4Signature: cosign bundle
    hmailserver.spdx.jsonSBOM (SPDX)Every component in the build, in SPDX JSON.145 KBb941084faeb724045ccc01998106415350bd105ce4584df6a995d7ac2ab4e495Signature: cosign bundle
    hmailserver.cyclonedx.jsonSBOM (CycloneDX)The same inventory in CycloneDX JSON.86 KB120b90734d214354d8d86c9c53c21e24a82238915bf4c9bb932e4b98121c6214Signature: cosign bundle
  15. 6.2.18

    Built 12 August 20261 package

    6.2.17 bounded message acceptance, which was the mechanism behind #18. This release finishes the job on the delivery side, and fixes the one place where adding a bound made things worse.

    Release notes, checksums and signatures
    Windows

    Windows 10 (version 1607 or later) and Windows 11, or Windows Server 2016 and newer; 64-bit only. Run the installer as an administrator: it upgrades any existing 5.x or 6.x installation in place.

    • hMailServer-6.2.18-x64.exex64 · 71.8 MB
      Download
      SHA-256acff774378fa…994d8b0e
    What’s new in 6.2.18

    6.2.17 bounded message acceptance, which was the mechanism behind #18. This release finishes the job on the delivery side, and fixes the one place where adding a bound made things worse.

    Full release notes for 6.2.18 8 sections
    The database-unavailable fix

    The most consequential change here is not a timeout. It is that a recipient lookup which fails because the database did not answer now returns 451 rather than 550.

    The two cases were indistinguishable inside the server: a lookup that failed and a lookup that found nothing were reported identically. So a database briefly locked by a backup told the sending server that a perfectly valid mailbox did not exist, and the mail was bounced instead of retried. That is mail loss with a delivery receipt.

    It is implemented as a thread-local marker set at the two points where the connection pool gives up, read at both RCPT TO decision points. One detail worth recording because it is easy to get wrong: the marker is set after the pool lock is released and before ReportError, because the error handler can re-enter the pool on the same thread and deadlock against it.

    This is also what made it safe to turn the pool deadline on: DBConnectionAcquireTimeout now defaults to 60 seconds rather than 0. It shipped disabled in 6.2.17 precisely because timing out an acquisition turned a slow database into a bounce. It no longer does.

    Bounded waits (#23, #24)

    Every remaining wait that could occupy a thread from a shared pool now has a ceiling, and the log names which one was hit.

    Outbound delivery sessions have an absolute ceiling (ClientSessionCeiling, 30 minutes) that is separate from the idle timeout. The idle timeout is re-armed on every byte received, so a peer dribbling one byte at a time held a delivery thread indefinitely — the idle timeout never fired because the peer was never idle. ClamAV on the delivery path (#23) is bounded and reports a timeout rather than holding the thread. DNS queries, event scripts and external scanner processes (#24) are bounded by DNSQueryTimeout (10s), ScriptTimeout (60s) and ExternalProcessTimeout (300s). Work queue saturation is reported on a schedule and names the task holding each thread, with its session and peer IP. Pre-authentication IMAP command buffering is capped at 11 MB. An unauthenticated peer could previously buffer without limit. Backup restore validates the source archive before deleting the target. It deleted first, so a corrupt archive destroyed the data it was restoring over.

    Static analysis

    First /analyze pass. The findings worth naming:

    GetExecutableName and two MySQL path helpers allocated a byte count where a character count was needed — a buffer overrun on long paths. Logger::WriteLogFile shadowed fileExists, so rotation tested an uninitialised value and could corrupt the log it was rotating. Every read of the virus-scanner counter is now interlocked, and the compare-and-swap uses the value it compared against rather than a fresh non-atomic read.

    Roadmap

    Roadmap.md is rewritten after a comparison against Postfix + Dovecot, Stalwart, iRedMail, Mail-in-a-Box and Exchange Online.

    It states plainly what this fork is ahead on, because a list of gaps misleads on its own: in-process MTA-STS (Postfix still has none native), DANE with in-process DNSSEC validation, ARC sealing, IMAP4rev2 advertised by default (Dovecot ships it compile-time experimental and off), SCRAM-SHA-256-PLUS with channel binding, and observability in the open tier that Stalwart charges for.

    Then the gaps that actually cost users — full-text search, shared and delegated mailboxes, Sieve depth, per-account send limits, message trace — each with a note on what is already in the tree that a fix would reuse.

    It adds a section on graphs and visual data, including three accessibility defects in the current Control Panel dashboard, and reasoned refusals for JMAP, CalDAV, webmail, active/active clustering, ActiveSync/EWS and mobile push — with the reason in each case rather than a bare no.

    Also flagged as time-sensitive: hMailServer can only present a username and password when relaying out through, or collecting from, Microsoft 365. Basic auth for SMTP AUTH there is scheduled off by default at the end of December 2026, so OAuth 2.0 client support (XOAUTH2) is now dated work, not speculative.

    Documentation

    DiagnosingStalledMail.md gains the delivery half and the corrected DBConnectionAcquireTimeout default.

    Verification

    1049 regression tests pass against this exact binary, up from 1038. New coverage for recipient verification (4), IMAP sequence sets (12) and aborted transfers (2).

    The delivery ceiling was verified against a deliberately hostile peer rig — the kind that dribbles bytes to defeat an idle timeout. That rig is what caught a bad_weak_ptr crash from calling shared_from_this() in a constructor; the timer is now armed from Start().

    Still open

    Discussion #18 — the root cause is found, fixed and bounded, and the tracking issue for it is closed, but it has not been confirmed against the original reporter's Postfix/PMG setup. That confirmation is the one thing still missing. If you are affected, the per-stage timings described in the troubleshooting guide will name the culprit in a single log line; please post it on the discussion.

    Correction (12 Aug 2026)

    A follow-up audit of the source tree found that ARC sealing is narrower than described above and in the roadmap. Arc::Seal has a single caller, sitting after every early return in DKIMSigner::Sign, so a message is sealed only when the sender's domain is hosted on this server and has DKIM enabled and signing succeeds. Relayed third-party mail — the case ARC exists for — is never sealed.

    Until that is fixed this should not be presented as an advantage over verify-only implementations, and the roadmap now says so. The same audit turned up fifteen other defects, all listed there.

    Release facts
    Version
    6.2.18
    Packages
    1Windows and Linux, x86-64 and AArch64
    Installer SHA-256
    acff774378fa…994d8b0ehMailServer-6.2.18-x64.exe
    Every file in 6.2.18

    Full SHA-256 of each file as recomputed on this server, with the signature bundles beside it. The SBOMs and provenance are not needed to install; they let anyone audit exactly what was built.

    FileWhat it isSizeSHA-256Signature
    hMailServer-6.2.18-x64.exeWindows installer · x6471.8 MBacff774378fa5f7480e3673fa4eab0f8603289a6d54558c55621c767994d8b0e
    hmailserver.spdx.jsonSBOM (SPDX)Every component in the build, in SPDX JSON.96 KB147b09b4d08ea8f90c67a794ab8357d6ddfa02ea8637329029e70282c57d05c9
    hmailserver.cyclonedx.jsonSBOM (CycloneDX)The same inventory in CycloneDX JSON.50 KBf319376b9bac0f417af941de9b9c00220207a34fac5f783a40cf97a1dacd52c8
  16. 6.2.17

    Built 12 August 20261 package · schema 6005

    This release identifies and fixes the cause of discussion #18 — mail relayed from Postfix / Proxmox Mail Gateway stalling after 354…

    Release notes, checksums and signatures
    Windows

    Windows 10 (version 1607 or later) and Windows 11, or Windows Server 2016 and newer; 64-bit only. Run the installer as an administrator: it upgrades any existing 5.x or 6.x installation in place.

    • hMailServer-6.2.17-x64.exex64 · 71.8 MB
      Download
      SHA-25678da603b3d29…17c182ce
    What’s new in 6.2.17

    This release identifies and fixes the cause of discussion #18 — mail relayed from Postfix / Proxmox Mail Gateway stalling after 354, with the sender reporting "timed out while sending end of data". That report has been open, unreproduced and honestly labelled as such since 6.2.13. It is now reproduced, understood and bounded.

    Full release notes for 6.2.17 5 sections

    No database change (schema version 6005).

    What was actually happening

    Postfix's error means the message body and the terminating dot were fully transmitted, and it was waiting for hMailServer's 250 — which never came. So the stall was never in reception; it was in the accept/save work that runs after end-of-data and before the reply.

    Two things combine to produce it

    For a trusted incoming relay — exactly what a Proxmox/Postfix front end is — hMailServer defers its whole spam battery to after end-of-data. For a direct or authenticated sender those same tests run earlier, during MAIL FROM/RCPT TO, on the connection thread. That is why this only ever happened to relayed mail, and why it never reproduced on a simple test server. That work runs on a bounded pool (15 threads by default) and holds the thread that sends the 250. SpamAssassin's wait had no overall ceiling: the connection's idle timeout is re-armed on every byte received, so a scanner that stalls, or dribbles, holds the thread indefinitely.

    Reproduced deliberately, against a SpamAssassin endpoint that accepts connections and never answers:

    TestResult
    20 concurrent messages, no scannerall acknowledged in 21–40 ms
    8 concurrent, virus scanner blackholedall acknowledged in 21–28 ms (virus scanning runs after the 250)
    1 message, SpamAssassin blackholedacknowledged after 120 s — slow, but it completes, which is why single-message tests never showed the bug
    18 concurrent, SpamAssassin blackholed0 of 18 acknowledged — 15 workers blocked, the rest with no worker at all: total silence, exactly the reported symptom
    The fixes

    SpamAssassin's wait is now bounded. It was an untimed wait released only by an idle timer that any trickle of data re-arms. It now has a hard ceiling (SAMaxTimeout + 30 s) after which the message is accepted without a SpamAssassin verdict — the same outcome as when spamd is simply down. This also required sharing the completion flag properly with the connection, since the old code held a reference to the waiting function's stack.

    Message acceptance is bounded overall. A new FinalizationTimeout (default 240 s, comfortably inside Postfix's 600 s data-done timeout; 0 disables it) makes the server answer 451 4.3.1 and let the sender retry, rather than leaving it waiting for a reply that will not arrive. The check runs on the accepting thread itself and only before anything is saved, so it cannot duplicate mail — a watchdog replying from outside would race the 250 and deliver the message twice.

    The silence is now instrumented. Acceptance is timed per stage, and each spam test is timed individually. A slow stage is reported at APPLICATION level rather than only under debug logging, so the log now says:

    Spam test: SpamTestSpamAssassin, Score: 0, Time: 120031 ms SMTPConnection - accept: done spam-protection in 120047 ms (session 5).

    Where previously there was nothing at all between 354 and the reply. If acceptance waited on a saturated queue, that is logged too, with the wait in milliseconds.

    If you are affected, upgrading is enough to convert silent stalls into either a completed delivery or a clean 451 retry. If you want to know why your server is slow, enable debug logging and the per-stage timings will name the scanner, DNS lookup or event script responsible.

    Known related issues, not fixed here

    The audit that found this also found other unbounded waits on the same and adjacent paths, deliberately left for a follow-up rather than rushed into this release: synchronous DNS lookups in the spam tests have no application-level timeout, the database connection pool has no acquisition deadline, event scripts have no execution limit, and on the delivery side the ClamAV read/write timeout is currently ineffective. None of these is the reported fault, and each needs its own fix and its own test.

    Also in this release

    The README has been rewritten around what the server does and what it is built on. Per-version change history now lives here on the Releases page, where it belongs. The regression suite gains coverage for clients that vanish mid-operation (aborted DATA, truncated BDAT, IMAP APPEND literals cut short, POP3 disconnects during RETR) — the class of defect that produced the two blockers found during 6.2.15. A new installer smoke test installs each release on a clean throwaway machine and verifies the service registers, starts and listens, so the installer is no longer validated only by the fact that it compiles.

    Release facts
    Version
    6.2.17
    Packages
    1Windows and Linux, x86-64 and AArch64
    Database schema
    6005upgrades in place
    Installer SHA-256
    78da603b3d29…17c182cehMailServer-6.2.17-x64.exe
    Every file in 6.2.17

    Full SHA-256 of each file as recomputed on this server, with the signature bundles beside it. The SBOMs and provenance are not needed to install; they let anyone audit exactly what was built.

    FileWhat it isSizeSHA-256Signature
    hMailServer-6.2.17-x64.exeWindows installer · x6471.8 MB78da603b3d293936fea9758ec9e194909a22f51baa7853c3bbb6a1ae17c182ce
    hmailserver.spdx.jsonSBOM (SPDX)Every component in the build, in SPDX JSON.96 KB69e7bd9791ea3febe1e1b0835619b80a0f68cea07230c98ce095e12c39eb8caf
    hmailserver.cyclonedx.jsonSBOM (CycloneDX)The same inventory in CycloneDX JSON.50 KB7a703cf4adde4d746fed302c38f50508f6d13d02069fb93b12c2c2bc88e2f766

Also published

Build inputs

Third-party binaries the build fetches instead of carrying in git. Each is the exact file that was committed until 11 September 2026, recorded by SHA-256 in hmailserver/docs/third-party-binaries.json, which is also where its provenance, licence and use are written down. build/get-installer-binaries.ps1 downloads these, verifies them against that manifest, and places them where the installer script expects them; the same script gathers the MSVC runtime from the build machine's Visual Studio. A release rather than a URL at the vendor, because two of the three have no stable first-party download left: Microsoft withdrew SQL Server Compact 4.0 SP1 from the Download Center, and 7-Zip's standalone console ships only inside a .7z archive that needs 7-Zip to open.

AssetSHA-256What it is
7za-19.00.exe8117e40ee7f824f63373a4f5625bb62749f69159d0c449b3ce2f35aad3b835497-Zip 19.00 standalone console, Igor Pavlov, LGPL-2.1-or-later; from https://www.7-zip.org/a/7z1900-extra.7z
mariadb-connector-c-3.4.9-win64-client.zipa23e96857e7e02fd26c9317638655c4d3da3f11d0ac2f40ae310842a5169cc8dMariaDB Connector/C 3.4.9 client (libmysql.dll) and its ten authentication plugins, MariaDB Corporation AB, LGPL-2.1-or-later; the per-file hashes are in the manifest
SSCERuntime_x64-ENU.msib2d2aba2102d0fa8915421b1e0b850f7c538983eed06e89c358cc2a0c578f958Microsoft SQL Server Compact 4.0 SP1 x64 runtime (MSI built 2012-04-06), redistributable under its licence; the installer's built-in database option

This release is immutable, like every release here: a changed or added input goes into build-inputs-2.

Every file in Build inputs

Full SHA-256 of each file as recomputed on this server, to compare with the manifest above.

FileWhat it isSizeSHA-256Signature
SSCERuntime_x64-ENU.msiWindows installer · x6410.1 MBb2d2aba2102d0fa8915421b1e0b850f7c538983eed06e89c358cc2a0c578f958 release notes
7za-19.00.exeWindows installer1.1 MB8117e40ee7f824f63373a4f5625bb62749f69159d0c449b3ce2f35aad3b83549 release notes
Progressiverobot-hmailserver-attestation-46764040.sigstore.jsonBuild attestationGitHub artifact attestation for the release, as a Sigstore bundle.3 KB4d58475f49eac111919c67a29bff14545c77990868799454dd6ce89a9533b19b
mariadb-connector-c-3.4.9-win64-client.zipFile293 KBa23e96857e7e02fd26c9317638655c4d3da3f11d0ac2f40ae310842a5169cc8d

Installed? The documentation takes it from here

Thirty-seven chapters written for first-time mail admins: DNS, certificates, anti-spam, backups, hardening and the REST API. Or let the maintainers run hMailServer for you.