Repository navigation
Release Process
Source of truth: RELEASE.md, kept in the repository and copied here.
The order below is not advisory. Each rule exists because violating it has already cost a release cycle or nearly shipped a defect.
-
Freeze the code. No fixes land after this point without restarting at step 2.
-
Adversarial review of the complete diff — independent reviewers per dimension (delivery paths, protocol handling, UI truthfulness vs. code, docs claims vs. code), each finding verified by skeptics instructed to refute it. This happens before version stamping, not before tagging: the 6.2.15 review ran after the installer was built and both blockers it found (a BDAT truncation defect and a fix that converted transient file locks into permanent mail loss) forced rebuilds and a re-run of the full suite. Same review, an hour cheaper, if run first.
-
Fix what survives review; each defect fix gets a negative-control test. Build the pre-fix binary and confirm the new test fails against it. A test that passes on both builds proves nothing — one of the 6.2.15 abort tests did exactly that until it was strengthened with session-count assertions.
-
Pre-flight the bench:
build\preflight-tests.ps1(add-Cleanto remove a stale ERROR log). Never skip this after an aborted run — an aborted run leaves a deliberate scanner error in the ERROR log and the next run fails 100% of tests in fixture setup, and it also leaves the TLS fixtures' twelve extra ports behind, which the pre-flight checks. -
Check the roadmap against itself:
build\check-roadmap.ps1. It reconciles every tick box inRoadmap.mdagainst the per-section counts and the contents table, because three hand-edited restatements of the same 750 numbers drift — nine sections had drifted when the check was written. -
Check the database scripts build a database:
build\check-db-scripts.ps1. It creates a throwaway SQL CE database fromCreateTablesMSSQL.sqlusing the same splitting rules the server uses, because nothing else here does - the regression bench's database is upgraded out of band, so no local test takes the CREATE path a fresh install takes. 6.2.22-pre4 shipped an installer whose database could not be created at all, and the only symptom was a service that started and did not listen.Run
build\check-schema-versions.ps1beside it (pwsh 7, not Windows PowerShell 5.1, where it dies inMeasure-Object -Property To). It walks the registered upgrade chain and proves it is contiguous and forward-only, and that every step is present for every backend. That matters most on exactly the kind of release this checklist keeps being used for - one that moves the schema several steps past the last stable - and it was missing from this step until 6.2.23. -
Version stamp:
Version.h(version, numeric, build),section_setup_64.iss, all seven.csproj<Version>values. Verify nothing else still carries the old version:git grep <old-version>. Insection_setup_64.isstheVersionInfoVersionfourth component stays0: the build number lives inVersion.hand the tag, and a build-only re-cut (the common case) must not have to touch the installer script or the seven.csprojfiles, whose versions are also<version>.0. -
Build everything at the stamped version:
build.ps1 -Configuration Release,build-tools.ps1 -Configuration Release, ControlPaneldotnet publishto itspublish\folder (build-tools does not cover it),build-tests.ps1. Confirm the stampedFileVersiononhMailServer.exeandpublish\hMailCP.dll.If
hMailServer.idlchanged in this range, regenerate the checked-in COM wrapper after the server build and before the tools build:build\regenerate-interop.ps1. It runs TlbImp AND rewrites the wrapper's SHA-256 and size inhmailserver\docs\third-party-binaries.json- the binary-provenance workflow fails when those disagree, and regenerating by hand without the manifest did exactly that twice in one day. A stale wrapper still compiles, which is why this is easy to skip: the tools use a small stable subset of the API, so nothing fails, and the members added this release are simply invisible to them. Regenerate AFTER any interface-ordering fix, never before, or the old vtable layout is baked into the shipped wrapper permanently.Then prove the build is still reproducible: build Release a second time from clean (
build\build.ps1 -Configuration Release -Clean) and compare the SHA-256 ofhMailServer.exewith the first. They must be identical - /Brepro, /d1trimfile, /pdbaltpath and OPENSSL_NO_FILENAMES in the project make the executable a pure function of the source and the toolchain, and a mismatch means something has started embedding a timestamp or a path again. Put the hash in the release notes' verification section so anyone with the same toolchain (v145, Windows SDK 10.0.26100) and library layout can check the published binary came from the published source. Both builds restart the service, so do this before step 9, never after.
8b. Full regression suite on the assertion build, first. Build with
build\build.ps1 -Configuration Release -Asserts - the same source, with
every HM_ASSERT kept and a violated one reported as HM6364 in the ERROR
log instead of compiled out - and run the whole suite on it. The ERROR log
is checked before each test, so a violation fails the test that provoked
it and names the expression, file and line. Expect none. Then rebuild plain
Release (step 8) before step 9: the assertion build is the dynamic-analysis
build and is never the binary that ships or is hashed.
8c. Timed fuzz run on the release source — required for every minor
release. On the build from step 8: fuzz\run-fuzz.ps1 -Target mime_message_fuzzer -Minutes 30, and the same for each other harness
listed in hmailserver/docs/Fuzzing.md. A crash, a hang or a violated
assertion is a release blocker: minimise it, fix it, add the input to
fuzz\regression, restart at step 1. Record the harnesses, the duration
and the execution count in the release notes' verification line. A patch
release may cite the previous minor's run when none of the fuzzed parsers
changed; anything else runs again.
-
Full regression suite on the stamped binary — every test, nothing skipped. If anything changes after this run, the run is void: rebuild and re-run. Never abort a run; if one must be stopped, expect step 4 to fail and clean up before trusting any result.
-
README release notes — every claim checked against the diff. "Fixed" means reproduced-then-fixed or negative-control-tested; anything else is described as hardening or diagnostics. Unfixed known issues are named as unfixed.
-
Installer: ISCC on
hMailServer64.iss. Never run the installer on the dev machine — validation is the CI smoke-test workflow (installer-smoke.yml), which installs it on a throwaway runner. -
Commit (as chrisholloway5, no co-author trailers - history has been rewritten once to remove them, and will be again).
masteris protected: changes arrive by pull request, force-pushes and deletions are refused, and the maintainer's bypass exists for emergencies, not for the release flow. So push the working branch, open the PR and merge it with a rebase so the history stays linear and the commits keep their own messages:git push origin server-fixes-wave gh pr create --base master --head server-fixes-wave --fill gh pr merge --rebase # self-merge is allowed; no reviewer is required git pull --ff-only origin masterThen tag
vX.Y.Zon master as an annotated, signed tag and push it (tags matchingv*are protected too: no deletion, no rewrite - a tag is spent once):git tag -s vX.Y.Z -m "hMailServer X.Y.Z" git -c gpg.ssh.allowedSignersFile=.github/allowed_signers verify-tag vX.Y.Z git push origin vX.Y.ZSigning is SSH-based (
git config gpg.format sshanduser.signingkeypointing at the public key, set once in the repository's config), and the key must be listed in.github/allowed_signers- the signing workflow below refuses to sign anything for a tag that is lightweight or that does not verify against that file, so an unsigned tag stops the release before it has any assets. Every tag up tov6.2.23-alpha1was lightweight; there was nothing to verify and nothing stopped av*ref being moved.Then publish as a draft first - this repository has immutable releases enabled, so a published release refuses every further asset upload and would be stuck with whatever it was created with:
gh release create vX.Y.Z <installer> --draft --prerelease \ --title "..." --notes-file <notes> gh workflow run "SBOM" -f release_tag=vX.Y.Z # SPDX + CycloneDX gh workflow run "Sign release artefacts" -f tag=vX.Y.Z # LAST: signs what is attached gh release view vX.Y.Z --json assets # expect installer + 2 SBOMs + bundle gh release edit vX.Y.Z --draft=false # publish, now completeThe installer's name is not cosmetic. It must be exactly
hMailServer-<tag without the leading v>-x64.exe, with its.cosign.bundlebeside it. The update checker in every running server builds that name from the tag and matches an asset on exact equality; a release named anything else reports "a new version is available" and then "there is nothing to download" to every installation in the field. This was broken once already, atv6.2.22-pre3, whose installer washMailServer-6.2.22-x64.exe. The signing workflow now refuses a release that does not carry the expected name, so the check happens before publication rather than after somebody notices.Order matters twice over: the SBOMs have to be on before signing, because cosign signs whatever is attached when it runs; and everything has to be on before publication, because nothing can be added afterwards. 6.2.22-pre4 was published straight away and ended up with an installer and no SBOM at all.
The two
gh workflow runlines are required, not belt-and-braces. Saving a draft does not start a workflow: GitHub does not deliverreleaseevents for draft releases, which was measured here rather than assumed - creating the pre5 draft produced no run at all. Therelease: createdtrigger on both workflows therefore only covers a release published without a draft, which is the path that can no longer attach anything. Dispatching them by hand is the path that works.And a tag is spent once: a tag that has backed an immutable release cannot back another, even after that release is deleted. There is no re-cutting a broken release under the same version - it needs the next number.
-
Close the loop: answer every issue the release resolves (and close them), update the ones it does not resolve saying so plainly.
- Prefer two small releases over one large one. 6.2.15 carried IMAP semantics, delivery changes, a connection-layer rewrite and a UI expansion in one tag; its worst defect lived in the interaction between two of those themes.
-
A fix to a shared layer needs a dependents audit. Before changing
behaviour in something like
TCPConnection, enumerate who depends on the old behaviour (the BDAT reads depended on "any error ends the session"). - The public record never overstates. If it is not reproduced and negative-control-tested, it is not "fixed" — say "hardened", "instrumented" or "still open". Credibility with reporters is the project's scarcest resource.
hMailServer 6.3.8, the latest release (2 November 2026, schema 6108) · AGPL-3.0-or-later · Repository · Mirror on GitHub · Report a documentation error (a GitLab issue naming the page)
Home — full index
Start here
1. Install and run
- Before You Install
- Installing hMailServer
- Installing on Linux
- Running in a Container
- Profiles and Environment
- The Control Panel
- The Control Deck
- Your First Domain and Mailbox
- Connecting a Mail Client
- The Webmail
- DNS for Your Domain
- Migration
2. Secure it
- Encryption and Certificates
- Database TLS
- Encryption for People
- Stopping Spam
- Stopping Viruses
- Security Hardening
- Secure Defaults
- The Service Account
- Secrets at Rest
- Sign-in and Identity
- Security Policy
3. Operate it
- Monitoring and Health
- The SNMP Agent
- Backup and Restore
- Message Storage
- Compliance and eDiscovery
- Troubleshooting
- Diagnosing Stalled Mail
- Relocating an Installation
- Upgrading hMailServer
- Upgrading Guide
- The Update Feed
- Migrating the Database Backend
- High Availability Runbook
- Warm Standby
- Runbooks Digest
4. Extend it
- Rules and Sieve
- Aliases Lists and Public Folders
- Rooms and Equipment
- Mailing Lists
- Routes and Relays
- Mail In to a Webhook
- Outbound Shaping
- The COM API and Scripting
- Event Scripts
- The REST API
- Command Line
- APIs Reference
5. Contribute to it
- Project Handbook
- Architecture
- Contributing
- Release Process
- Governance
- Assurance Case
- Regression Test Environment
- Fuzzing
- Regulatory Scope
- Third-Party Binaries
Look it up — from any journey
- Settings Reference
- Settings Overview
- Capabilities and Configuration
- Control Panel Reference
- Control Panel Pages
- Ports Reference
- Changes-Since-6210
- Version-History-to-6210
- Glossary
About this wiki
- hMailServer 6.3.8 is the latest release (2 November 2026, schema 6108)
- Pages say since 6.3.4 where a feature is new in it
- AGPL-3.0-or-later
- Repository, the one place the project is worked on
- Its mirror on GitHub, since 18 October 2026, with no Issues
- Questions: a GitLab issue with the Question template, GitHub Discussions Q&A, or the forum
- Report a documentation error: a GitLab issue naming the page