hMailServer 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. 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.