Repository navigation
Pre-release: hMailServer 6.2.22-pre6 (build 30) — Pre-Alpha
Pre-releaseRe-published on GitHub on 18 October 2026 from the GitLab release v6.2.22-pre6 of 2026-08-20. Each file below was checked against the SHA-256 that GitLab's package registry records for it before it was uploaded here.
Re-published on GitLab on 24 September 2026 from the GitHub release of 20 August 2026 (2026-08-20 23:09:03 UTC). Issue, pull request and discussion numbers below are GitHub's.
6.2.22-pre6 (build 30) — Pre-Alpha
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.
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 acheck-db-scriptsstep 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.
Thev6.2.22-pre4tag 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
theDmarcTreeWalkEnabled=0escape 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 andsp=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 applynp=rejectto real parts of a sender's own estate. - Aggregate reports name the selector. RFC 7489 has a
<selector>inside
auth_results/dkimand 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-sha1is 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 signedrsa-sha256instead and told so.
DkimAcceptSha1=1restores 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,SpfVoidLookupLimitto 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
renewalInfoendpoint 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 atstartmoves 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=0restores 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) — GitHub 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.
ArchiveDiris 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.emlfiles, and does
not follow symlinks or junctions. - Per-domain metric labels. Every counter was server-wide, which is what
stopped metrics becoming reporting.MetricsPerDomainEnabledadds adomain=
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.
Assets
| File | Size | SHA-256 |
|---|---|---|
Progressiverobot-hmailserver-attestation-42002219.sigstore.json |
3,933 | 29af2ae1d3b84209484918113a71584508ed2df579897c2d8e4cfbce8f60b343 |
hMailServer-6.2.22-x64.exe |
76,834,030 | eb565d4b604b64c78e513a97e48e54716f2a059107990badc7d16b98b384cdcd |
hMailServer-6.2.22-x64.exe.cosign.bundle |
10,567 | 59df5adb5e49fa117d2ea482cbff0fb4c57d8430d0b4dea976e975b70eb2d012 |
hmailserver.cyclonedx.json |
98,021 | 52edf8838e670be0eb59ff34055276e09a0bbf608031ac956d08a43bdfe18928 |
hmailserver.cyclonedx.json.cosign.bundle |
10,671 | 29fb6cf0aedeb75ad7e3c4f63a08e6e53f28d8f67d68473f2f24a285bd1ad20e |
hmailserver.spdx.json |
163,668 | 6f2f2b774cc6b2affa11e33505d7a96f86dde57b5ed56d48c1eb44abb5c60f3b |
hmailserver.spdx.json.cosign.bundle |
10,715 | 15e3471a48889c1fe1a834600caab95fabd0281dddac2d89d5662e41d032d987 |
These are the files published with the GitHub release, byte for byte, re-hosted in this project's package registry. The hashes above were computed from them on 24 September 2026. Each .cosign.bundle / .sigstore.json is the Sigstore bundle GitHub Actions made when the release was signed; all of them were verified again before re-publishing, against the former GitHub repository's workflow identity (https://github.com/Progressiverobot/hmailserver/..., issuer https://token.actions.githubusercontent.com), which is the identity to give cosign verify-blob for these files. The Windows installer of this release was not Authenticode-signed (Authenticode signing began with 6.3.1).