Repository navigation
Runbooks Digest
What each runbook in the repository covers, its procedures and prerequisites, and where its claims were checked against the code. File and line references point into the repository at the commit this page was written from (6.2.24, master of 4 September 2026). Sections headed Unconfirmed or Contradictions record what could not be verified or where documents disagreed, and are left in on purpose.
The project's home is GitLab, https://gitlab.com/hmailserver/hmailserver, the one place it is worked on. GitHub suspended its account
Progressiveroboton 18 September 2026, and since 18 October 2026 https://github.com/RainbowWizard-coder/hMailServer is GitLab's mirror. Where this page names a file under.github/it is the tree of 4 and 8 September:CONTRIBUTING.md,SECURITY.mdandSUPPORT.mdhave been at the repository root since 24 September 2026, and the workflows it says run are GitHub Actions workflows, which do not run on GitLab. In 6.3.4 the project's own CI is defined in.gitlab-ci.yml, for GitLab's shared runners and the project's own (ContinuousIntegration.md), and the checks it does not run are run by hand (Contributing). Brought up on 5 October 2026 to 6.3.6 (6 October 2026, schema 6108), for what 6.3.6 changes on this page. 6.3.7, the latest release (17 October 2026, schema 6108), is not yet reflected here.
Scope: the thirteen files hmailserver/docs/ held on 4 September 2026; the documents added since are listed, not digested, under The documents added since in section 0, and the figures the page quotes were brought up to master 540347500 on 24 September 2026, and to 92b8f9f63 on 26 September 2026; the release, 6.3.4 (27 September 2026, schema 6104), is the tag v6.3.4 (51e2dbda0), which adds a last batch to that tree, and the schema, suite and document counts below are the tag's. Checked against the working tree at branch server-fixes-wave (HEAD 4d5da3f) on 2026-09-04; sections 1, 2, 6, 6a, 7, 8, 10, 12a and 13 re-checked against master 40ae9491d (the tree 6.2.28 was cut from later that day) on 2026-09-08. Paths below are relative to the repository root c:/Users/chris/Documents/projects/hmailserver/. Where a doc claim disagrees with the code, the code wins and the disagreement is listed under "Stale or wrong in the doc" for that document and again in the summary at the end.
This page is the digest — what each runbook covers and where its claims were checked. If you have a job to do rather than an audit to perform, start here.
flowchart TD
Q["What are you doing?"]
Q --> A["Moving to a newer release"]
Q --> B["Mail is stuck"]
Q --> C["Planning for the machine dying"]
Q --> D["Changing database engine"]
Q --> E["Bringing mailboxes in from<br/>another mail system"]
Q --> F["Setting up a machine to run<br/>the regression suite"]
Q --> G["Hunting parser bugs"]
Q --> H["Answering a compliance or<br/>supply-chain question"]
Q --> I["Building a monitoring dashboard"]
A --> A1["docs/Upgrading.md<br/>wiki: Upgrading Guide"]
B --> B1["docs/DiagnosingStalledMail.md<br/>wiki: Diagnosing Stalled Mail"]
C --> C1{"How much downtime<br/>can you accept?"}
C1 -- "minutes, and you have<br/>a second machine" --> C2["docs/HighAvailabilityRunbook.md<br/>wiki: High Availability Runbook"]
C1 -- "hours, and you want<br/>something simpler" --> C3["docs/WarmStandby.md<br/>wiki: Warm Standby"]
D --> D1["docs/MigratingDatabaseBackend.md<br/>wiki: Migrating the Database Backend"]
E --> E1["docs/Migration.md - new in 6.2.27.<br/>IMAP mirror, mbox, Maildir,<br/>Outlook, upstream hMailServer"]
F --> F1["docs/RegressionEnvironment.md<br/>wiki: Regression Test Environment"]
G --> G1["docs/Fuzzing.md<br/>wiki: Fuzzing"]
H --> H1["docs/RegulatoryScope.md and<br/>docs/ThirdPartyBinaries.md<br/>wiki: Regulatory Scope,<br/>Third-Party Binaries"]
I --> I1["docs/grafana-dashboard.json<br/>wiki: Monitoring and Health"]
Thirteen files plus a Licenses/ folder when this digest was written. Every runbook had a wiki page except
Migration.md, which still has none.
hmailserver/docs/ |
Covers | Wiki page | "Verified against the code" stamp in the doc |
|---|---|---|---|
README.md |
The index for the other twelve | — | undated |
Upgrading.md |
Release-to-release moves, what the schema upgrade touches, backup, failure, rollback, silent upgrade | Upgrading Guide | 21 Aug 2026 |
DiagnosingStalledMail.md |
Which half stalled — accepting or delivering — and the settings that bound a session | Diagnosing Stalled Mail | 13 Aug 2026 |
HighAvailabilityRunbook.md |
Two machines, shared storage, failover | High Availability Runbook | 13 Aug 2026 |
WarmStandby.md |
One spare machine, restored periodically | Warm Standby | 21 Aug 2026 |
MigratingDatabaseBackend.md |
Moving between MySQL, MS SQL, PostgreSQL and SQL CE | Migrating the Database Backend | undated |
Migration.md (6.2.27) |
Bringing existing mailboxes in: IMAP mirror, mbox, Maildir, Outlook, upstream hMailServer | none yet | newest of the set |
RegressionEnvironment.md |
Building the bench the suite needs | Regression Test Environment | undated |
Fuzzing.md |
The MIME harnesses, the two oracles, what to do with a finding | Fuzzing | 13 Aug 2026 |
RegulatoryScope.md |
CRA and PLD determination, dated and reviewable | Regulatory Scope | determination 12 Aug, list 13 Aug 2026 |
ThirdPartyBinaries.md |
The third-party binaries - 40 committed files when this was written, none committed at master 540347500 (section 10) |
Third-Party Binaries | undated |
grafana-dashboard.json |
19 panel entries over the Prometheus metrics | Monitoring and Health | n/a |
third-party-binaries.json |
The machine-readable manifest CI verifies | — | n/a |
At master 540347500 (24 September 2026) hmailserver/docs/ held 85 Markdown documents, and 104 at 92b8f9f63; in 6.3.4 (the tag) it holds 105 - the eleven above and 94 added since, the last Mirroring.md, most of them with a feature 6.3.4 brought, the last nineteen in the five rows at the foot of the table - with an images/ folder and the QuickJS licence beside them. They are not digested on this page: each is the operator's document for its feature, the Control Panel's, the Deck's and the webmail's help panes show it beside the page it describes, and Changes-Since-6210 section 4e has a line for each. Command Line and Relocating an Installation are copies of one here, and since 27 September 2026 so are The SNMP Agent, Event Scripts, Secure Defaults, The Service Account, Secrets at Rest, Database TLS and The Update Feed; the right-hand column names the wiki pages that describe the same ground.
From Migration.md, which is the newest runbook and the one with no wiki page of
its own. What a route does not keep is the part worth reading before starting.
| Route | Mechanism | Keeps | Does not keep |
|---|---|---|---|
| From another IMAP server | An external account with Mirror every folder on — FetchAccount.ServerType 1 plus FetchAccount.MirrorFolders, schema 6031 |
Folders and hierarchy (delimiter mapped), every message byte for byte, \Seen \Flagged \Answered \Draft \Deleted, the internal date |
Subscriptions, custom keywords, ACLs |
| From mbox files | The Import Tool, one IMAP folder per file, via Utilities.ImportMessageFromFileToIMAPFolder
|
The messages | Flags |
| From a Maildir | The Import Tool's Select Maildir button — reads cur/new/tmp and every Maildir++ folder beside it |
Flags from the :2, / ;2, / !2, file-name suffixes; line endings made CRLF |
— |
| From Outlook | Through IMAP: add this server as an IMAP account and drag folders across | Whatever the client copies | PST is deliberately not parsed |
| From the original hMailServer up to 5.6 | In place, per Upgrading.md; accounts in bulk through the Import Tool's comma-separated account import |
Everything, including the data directory | — |
| From the original hMailServer 5.7 | Not in place: setup cannot upgrade a 5.7 database. A migration of kind imap from it, with the domains and accounts made here first (Migration.md, "From the original hMailServer") |
What a migration from another IMAP server keeps | The settings, rules and the rest of its configuration |
Two things about the mirror route that surprise people, both deliberate:
nothing is delivered, so no rule, anti-spam or anti-virus check touches a copy and
OnExternalAccountDownload does not fire; and setting days to keep messages to -1
turns the mirror into a move (0 leaves every message on the remote server; before
6.3.6 an IMAP account read the two the other way round). Use a new external account for a mirror rather than
converting an existing fetch account.
-
hmailserver/docs/holds 13 files + aLicenses/folder: README.md, Upgrading.md, DiagnosingStalledMail.md, HighAvailabilityRunbook.md, WarmStandby.md, MigratingDatabaseBackend.md, Migration.md (6.2.27), RegressionEnvironment.md, Fuzzing.md, RegulatoryScope.md, ThirdPartyBinaries.md, grafana-dashboard.json, third-party-binaries.json (source:ls hmailserver/docs/) - README.md is the index; it says everything is "operator-facing" and points code readers to
.github/CONTRIBUTING.mdandRELEASE.md(source: docs/README.md:4-7) - README.md's table lists Migration.md (:14) and RegressionEnvironment.md (:16) but still NOT WarmStandby.md or Fuzzing.md (source:
grep -c "WarmStandby\|Fuzzing" hmailserver/docs/README.md= 0) — GAP - README.md's closing paragraph still says "the two documents above both exist because a specific problem took far longer to diagnose" — written when there were two docs; there are now eleven (source: docs/README.md:36-39)
- README "Elsewhere" table links:
README.md,build/hmconfig.ps1,ARCHITECTURE.md,Roadmap.md,.github/SUPPORT.md,.github/SECURITY.md,RELEASE.md— all exist (source:lsof each) -
build/hmconfig.ps1 applyis a dry run unless-Force; deletions additionally need-AllowDelete(source: build/hmconfig.ps1:42-57,85-87) -
Licenses/contains 10 RTF licence texts (7Zip, Boost, Fat Cow Icons, MIME Message encoder analyzer, Microsoft SQL Server Compact Edition, OpenSSL, PostgreSQL interface, RMSPF, String library, ZLib) plusLicense list.ods; there is NO LGPL text for MariaDB Connector/C (source:ls hmailserver/docs/Licenses/) - "Verified against the code" dates stamped in the docs: Upgrading 21 Aug 2026; DiagnosingStalledMail 13 Aug; HighAvailabilityRunbook 13 Aug; WarmStandby 21 Aug; RegulatoryScope determination 12 Aug / list 13 Aug; Fuzzing 13 Aug; ThirdPartyBinaries undated. Code has moved since on several of them (see below)
- Moving an installation to this fork or between releases; what the schema upgrade touches; backup; failure; rollback; silent upgrade; .NET 10 (source: docs/Upgrading.md:1-7)
- Back up both the database (native dump:
mysqldump,pg_dump, SQL Server backup) and the data directory (source: Upgrading.md:81-86) - Note current versions: server version in Control Panel; schema version via
select value from hm_dbversion(single-column single-row table, not a settings row) (source: Upgrading.md:87-92) - Run the new installer over the existing install: stops service, installs, upgrades schema, restarts (source: Upgrading.md:93-94)
- Check the service is listening and the ERROR log for the upgrade window is empty (source: Upgrading.md:95-97)
- Send a message to yourself end-to-end and read it back over IMAP/POP3 (source: Upgrading.md:98-99)
-
REQUIRED_DB_VERSIONwas 6031 when this line was written (source: hmailserver/source/Server/Common/Application/Constants.h:173); it is 6040 in v6.3.3, was 6045 at3233b0cc4(Constants.h:208) and 6096 at master540347500(Constants.h:987), was 6103 at92b8f9f63(Constants.h:1205), and is 6104 in 6.3.4 (Constants.h:1224 at the tag) - Upgrade chain: scripts
Upgrade0to1100…throughUpgrade6030to6031…existed when this line was written (source:ls hmailserver/source/DBScripts/); 6025→6030 shipped in 6.2.25, 6030→6031 in 6.2.27. At3233b0cc4the chain runs toUpgrade6044to6045…in all four dialects: 6031→6038 shipped in 6.3.2, 6038→6040 in 6.3.3, and 6040→6045 came after it; at master540347500the chain ran toUpgrade6095to6096…, and in 6.3.4 it runs toUpgrade6103to6104…, sixty-four steps after 6.3.3, all shipped in 6.3.4 (Changes-Since-6210 section 2 has each step) - Per-dialect coverage: MSSQL and MySQL scripts exist from
Upgrade0to1100; MSSQLCE and PGSQL scripts start atUpgrade5001to5002(source:ls hmailserver/source/DBScripts/— first CE/PGSQL files areUpgrade5001to5002MSSQLCE.sql,Upgrade5001to5002PGSQL.sql) - DBUpdater registers steps in
formMain.LoadScripts(); the last registered step wasnew UpgradeScript(6030, 6031)when this line was written (source: hmailserver/source/Tools/DBUpdater/formMain.cs:159,242) and isnew UpgradeScript(6044, 6045)at3233b0cc4(formMain.cs:255)new UpgradeScript(6095, 6096)at540347500(formMain.cs:306) andnew UpgradeScript(6103, 6104)in 6.3.4 (formMain.cs:314 at the tag). The service can also walk the chain itself as it starts where[Database] UpgradeOnStart=1(6.3.4;Common/Persistence/SchemaUpgrader, whichhmailserver --upgrade-databaseon Linux shares). After each step it runs a probe statement; since 6.2.26 the four foreign-key probes for 6030 readupdate hm_dbversion set value = value / (value - value) where not exists (select 1 from information_schema.table_constraints ...)because the earliercase when existsform crashed the SQL Server Compact provider and the crash oracle then stopped the service (GitHub issue 114);build/check-db-scripts.ps1now executes every probe against a freshly created database through the provider the server uses, with a negative control that must fail (source: v6.2.26 body; build/check-db-scripts.ps1:126-156) -
build/check-schema-versions.ps1exists and readsREQUIRED_DB_VERSIONfrom Constants.h and the step list (source: build/check-schema-versions.ps1:12,82-90) - The two refusal messages are verbatim in code: "The database is too new for this version of hMailServer. Please upgrade hMailServer." and "The database is too old for this version of hMailServer. Please run hMailServer Database updater (DBUpdater.exe) to upgrade it." — error 5011; refusal is in BOTH directions (
<or>required version); a version that cannot be detected is error 5010 (source: hmailserver/source/Server/Common/Application/Application.cpp:251-275) - The server reads the version with
select * from hm_dbversion(doc givesselect value from hm_dbversionfor the operator; column isvalue) (source: hmailserver/source/Server/Common/SQL/DatabaseConnectionManager.cpp:416) - Control Panel shows "Database schema version" on the Status > Server status page (source: hmailserver/source/Tools/ControlPanel/Views/StatusView.xaml.cs:165; NavigationMap.cs:207)
- DBSetupQuick decides create vs upgrade by
_application.Database.DatabaseExistsand launchesDBUpdater.exefrom its own directory (AppContext.BaseDirectory) with/SilentIfOk, plus/silentif it was given/silent; returns DBUpdater's exit code (source: hmailserver/source/Tools/DBSetupQuick/Program.cs:29-62) - Installer runs
{app}\Bin\DBSetupQuick.exewhen a database type is configured or the internal engine is chosen, else{app}\Bin\DBSetup.exe; checks both launch success AND exit code (source: hmailserver/installation/hMailServerInnoExtension.iss:955-985) - .NET Desktop Runtime major is
10; bundled file name iswindowsdesktop-runtime-<channel>-win-x64.exe; installed with/install /quiet /norestartonly when missing (source: hmailserver/installation/hMailServer64.iss:16,18; hMailServerInnoExtension.iss:432) - DBSetup, DBSetupQuick and DBUpdater are .NET apps needing the same runtime (source: hmailserver/installation/section_files_64.iss:17)
-
hm_adsynchronizationis dropped in step 5004→5005 on MSSQL, MySQL, MSSQLCE and PGSQL (select hm_drop_table('hm_adsynchronization')), and no CreateTables script creates it (source: hmailserver/source/DBScripts/Upgrade5004to5005PGSQL.sql:35; Upgrade5004to5005MSSQL.sql:101; Upgrade5004to5005MySQL.sql:35; Upgrade5004to5005MSSQLCE.sql:42;grep -il adsynchronization CreateTables*.sql= none) - Unattended install flags documented in root README:
/SILENT,/VERYSILENT, example uses/VERYSILENT /SUPPRESSMSGBOXES /LOG=(source: README.md:134-152)
-
"carries on": doc says a non-zero DBSetupQuick exit shows a dialog and the installer "goes on to start the service and the wizard finishes" (Upgrading.md:104-113). Code now raises:
if (RunPostInstallTasks() = false) then RaiseException('The hMailServer database could not be created or upgraded…')so the install FAILS with a non-zero exit code; the comment says the result "used to be discarded" (source: hmailserver/installation/hMailServerInnoExtension.iss:1211-1218) -
Silent-upgrade hang: doc says
DBSetupQuick.exedoes not forwardpassword:on the upgrade path and DBUpdater opens a modal dialog regardless of/silent(Upgrading.md:169-188). Code now forwardspassword:<value>on the upgrade path (source: Tools/DBSetupQuick/Program.cs:49-55), the installer passespassword:+ admin password to DBSetupQuick (source: hMailServerInnoExtension.iss:936-944, comment: "until 6.2.23 DBSetupQuick forwarded only /silent"), andAuthenticator.AuthenticateUsertries empty → thepasswordargument → each raw argument → then under/silentreturns false instead of prompting (source: hmailserver/source/Tools/Shared/Miscellaneous/Authenticator.cs:24-49). The paragraph is stale as of 6.2.23. -
PostgreSQL keeps
hm_adsynchronization: doc says step 5004→5005 drops it "but not on PostgreSQL" (Upgrading.md:69-76). The PGSQL script drops it too (source: Upgrade5004to5005PGSQL.sql:35). - Doc's "Verified against the code" section (Upgrading.md:200-216) names the right functions but was written before the three changes above.
- Mail not moving while the server looks healthy; split into "Accepting" (sender times out after DATA) vs "Delivering" (accepted, never leaves) (source: DiagnosingStalledMail.md:14-28)
- Origin: GitHub discussion 18, three releases to track down (source: DiagnosingStalledMail.md:7-9)
- Decide which half: Postfix
timed out while sending end of data= accepting; message visible in queue and never leaves = delivering (source: :20-26) - Control Panel → Logging → tick "Debug messages"; reproduce once; turn it off afterwards (source: :33-38). Control Panel label confirmed:
"Debug messages"bound toLogging.LogDebug(source: hmailserver/source/Tools/ControlPanel/Views/ServerSettingsView.xaml.cs:2045) - Read the accept-pipeline stage lines in sequence; the stuck stage is the one after the last line written (source: :60-69)
- If every message stalls, look for the work-queue saturation lines (source: :111-131)
- For delivery: check scanner, slow remote, external tool, database; check delivery queue in Control Panel and the SMTP log (source: :133-156)
- Report: log from
354onward with stage lines,ERROR_hmailserver_<date>.log, scanners/scripts enabled, whether the message arrives (source: :190-201)
-
SMTPConnection - accept: start spam-protection.(source: hmailserver/source/Server/SMTP/SMTPConnection.cpp:1732) -
SMTPConnection - accept: done <stage> in <n> ms (session <id>).for stagesspam-protection,message-modifications,script/save(source: SMTPConnection.cpp:1746,1750,1962,1993) -
SMTPConnection - accept: start script/save.(source: SMTPConnection.cpp:1882) - There is NO
start message-modificationsline — the middle stage only has adone(source: SMTPConnection.cpp:1746-1750) - A stage ≥ 10 000 ms is logged at application level, otherwise debug (source: SMTPConnection.cpp:1999-2007)
-
Spam test: <name>, Score: <n>, Time: <n> ms; ≥ 10 000 ms escalates to application level (source: hmailserver/source/Server/Common/AntiSpam/SpamTestRunner.cpp:125-126) -
Task {0} waited {1} seconds for a thread in work queue {2}. {3} task(s) are still queued.(source: hmailserver/source/Server/Common/Threading/WorkQueue.cpp:317) -
All {0} threads in work queue {1} have been busy for at least {2} seconds, so no further task on this queue can start. {3} task(s) are queued. Running: {4}— error 5526, High (source: WorkQueue.cpp:566-569) - The queue is named
"Asynchronous task queue"(source: hmailserver/source/Server/Common/Application/Application.cpp:87) - Finalization deadline: error 5525 (Medium) then
451 4.3.1 Server temporarily overloaded while accepting the message; please retry.;FinalizationTimeout <= 0disables the deadline (source: SMTPConnection.cpp:2010-2024) - Database-unavailable recipient check answers
451 4.3.2 Unable to verify the recipient at the moment. Please retry later.instead of 550, in twoDatabaseUnavailableMarker::Scopeblocks (source: SMTPConnection.cpp:1218-1228,1380-1386) - Error log file name is
ERROR_hmailserver_<date>.log(source: hmailserver/source/Server/Common/Application/Logger.cpp:477)
-
[Settings] FinalizationTimeoutdefault 240 (source: hmailserver/source/Server/Common/Application/IniFileSettings.cpp:281); consumed at SMTPConnection.cpp:2012 -
SAMaxTimeoutdefault 90;SAMinTimeoutdefault 30 (source: IniFileSettings.cpp:274-275) -
ClamMaxTimeoutdefault 90;ClamMinTimeoutdefault 15 (source: IniFileSettings.cpp:282-283) -
DNSQueryTimeoutdefault 10 (source: IniFileSettings.cpp:293); consumed at Common/TCPIP/DNSResolverWinApi.cpp:120 -
ScriptTimeoutdefault 60 (source: IniFileSettings.cpp:307); consumed in Common/Scripting/ScriptServer.cpp -
ExternalProcessTimeoutdefault 300 (source: IniFileSettings.cpp:308); consumed in Common/Util/ProcessLauncher.cpp -
ClientSessionCeilingdefault 1800 (source: IniFileSettings.cpp:299); consumed at SMTP/SMTPClientConnection.cpp:65 viaSetSessionCeiling -
AsyncQueueStallThresholddefault 120 (source: IniFileSettings.cpp:314); consumed at WorkQueue.cpp:451 -
DBConnectionAcquireTimeoutdefault 60 (source: IniFileSettings.cpp:306); consumed at Common/SQL/DatabaseConnectionManager.cpp:436 -
DNSServerdefault "" (source: IniFileSettings.cpp:401) - SpamAssassin: idle timeout =
TimeoutCalculator::Calculate(SAMinTimeout, SAMaxTimeout); session ceiling =SAMaxTimeout + 30(source: hmailserver/source/Server/Common/AntiSpam/SpamAssassin/SpamAssassinClient.cpp:32,39) - ClamAV: per-operation timeout =
Calculate(ClamMinTimeout, ClamMaxTimeout)(source: hmailserver/source/Server/Common/AntiVirus/ClamAVVirusScanner.cpp:62) -
TimeoutCalculator::Calculate(min,max)starts at max, scales down with connection count, and clamps UP to min if below it — so max=0 yields min, never "unbounded" (source: hmailserver/source/Server/Common/Application/TimeoutCalculator.cpp:27-55; note the file lives inCommon/Application/, notCommon/Util/)
-
DNSResolverWinApi.cppsetsserverAddress.sin_port = 0with a comment "Do not 'fix' this line"; port 53 returns status 87 and no records (source: hmailserver/source/Server/Common/TCPIP/DNSResolverWinApi.cpp:273-292) - Custom server list is applied together with
DNS_QUERY_BYPASS_CACHE(source: DNSResolverWinApi.cpp:306)
- Nothing contradicted. Minor: Logger.cpp is under
Common/Application/, TimeoutCalculator underCommon/Application/(neither is inCommon/Util/).
- Active/passive topology with shared database + shared message store + VIP + health probe; no clustering code; active/active out of scope (source: HighAvailabilityRunbook.md:3-11)
- External database (MSSQL/MySQL/PostgreSQL), not SQL CE (source: :50-51)
- Both nodes: identical
[Database]INI settings and the same[Directories] DataFolderon shared storage; there is no setting calledDataDirectory(source: :52,59-64). Confirmed: key is("Directories","DataFolder")and the accessor isdata_directory_(source: IniFileSettings.cpp:129) - Other
[Directories]keys that exist:ProgramFolder,TempFolder,EventFolder,DatabaseFolder,LogFolder(source: IniFileSettings.cpp:125-164,886)
-
MetricsServerPortdefault 0 (off) (source: IniFileSettings.cpp:426) -
MetricsServerBindAddressdefault127.0.0.1(source: IniFileSettings.cpp:427) -
MetricsServerAuthToken,MetricsServerAuthUsername,MetricsServerAuthPassword,MetricsServerCertificateFile,MetricsServerPrivateKeyFileall default "" (source: IniFileSettings.cpp:433-437) -
ShutdownDrainSecondsdefault 0 = no drain (source: IniFileSettings.cpp:408; Application.cpp:826-828) - Doc's example block:
MetricsServerPort=8080,MetricsServerBindAddress=0.0.0.0,MetricsServerAuthToken=<32+ chars>,ShutdownDrainSeconds=30(source: HighAvailabilityRunbook.md:87-93)
-
/livez→ 200alivewhenever the listener is up;/readyz→ 200readyor 503not ready: <reason>;/healthz→ JSON, 200/503; all three are answered BEFORE any refusal branch (source: hmailserver/source/Server/Common/Util/MetricsServer.cpp:925-950) -
/readyzis 200 only when running AND the database answered a probe withinDatabaseProbeStalenessMilliseconds = 20000(source: MetricsServer.cpp:108,2345) -
/metricsanswers 503 (not 401) on a non-loopback bind with no credential; the log line namesMetricsServerAuthToken, the username/password pair, "or bind to 127.0.0.1 or ::1" (source: MetricsServer.cpp:375-397,415-419) - Bearer scheme:
WWW-Authenticate: Bearer realm="hMailServer metrics"(source: MetricsServer.cpp:1404) - Loopback test accepts the whole of
127.0.0.0/8and exactly::1(source: MetricsServer.cpp:632-672) - Request-read deadline 5000 ms, response-write deadline 15 000 ms, socket timeout 5000 ms (source: MetricsServer.cpp:59,65,76)
- Invalid bind address logs
MetricsServer: Invalid bind address: <addr>(source: MetricsServer.cpp:319) - Drain order:
StopServerssetsStateStoppingfirst, then waits up toShutdownDrainSeconds, then tears down listeners; metrics listener kept up through the drain (source: Application.cpp:808-851) - Regression coverage:
hmailserver/test/RegressionTests/Infrastructure/HealthProbes.cs,MetricsSecurity.cs(source:ls)
- Planned failover: (1) stop service on A —
/readyz503 then drain; (2) verify/readyzon A refused; (3) move VIP; (4) start B, wait for/readyz200; (5) send test message, check/healthzshowsdatabase: up,state: running(source: HighAvailabilityRunbook.md:155-166) - Unplanned: LB drops A; fence A; move VIP; start B; bring A back as passive (source: :172-185)
- Validation checklist of 7 items (source: :191-203)
-
IPv4-only bind address: doc says
MetricsServerBindAddress"takes an IPv4 literal and nothing else" and IPv6 is rejected (HighAvailabilityRunbook.md:112-117). Code:ParseBindAddressaccepts IPv4 or IPv6 literals (family chosen by presence of a colon; scopedfe80::1%3rejected) andIsLoopbackAddress_accepts::1(source: MetricsServer.cpp:195-231,632-660). Host names are still rejected. -
/healthzbody: doc says it includessessionsper protocol (HighAvailabilityRunbook.md:130). Code: body is exactly{"status","state","database","uptime_seconds"}; session counts were deliberately removed from/healthzbecause it is unauthenticated (source: MetricsServer.cpp:2395-2412) - Doc's verification section (:220-235) predates both changes.
- A second machine that takes over on failure; the four clauses: share the database, replicate the data directory, keep the standby's service stopped, DPAPI-protected passwords do not travel (source: WarmStandby.md:1-11)
-
PersistentMessage::UnlockAll()runsupdate hm_messages set messagelocked = 0 where messagetype = 1 and messagelocked = 1and is called fromSMTPDeliveryManager::Start()(source: hmailserver/source/Server/Common/Persistence/PersistentMessage.cpp:226-228; SMTP/SMTPDeliveryManager.cpp:80) - Queue selection:
select * from hm_messages where messagetype = 1 and messagelocked = 0 and messagenexttrytime <= <now> order by messagesize, messagecurnooftries, messageid ascinLoadPendingMessageList_(source: SMTPDeliveryManager.cpp:115-127) -
PersistentFetchAccount::UnlockAll()called fromExternalFetchManagerat start (source: Persistence/PersistentFetchAccount.cpp:50; ExternalFetcher/ExternalFetchManager.cpp:71) - DPAPI:
CryptProtectData(..., L"hMailServer secret", ..., CRYPTPROTECT_LOCAL_MACHINE, ...)(source: hmailserver/source/Server/Common/Util/DataProtector.cpp:38);Crypt::ProtectSecret(source: Common/Util/Crypt.cpp:191) - Protected columns:
routeauthenticationpassword(PersistentRoute.cpp:74,145),fapassword(PersistentFetchAccount.cpp:102,167),sslprivatekeypassword(PersistentSSLCertificate.cpp:53,100),domainrelaypassword(PersistentDomain.cpp:484) — matches the doc's "route, fetch-account, per-domain-relay and SSL-key passwords" -
[Settings]mirror:IniSettingStoremirrors the[Settings]section intohm_inisettingswith a three-way merge; on both-changed conflict "the file wins" (source: hmailserver/source/Server/Common/Application/IniSettingStore.h:18,36,46; DBScripts/CreateTablesMSSQL.sql:337). Re-checked 15 September 2026 against v6.3.3: the merge does not carry a primary's change to a standby -inisettingfilevalueis one shared row, so a standby starting with its older file is read as "the file changed" and its old value wins and is written over the row, and a standby with no[Settings]keys drops the agreeing rows. Warm Standby now says to copy the section on 6.3.3. Since 6.3.4 (schema 6042, checked at3233b0cc4) the table is the store: the stored value wins on every machine, an edit to a machine's file is HM5804 and is put back where the service may write the file (in 6.3.4 as released, a file read-only to the service,NT SERVICE\hMailServer's case, keeps the edit - The Service Account),PasswordPepperstays in each machine's file only, and[SettingsOverride]forces keys per machine (HM5807) (source: IniSettingStore.cppSynchronize,IsFileOnlyName; IniFileSettings.cppLoadDatabaseSettings) - Certificate/DKIM key columns hold PATHS:
hm_sslcertificates.sslcertificatefile,sslprivatekeyfile(CreateTablesMSSQL.sql:696-697);hm_domains.domaindkimprivatekeyfile(CreateTablesMSSQL.sql:251) -
DatabaseStatementTimeoutdefault 30 (source: IniFileSettings.cpp:645) -
/readyz200 = running and database answered within 20 s (source: MetricsServer.cpp:108,2345) - Schema pin refuses mismatch in both directions (source: Application.cpp:257-265)
- Building the standby: (1) same build, same drive letter/layout; (2) point
[Database]at the same server, verify with Control Panel, then stop and set to Manual (not Disabled); (3) one-way replication primary→standby of message store + certs + DKIM keys (robocopy /MIRevery few minutes is the baseline; DFS-R/storage replication OK; never SMB or CSV as the live store); (4) record the DPAPI credential list; (5) rehearse (source: WarmStandby.md:183-196) - Failover: (1) confirm primary is stopped/powered off/isolated; (2) stop replication; (3) start standby service; (4) move DNS/VIP/port-forward (keep MX TTL 300–3600 s); (5) re-enter route/fetch/relay passwords on the standby; (6) verify (source: :198-219)
- Failback: resync data directory backwards first, stop standby, final delta, start primary, move endpoint, re-enter DPAPI credentials on primary, re-establish replication (source: :221-229)
- Verify:
GET /readyz200; inbound + IMAP/POP3 read-back; one message through each credentialed route; check application log for ERROR since failover; confirm TLS certificate on 25/143/110/587/993/995 (source: :231-243)
- Two servers on one database double-deliver (startup unlock + uncoordinated selection); a POP3 fetch account fetched twice with delete-after-download splits mail (source: :16-34)
- Never copy the primary's INI over the standby's — its
[Database] Passwordis DPAPI-per-machine (source: :109-113). Confirmed: INI password written viaprotectedValue(source: IniFileSettings.cpp:1110) - SQL Server Compact is excluded from everything (source: :171-174)
- None found. The SMB/CSV section is declared "operational judgement" not code fact (source: :263-267).
- Moving from one of the four backends to another (in practice off SQL CE, which the installer still defaults to) via backup → repoint → restore; no migration tool exists or is needed (source: MigratingDatabaseBackend.md:1-13)
- Neither
BackupExecuternorBackupRestorerbranches on database type (source:grep DatabaseType|MSSQL|MySQL|PostgreSQLover both files = no hits) -
Account::XMLStorewrites no id;PersistentAccounttreatsGetID() == 0as new (source: hmailserver/source/Server/Common/BO/Account.cpp:296; Persistence/PersistentAccount.cpp:478-494) - On-disk message path:
<DataFolder>\<domain>\<local-part>\<2 chars of guid>\<guid>.eml; public folder is<DataFolder>\#Public\<xx>\<guid>.eml; queue is<DataFolder>\<partial>(source: Persistence/PersistentMessage.cppGetFileName(...FileLocation); IMAP/IMAPConfiguration.cpp:207-210 returns"#Public") - Backup refuses with
All messages are not located in the data folder.if any row points outsideDataFolder(source: Common/Application/BackupExecuter.cpp:369) - Archive name
HMBackup <time>.7z; indexhMailServerBackup.xml(source: BackupExecuter.cpp:432,443; BackupRestorer.cpp:24; BackupRetention.cpp:23 documentsHMBackup YYYY-MM-DD HHMMSS.7z) - Restore refuses an archive lacking domains / settings / messages when that option is selected, each with a "Restore refused: …Nothing has been changed on this server." message (source: BackupRestorer.cpp:332,338,344,452)
- Restore ends with
Reinitializator::Instance()->ReInitialize()("Reinitializing server (async)...") — the server reinitialises, it does not restart the Windows service (source: BackupExecuter.cpp:844-851)
-
[Database] TypeacceptsMSSQL,MYSQL,PostgreSQL,MSSQLCEcase-insensitively;Internal(0/1),Server,Database,Username,Password,Port(0 = default) (source: IniFileSettings.cpp:99-123) -
[Settings] BackupMessagesDBOnlydefault 0; read by both BackupExecuter (362,736) and BackupRestorer (361) so it must match on both sides (source: IniFileSettings.cpp:397; BackupExecuter.cpp:362,736; BackupRestorer.cpp:361). Since 6.3.4 it is a stored setting from schema 6042: set with the Control Panel,PUT /api/v1/settings/ini/BackupMessagesDBOnlyorhmctl ini set, not by editing the file; on the same machine the line the store writes into the file, where the service may write it, is adopted by the new database at its first start - underNT SERVICE\hMailServerthe store writes no line, so check the key on the new database before the restore, as Migrating the Database Backend says. From 23 September 2026 (6.3.4) a stored value applies when it is saved; this key is markednow - MSSQL session isolation:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED(source: Common/SQL/ADOConnection.cpp:213) - MySQL: transactions only if every table reports InnoDB at connect (source: Common/SQL/MySQLConnection.cpp:494; DatabaseConnectionManager.cpp:566-578)
- COM API names used in the doc's PowerShell all exist:
Settings.Backup(IDL:605),IInterfaceBackupSettings.Destination/BackupSettings/BackupDomains/BackupMessages(IDL:2304-2312),BackupManager(IDL:1672),StartBackup(),LoadBackup(file)(IDL:2291-2292),IInterfaceBackup.RestoreDomains/RestoreSettings(COM/InterfaceBackup.cpp:85-133),StartRestore()(InterfaceBackup.cpp:19) (source: hmailserver/source/Server/hMailServer/hMailServer.idl; COM/InterfaceBackup.cpp) -
DBSetupQuick.execreates the schema whenDatabase.DatabaseExistsis false and returns a non-zero exit code on failure (source: Tools/DBSetupQuick/Program.cs:29). Since 19 October 2026 the document says what that means: it creates only the built-in SQL Server Compact database, when[Database]names no type, and with the section naming any other backend it starts DBUpdater, which cannot build a schema from an empty database -
DBSetup.exe(interactive wizard) exists (source:ls hmailserver/source/Tools/DBSetup) -
Stop-Service hMailServer/Start-Service hMailServer— service namehMailServer(source: build/preflight-tests.ps1 useshMailServer; hMailServerInnoExtension.iss:709IsServiceRunning('hMailServer')) - SQL log device writes to table
hm_log, created at runtime bySqlLogDevice(not by CreateTables scripts) — so it is indeed outside the backup (source: Common/Application/SqlLogDevice.cpp:296,731,750;grep hm_log CreateTablesMSSQL.sql= none) -
[Database]also accepts (6.2.25):AllowUnencryptedConnection(0; MySQL fallback from TLS),PostgreSQLSslModeandPostgreSQLSslRootCert(""; libpq sslmode/sslrootcert — an unknown mode refuses the connection with the reason rather than falling back to prefer),ConnectionStringOptions(""; appended verbatim to the MS SQL OLE DB connection string) — the doc's backend table now shows the recommended values (ConnectionStringOptions=Encrypt=yes;TrustServerCertificate=no,PostgreSQLSslMode=verify-full+PostgreSQLSslRootCert,AllowUnencryptedConnection=1for a MySQL with no TLS) (source: IniFileSettings.cpp:162-165; MigratingDatabaseBackend.md:61-63). Verified by reading only; the bench runs SQL Server Compact (v6.2.25 known limitation 1).
-
Stop-Service hMailServer; restart for the backup itself; hold inbound traffic (source: :104-117) - Backup with domains + settings + messages, via Control Panel or the COM script (source: :119-143); optionally
BackupMessagesDBOnly=1before both backup and restore, remove afterwards (source: :145-159); since 6.3.4, stored rather than edited into the file, and removed withDELETE /api/v1/settings/ini/BackupMessagesDBOnly - Create an empty target DB, edit
[Database], run& "C:\Program Files\hMailServer\Bin\DBSetupQuick.exe"and check$LASTEXITCODE(source: :161-197). Since 19 October 2026 the document's step 3 runsDBSetup.exeinstead - the wizard's Create a new hMailServer database, orDBSetup.exe /unattended- which creates the database and its schema, writes[Database]through the service and restarts the server on it; the step as written here made no usable database (Migrating the Database Backend, NotDBSetupQuick.exe) -
Start-Service hMailServer,LoadBackup(...), setRestoreDomains/RestoreMessages/RestoreSettings,StartRestore()(source: :199-215) - Verify counts, IMAP visibility, a full message body, settings, send/receive, empty error log (source: :217-236)
- Rollback: put the old
[Database]section back; old DB untouched (source: :238-252)
-
Public folder directory name: doc says
<DataFolder>\.Public_Folder\<xx>\<guid>.eml(:47). Code returns"#Public"(source: IMAPConfiguration.cpp:209). -
Error log file name: doc says
hmailserver_ERROR_<date>.log(:234). Code writesERROR_hmailserver_<date>.log(source: Logger.cpp:477). DiagnosingStalledMail.md has it right. -
Control Panel navigation: doc says "Settings → Advanced → Backup" (:121). hMailCP's map is
Utilities > Backup/Utilities > Backup & restore(source: Tools/ControlPanel/Services/NavigationMap.cs:279-280). "Settings > Advanced" exists but holds Routes/IP ranges/SSL certificates/TCP-IP ports (NavigationMap.cs:227-231). -
SQLLogging: doc says "If you useSQLLogging" (:267-268). No setting by that name exists; the SQL log device is selected by the database-heldLogging.LogDeviceproperty (PROPERTY_LOGDEVICE, COMhLogDeviceSQL) (source: Common/Application/Configuration.cpp:103-111; COM/InterfaceLogging.cpp:342-380). -
"The server restarts itself" (:215): it reinitialises in-process via
Reinitializator; the Windows service is not restarted (source: BackupExecuter.cpp:844-851).
- Bringing existing mailboxes into this server: from another IMAP server, from mbox files, from a Maildir, from Outlook, and from the original hMailServer, in place up to 5.6 (its 5.7 releases by a migration of kind
imap, as the document says since 21 October 2026); what each route keeps and what it does not; why PST is not parsed (source: Migration.md:1-8, 129). Points to Upgrading.md for release-to-release moves and MigratingDatabaseBackend.md for a change of database (:9-12)
- Another IMAP server: an external account with Mirror every folder on —
FetchAccount.ServerType1 (IMAP) plusFetchAccount.MirrorFolders(schema 6031,hm_fetchaccounts.famirrorfolders);LIST "" "*", then per selectable mailboxSELECT,UID SEARCH ALL,UID FETCH (FLAGS INTERNALDATE BODY.PEEK[])for every UID not on record; keeps folders and hierarchy (delimiter mapped), every message byte for byte,\Seen \Flagged \Answered \Draft \Deleted, the internal date; does not keep subscriptions, custom keywords, ACLs; nothing is delivered, so no rule, anti-spam or anti-virus touches a copy andOnExternalAccountDownloaddoes not fire; days to keep messages -1 makes it a move (0 before 6.3.6); use a new external account for the mirror (source: Migration.md:26-87; hMailServer.idl:1854,1889; DBScripts/Upgrade6030to6031MSSQL.sql; v6.2.27 body) - mbox: the Import Tool, one IMAP folder per file, via
Utilities.ImportMessageFromFileToIMAPFolder(IDL id 13); no flags (source: Migration.md:18-20, 88-100; hMailServer.idl:1209) - Maildir: the Import Tool's Select Maildir button reads
cur/new/tmpand every Maildir++ folder beside it, flags from:2,/;2,/!2,file names, line endings made CRLF (source: Migration.md:101-127; Tools/ImportTool/formChooser.Designer.cs:65; v6.2.27 body) - Outlook: through IMAP (add this server as an IMAP account, drag folders) — PST is deliberately not parsed (source: Migration.md:129-139)
- The original hMailServer up to 5.6: in place, per Upgrading.md; its 5.7 releases: not in place, a migration of kind
imapfrom it with the domains and accounts made here first (Migration.md's "From the original hMailServer", since 21 October 2026; the table above); accounts in bulk: the Import Tool's account import from a comma-separated text file (address, password, optional quota) (source: Migration.md:141-157)
- None found; the doc is newer than every other runbook.
- Building a machine on which
build/run-tests.ps1passes;build/preflight-tests.ps1is the machine-readable twin (source: RegressionEnvironment.md:1-18) - Warning: never run against production — authenticates as Administrator, binds live ports, wipes data (source: :17-18)
- Suite size: 2,302
[Test]attributes at v6.3.3, 3,390 at master540347500(24 September 2026) 3,698 at92b8f9f63and 3,811 in 6.3.4 (the tag; the same grep), whose gates run in two halves on two benches. When this line was written: 2127 ("roughly 2,000" in the doc since 6.2.28; the 6.2.28 release run reported 2,127 tests, 2,119 passed, 8 skipped) (source:grep -rn "^\s*\[Test\]" hmailserver/test/RegressionTests --include=*.cs | wc -lat f088336c2; v6.2.28 body) - Step 1:
build/build.ps1 -Configuration Release, thenbuild/post-build.ps1 -Configuration Release(self-elevates viaStart-Process powershell -Verb runAs); service must point athmailserver\source\Server\hMailServer\x64\Release\hMailServer.exe(source: build/post-build.ps1:8-9,17; preflight-tests.ps1:36-44) - Preflight check 1 reads the service
PathName; recovery text: "post-build.ps1, re-apply the sc sdset AU grant, then delete HKLM\SOFTWARE\hMailServer in BOTH /reg:32 and /reg:64" (source: preflight-tests.ps1:42-44) - Preflight check 2:
reg query "HKLM\SOFTWARE\hMailServer" /v InstallLocationin both views must FAIL (source: preflight-tests.ps1:54-59) - Provisioning list is in
IMPLEMENTATION-NOTES.mdunder "Test-environment recipe (dev tree, no installer)";sc sdsetgrant at line 334 (source: IMPLEMENTATION-NOTES.md:288,334) -
build/make-hmailserver-writable.ps1exists (source:ls build/) - Step 2: SQL CE 4.0 x64 package in
hmailserver/installation/SQLCE/SSCERuntime_x64-ENU.msi(source: third-party-binaries.json path list); COMDatabase.CreateInternalDatabase(),DatabaseExists,Application.Reinitialize()exist (source: hMailServer.idl:1053,1055,1670) - Preflight check 4 compares DB version to
REQUIRED_DB_VERSION("Database schema at the required version") (source: preflight-tests.ps1:96);build/upgrade-test-database.ps1andbuild/check-db-scripts.ps1exist; RELEASE.md step 6 isbuild\check-db-scripts.ps1(source: RELEASE.md:32) - Step 3: data folder
C:\HMTest\Datais hard-coded in the preflight (source: preflight-tests.ps1:261) - Step 4: preflight expects
Domains.Count -eq 1andSettings.TCPIPPorts.Count -eq 4; listening check is on 25, 110, 143 only (source: preflight-tests.ps1:72-78,136-139) - Admin password
testar:application.Authenticate("Administrator", "testar")(source: hmailserver/test/RegressionTests/Shared/TestSetup.cs:34; preflight-tests.ps1:69) - Step 5: ClamAV — preflight checks a listener on 3310;
CustomAssertsstartsC:\clamav\clamd.exeif noclamdprocess (source: preflight-tests.ps1:128-129; test/RegressionTests/Infrastructure/CustomAsserts.cs:179-184 — note the file is underInfrastructure/, notShared/) - Step 6: service name
SpamAssassinJAMchecked by preflight; used byAntiSpam/SpamAssassin.csandInfrastructure/CustomAsserts.cs(source: preflight-tests.ps1:133-134; grep) - Step 8:
build/build-tests.ps1restores via MSBuild/t:Restore /p:RestorePackagesConfig=trueintohmailserver\test\packagesand builds/p:Platform=x64(source: build/build-tests.ps1:33-47) - Step 9: runner is
hmailserver\test\packages\NUnit.ConsoleRunner.3.22.0\tools\nunit3-console.exe; assemblyhmailserver\test\RegressionTests\bin\x64\Debug\RegressionTests.dll;-Wheremaps to--where;-StopOnErrormaps to/stoponerror(source: build/run-tests.ps1:6,16-17,25,28) - "Never abort a run" and "the run is void if anything changes" are RELEASE.md steps (source: RELEASE.md:103-105, step 9 — they moved down when steps 8b and 8c were added in 6.2.25)
-
preflight-tests.ps1 -Cleanremoves a stale ERROR log, leftover test-only INI keys, and empty orphan directories; the INI it inspects ishmailserver\source\Server\hMailServer\x64\Release\hMailServer.ini(source: preflight-tests.ps1:14-16,113,190-242,307) - Leftover keys the preflight scans for (exact list):
DNSServer, Pop3LoginDelaySeconds, PasswordPolicyMinimumLength, PasswordPolicyRequireMixedCase, PasswordPolicyRequireDigit, PasswordPolicyRequireNonAlphanumeric, PasswordPolicyRejectCommon, QuarantineEnabled, PasswordPolicyHistoryCount, PasswordPolicyMaximumAgeDays, DmarcTreeWalkEnabled, AuthenticationResultsEnabled, DNSQueryTimeout, SpfVoidLookupLimit, RejectFullMailboxAtRcpt, DkimAcceptSha1, QuotaWarningPercent, ArchiveDir, ArchiveRetentionDays, MetricsPerDomainEnabled, PreferredHashAlgorithm, FilterHookUrl, FilterHookFailClosed, FilterHookTimeoutSeconds, FilterHookRejectScore, SMTPProxyProtocolEnabled, SMTPProxyProtocolTrustedIPs, SMTPXClientEnabled, SMTPXClientTrustedIPs, OtelEndpoint, OtelMetricsEndpoint, OtelLogsEndpoint; a key with an EMPTY value is not a leftover (source: preflight-tests.ps1:206-236) - Success line:
Pre-flight passed - safe to run the suite.(source: preflight-tests.ps1:323)
- ESET
+ just send it; ProtonVPN/WireGuard loopback; CRLF checkout (.gitattributes); Mark-of-the-Web; "passes alone, fails in a group"; build number naming a dead tree (source: RegressionEnvironment.md:202-229)
- Doc says "the suite's
CustomAssertshard-codesC:\clamav\clamd.exe" — true, but the file istest/RegressionTests/Infrastructure/CustomAsserts.cs(noShared/CustomAsserts.cs). - Doc says the runner assembly is built "for x64" — run-tests.ps1 runs the Debug x64 assembly (
bin\x64\Debug), not Release (source: build/run-tests.ps1:27-28). The doc does not say which configuration; a page writer should say Debug. - "hMailServer fails open when the scanner gives no verdict" (:123) — UNCONFIRMED; no explicit fail-open branch was found by grep in
Common/AntiVirus/(VirusScanner.cppreturnsNoVirusFoundat :412 when nothing found; error paths not traced).
- libFuzzer + ASan harness for the MIME parser; HTML, iCalendar, vCard, Sieve/ManageSieve and the SMTP command parser are NOT covered (source: Fuzzing.md:10-13,367-383)
- The harness lives in
fuzz\(source: :15). Directory containsFind-ClangCl.ps1, README.md, artifacts/, bin/, build/, build-fuzz.ps1, corpus/, dict/, findings/, harness/, make-corpus.ps1, regression/, run-fuzz.ps1(source:ls fuzz/)
- Server compiled
/EHa:<ExceptionHandling>Async</ExceptionHandling>in both configurations (source: hmailserver/source/Server/hMailServer/hMailServer.vcxproj:79,150) -
Common/Util/CrashOracle.{h,cpp}exist (source:ls) - Fuzz build uses
/EHsc(source: fuzz/build-fuzz.ps1:299)
- Prerequisite: clang-cl with libFuzzer + ASan runtimes; portable LLVM to
%LOCALAPPDATA%\Programs\LLVM-22.1.8(doc example, external) or VS componentMicrosoft.VisualStudio.Component.VC.Llvm.Clang(source: Fuzzing.md:88-121) -
.\fuzz\build-fuzz.ps1[-Target <name>] [-BoostInclude <dir>] [-RuntimeLibrary MT|MD, defaultMT] (source: fuzz/build-fuzz.ps1:43-78) - Defines
/D_DISABLE_STRING_ANNOTATION=1 /D_DISABLE_VECTOR_ANNOTATION=1; library objects-fsanitize=fuzzer-no-link,address(source: build-fuzz.ps1:284,304) - Boost from
%hMailServerLibs%\boost_1_92_0, else$env:BOOST_INCLUDE_PATH, else-BoostInclude(source: build-fuzz.ps1:193-199; 1.92.0 since 6.2.25) - The server's
HM_ASSERT(the lowercaseassertsites were renamed to it on 5 September 2026) maps ontoASSERTin the shim, so-Assertsgoverns both; the suite counterpart isbuild\build.ps1 -Configuration Release -Asserts, where a violated assertion is HM6364 (source: Fuzzing.md:312,358-361, updated 5 Sep 2026) - A timed fuzz run is a release step: RELEASE.md step 8c runs each harness for 30 minutes on the release source and records the execution counts in the notes; step 8b runs the whole suite on the assertion build first (source: RELEASE.md:84-101; v6.2.25 verification line: 265,770 / 274,986 / 4,793,507 executions, no finding)
- Seed corpus regenerated by
make-corpus.ps1(source: build-fuzz.ps1:456) - Three targets:
mime_message_fuzzer,mime_header_fuzzer,mime_decode_fuzzer(source: build-fuzz.ps1:125-127; fuzz/harness/) - Shim header
fuzz/harness/shim/stdafx.h(source:ls)
-
.\fuzz\run-fuzz.ps1 -Target <t> [-Minutes N (default 5)] [-Jobs N] [-Replay] [-Reproduce <path>] [-MaxLen N (default 16384)]; passes-error_exitcode=77; exit 77 = finding (source: fuzz/run-fuzz.ps1:22,38-56,153,191) - Doc examples:
-Minutes 5smoke;-Replay;-Minutes 480 -Jobs 4overnight;-merge=1corpus minimisation;-minimize_crash=1 -runs=100000 -exact_artifact_path=...(source: Fuzzing.md:201-244,320-323) - Artifacts to
fuzz\artifacts\<target>\crash-<sha1>(timeout-,oom-,leak-) (source: Fuzzing.md:264-266) -
kMaxTraversalDepth = 8in the harness (source: fuzz/harness/fuzz_mime_common.h:151) -
mime_header_fuzzerreproducesUtilities::GetMimeHeader's call as shape 3 (source: fuzz/harness/mime_header_fuzzer.cpp:38,57) -
BEncode/QEncodeguardif (nMaxBlockSize < 3)present (source: hmailserver/source/Server/Common/Mime/MimeCode.cpp:681) - Committed reproducers:
fuzz/regression/mime_decode_fuzzer/{heap-buffer-overflow-mimeencodedword-decode, out-of-memory-mimeunicodeencoder-encodevalue},fuzz/regression/mime_message_fuzzer/{heap-buffer-overflow-findstring-boundary-scan, new-delete-type-mismatch-mimecodebase-nonvirtual-dtor}; nomime_header_fuzzerreproducers yet (source:ls fuzz/regression/*) -
fuzz/.gitattributes:regression/** -text -diff,corpus/** -text -diff,artifacts/** -text -diff,regression/README.md text eol=crlf diff,dict/*.dict text eol=crlf(source: fuzz/.gitattributes)
- Exclude
fuzz\from AV real-time scanning; do not run on the mail server; a stack-overflow inhm_fuzz::ExerciseBodyis a harness limit, not a finding (source: Fuzzing.md:249-259,290-295)
- Doc's suite count was corrected in 6.2.28 and is no longer stale: "over 2,000 tests as of September 2026", against 2,127 run in the 6.2.28 gate (2127
[Test]attributes then; 3,811 in 6.3.4) — and the doc itself says treat it as a floor (source: Fuzzing.md:57-58; v6.2.28 body). - Doc does not mention
fuzz/README.md,fuzz/dict/,fuzz/findings/ormake-corpus.ps1by name (only "regenerates the seed corpus").
- Dated self-assessment (12 Aug 2026): out of scope as a manufacturer under the EU Cyber Resilience Act (Reg. 2024/2847) and the revised Product Liability Directive (2024/2853); ineligible as an OSS steward (sole individual); triggers for re-doing it (source: RegulatoryScope.md:4-124)
- Key dates quoted: CRA in force 10 Dec 2024; Art. 14 reporting from 11 Sep 2026; remaining obligations from 11 Dec 2027; PLD transposition 9 Dec 2026 (source: :32-35,74-75) — legal claims, not code-checkable
- Distribution facts it depends on: AGPLv3, no paid edition/support/hosting, no telemetry, one individual maintainer, no donations (source: :19-27)
- Not legal advice (source: :13-14)
- SBOM workflow
.github/workflows/sbom.ymlexists, runs onreleaseand manualrelease_tagdispatch;build/merge-native-dependencies-into-sbom.ps1exists (source:ls; sbom.yml:27-33) - Binary inventory: 40 entries in
third-party-binaries.json;verify-binary-provenance.ymlruns on push and pull_request, warns (not fails) onremove-*dispositions (source: json artifact count; verify-binary-provenance.yml:35-38,89-90,152-153) - Signing:
sign-release.ymlusessigstore/cosign-installer(pinned to v3.10.1 SHA),sign-blob/verify-blobbundles (source: sign-release.yml:32-33,113-114) -
SECURITY.mdexists, at the repository root since 24 September 2026 (it was.github/SECURITY.mdbefore), and since 6.3.4 the served file'sPolicy:line names it on GitLab;security.txtserved at/.well-known/security.txtbyWebServicesServer— "for hosted domains with a postmaster address", no enable setting of its own (source: hmailserver/source/Server/Common/Util/WebServicesServer.cpp:476-479,891) - CodeQL analysed BOTH
csharpandc-cppwhile it ran on GitHub (source: .github/workflows/codeql.yml:88,229);scorecard.ymlexists - "Authenticode signing of the installer is still outstanding" is the doc's claim, and it is stale: the installer is Authenticode-signed from 6.3.1 (Azure Artifact Signing, Progressive Robot Ltd, countersigned; Installing hMailServer).
RegulatoryScope.mdstill says outstanding at master540347500(RegulatoryScope.md:172)
- None found in the checkable half.
At master
540347500(24 September 2026) this section describes a tree that has gone. No binary is committed to the repository any more -git ls-treefinds no.dll,.exe,.msior.tlb- andthird-party-binaries.jsonlists 35 artifacts, markedfetched(22),gathered(10),system(2) orgenerated(1), beside 3 build inputs thatbuild/get-installer-binaries.ps1downloads for the Windows installer build (the 7-Zip console, the MariaDB Connector/C client and the SQL Server Compact runtime). The document's first sentence still says "40 committed binary files", which is stale in the repository too; and theBinary provenancecheck is a GitHub Actions workflow, which has not run since GitHub suspended the project's account on 18 September 2026. What follows is the digest as it was written, on 4 and 8 September.
- Inventory and policy for the 40 committed binaries; JSON is the only place hashes live; CI (
Binary provenanceworkflow) fails on hash change, missing file, or unlisted binary (source: ThirdPartyBinaries.md:4-19,152-171)
- 40 artifacts; dispositions:
retain13,retain-review13,remove-duplicate10,remove-unused3,retain-generated1 (source: python count over third-party-binaries.json) - Authenticode: 12
Valid(10 signedCN=Microsoft Windows Software Compatibility Publisher= MSVC v145 CRT 14.51.36231.0; 2CN=Microsoft Corporation, OU=MOPR= the SQL CE MSIs), 28NotSigned(source: jsonauthenticode/authenticode_signercounts) - ALL 40 files listed still exist in the tree, including the 3
remove-unused(installation/System Files/dnsapi.dll,installation/isxdl.dll,installation/SQLCE/SSCERuntime_x86-ENU.msi) and the 10remove-duplicate(installation/Extras/libmysql.dll+Extras/plugin/*.dll) — nothing has been deleted since the doc was written (source: existence check per path) - JSON schema id
hmailserver-third-party-binaries/1;policyfield points athmailserver/docs/ThirdPartyBinaries.md(source: third-party-binaries.json:2-3) - Per-entry fields:
path, sha256, size, component, version, publisher, license, upstream, authenticode, authenticode_signer, disposition, used_by, why(source: third-party-binaries.json:6-20) - 7za.exe is 7-Zip 19.00; used via
Compression::GetExecutableFullPath_()(source: json entry; hmailserver/source/Server/Common/Util/Compression.cpp:80) - MariaDB Connector/C 3.4.9:
MySQLInterface.cppbuilds<exe dir>\libmysql.dlland loads plugins viaMYSQL_PLUGIN_DIR(source: Common/SQL/MySQLInterface.cpp:75,113) - ADO type libraries
#imported athmailserver/source/Server/hMailServer/stdafx.h:48(x64) and:52(x32) (doc says 47/51) -
installation/section_files_common.iss:11-12says atl70.dll "looks vestigial on a v145 build" and installs it to{sys};:33,37shipExtras\libmysql.dllandExtras\plugin\*.dll(source: section_files_common.iss) -
hMailServerInnoExtension.iss:CheckPortsexternal fromISC.DLLat :107-108, called at :709; the eightisxdl_*externals at :111-133; x86 branch comment at :852 (doc says 94/695, 97–119, 839 — line-number drift only) -
RMSPF.cppcallsLoadLibrary(_T("DNSAPI.DLL"))athmailserver/source/Server/SMTP/SPF/RMSPF.cpp:421(doc says 415; and it sits under#ifdef DNSAPI_SUPP, which is commented out at :51) -
libraries/build-openssl.ps1,build-boost.ps1andbuild-pgsql.ps1(withbuild-common.ps1) replacedbuild-dependencies.ps1in 6.2.25; each downloads one source archive from the project's own release server, verifies it against a SHA-256 pinned in the script and builds outside the tree; nothing fetched is committed and no script fetches any of the 40 listed binaries (source:ls libraries/; ThirdPartyBinaries.md:49-52, updated 5 Sep 2026) - The one manifest change since 6.2.24:
hmailserver/source/Tools/Interop/Interop.hMailServer.dllregenerated by the 6.2.25 release build (sha256f74c3e95…, 195,072 bytes) (source: third-party-binaries.json:412-414; commit f7418b81d) -
hmailserver/source/Server/hMailServer/post-build.batandhmailserver/source/Tools/Interop/README.md("Regenerating" section, tlbimp) exist (source:ls; Interop/README.md:3-11) - No
scorecard.yml/.github/scorecard.ymlannotation file exists — consistent with the doc's "deliberately not been added yet" (source:ls) - Still no LGPL-2.1 text for Connector/C in
docs/Licenses/— consistent with "Not yet done" (source:ls hmailserver/docs/Licenses/)
- Change the file; 2. update
sha256,size,version,upstream,authenticode— hash via(Get-FileHash <path> -Algorithm SHA256).Hash.ToLower(); 3. update the doc row; 4. push (source: ThirdPartyBinaries.md:176-184)
- Warning: never paste the hash CI printed without knowing why it changed (source: :186-188)
- Only line-number drift (listed above). Substantive claims hold.
- Title
hMailServer, uidhmailserver-overview, schemaVersion 39, version 2, refresh1m, default rangenow-24h; templating:datasource(prometheus) andinstancefromlabel_values(hmailserver_build_info, instance)(source: grafana-dashboard.json:3-31) - 19 entries in
panels: 4 row headers (Health; Mail flow; Authentication and TLS; Latency and database), 5 stat panels, 10 timeseries panels (source: python count). docs/README.md's "19 panels" counts the row headers. - Stat panels: Server state, Database, Uptime, Missing message files, TLS certificate expiry (amber < 21 d = 1 814 400 s, red < 7 d = 604 800 s) (source: :40-132)
- Timeseries: Delivery outcomes (stacked), Delivery queue depth + oldest age (right axis), Processed/spam/viruses, Active sessions, Authentication outcomes, TLS handshakes, Command processing latency p50/p95/p99, Database query latency p50/p95/p99, Database connection pool, Slow queries (source: :139-331)
-
hmailserver_auth_failures_total,hmailserver_auth_success_total,hmailserver_build_info{version,architecture,database_schema_version},hmailserver_command_processing_seconds(histogram →_bucket{le}),hmailserver_database_connected,hmailserver_db_connections{state="busy"|"available"},hmailserver_db_query_seconds(histogram),hmailserver_db_slow_queries_total,hmailserver_delivery_queue_messages,hmailserver_delivery_queue_oldest_message_age_seconds,hmailserver_messages_{bounced,deferred,delivered}_total,hmailserver_messagestore_missing_files,hmailserver_processed_messages_total,hmailserver_sessions{protocol="smtp"|"imap"|"pop3"},hmailserver_spam_messages_total,hmailserver_start_time_seconds,hmailserver_state{state=...}(StateSet),hmailserver_tls_certificate_expiry_seconds{certificate=...},hmailserver_tls_handshake_failures_total,hmailserver_tls_handshakes_total,hmailserver_viruses_removed_total(source:grep -o "hmailserver_[a-z_0-9]*" hmailserver/source/Server/Common/Util/MetricsServer.cpp | sort -u; labels at MetricsServer.cpp:1539-1543,1640,1661,1718-1720,1803-1811,2206) -
hmailserver_database_upis NOT emitted (only mentioned in a comment at :1667) — the Database panel description's "_connected rather than _up" remains accurate
-
hmailserver_database_probe_age_seconds,hmailserver_database_probe_success_timestamp_seconds(source: MetricsServer.cpp:1708-1710) -
hmailserver_workqueue_depth{queue},hmailserver_workqueue_blocking_tasks_waiting{queue}(source: MetricsServer.cpp:1566-1587) -
hmailserver_metrics_unauthorized_requests_total(source: MetricsServer.cpp:1517-1519) -
hmailserver_domain_messages_received_total,hmailserver_domain_messages_sent_total— only when[Settings] MetricsPerDomainEnabled=1(default 0) (source: MetricsServer.cpp:1594-1601; IniFileSettings.cpp:656)
- Upgrading.md vs installer: installer now FAILS (RaiseException, non-zero exit) on a failed DBSetupQuick; doc says it "carries on" (hMailServerInnoExtension.iss:1211-1218)
- Upgrading.md vs DBSetupQuick/Authenticator: silent upgrade with an admin password no longer hangs —
password:is forwarded and/silentfails instead of prompting (Program.cs:49-55; Authenticator.cs:31-49; iss:936-944 "until 6.2.23") - Upgrading.md vs DBScripts: PGSQL 5004→5005 DOES drop
hm_adsynchronization(Upgrade5004to5005PGSQL.sql:35) - HighAvailabilityRunbook.md vs MetricsServer: bind address accepts IPv6 literals and
::1loopback (MetricsServer.cpp:195-231,632-660);/healthzhas nosessionsfield (MetricsServer.cpp:2405-2412) - MigratingDatabaseBackend.md vs code: public folder dir is
#Publicnot.Public_Folder(IMAPConfiguration.cpp:209); error log isERROR_hmailserver_<date>.lognothmailserver_ERROR_<date>.log(Logger.cpp:477); Backup is underUtilitiesnotSettings > Advancedin hMailCP (NavigationMap.cs:279); no setting namedSQLLogging— it isLogging.LogDevice= SQL (Configuration.cpp:103-111); restore reinitialises in-process rather than restarting the service (BackupExecuter.cpp:844-851) - MigratingDatabaseBackend.md vs DiagnosingStalledMail.md: the two docs spell the error-log filename differently; DiagnosingStalledMail is correct
- docs/README.md omits WarmStandby.md and Fuzzing.md from its index; its closing "two documents above" is stale
- Line-number citations in ThirdPartyBinaries.md have drifted (stdafx.h 47/51→48/52; Compression.cpp 81→80; RMSPF.cpp 415→421; iss 94/97–119/695/839→107/111–133/709/852)
- Upgrading.md's
DBUpdater.exe"from the installation's Bin folder": consistent — DBSetupQuick resolves DBUpdater beside itself and the installer runs{app}\Bin\DBSetupQuick.exe(Program.cs:39; iss:959)
-
build/analyze.ps1(5 Sep 2026): the static-analysis build - the server Release x64 rebuilt with MSVC/analyze(/p:EnablePREfast=true /p:RunCodeAnalysis=true), full log tologs/analyze-Release.log, one line per distinct finding tologs/analyze-warnings.txt, the project's own defect-class findings (C6xxx, C28182, C33xxx) printed apart from those under libraries/, Boost, SQL CE and the Windows SDK. It replaces the Release binaries with the analysis build's output, sobuild/build.ps1 -Configuration Releaseis run again before a regression gate. First full triage 5 Sep 2026: 632 distinct findings, 28 defect-class in own code, 20 fixed in 7 files, 8 left with the reason in Roadmap.md (Static-analysis backlog) (source:ls build/; v6.2.25 body). - Schema 6030 (5 Sep 2026): seventeen
FOREIGN KEY ... ON DELETE CASCADEconstraints (accounts/aliases/domain aliases/lists -> domain, list recipients -> list, route addresses -> route, fetch accounts/app passwords/password history/index state -> account, fetch UIDs -> fetch account, rule criteria/actions -> rule, group members -> group, message recipients/metadata/index terms -> message, expunged marks -> folder). The upgrade deletes the orphans each constraint would reject first (children before parents); MySQL getsENGINE=InnoDBon every table involved. Columns where 0 is a value and two type-mismatched columns are deliberately without a key (roadmap row 'Referential integrity in the schema').Infrastructure/ReferentialIntegrityproves refusal and cascade against the SQL CE file (source: DBScripts/Upgrade6029to6030MSSQL.sql, 17FOREIGN KEYlines; v6.2.25 body — an earlier draft of this page said nineteen). - Authorisation choke point (5 Sep 2026): every folder-access decision is
ACLManager::CheckPermission(enforcement off = everything allowed, the historical meaning) orACLManager::CheckDelegatedRight(a right one account grants another -lunder #Users,pfor Send-As; enforcement off = nothing granted).GetPermissionForFolderonly reports rights (MYRIGHTS, SELECT).build/check-authz-choke-point.pyin style.yml fails any other caller. Behaviour change: with IMAP ACL off, rules and deliveries may file into public folders, as IMAP always allowed (source: Server/Common/Application/ACLManager.h:49,57; .github/workflows/style.yml:100). - Control Panel High Contrast (shipped in 6.2.25):
MainWindow.ApplySavedThemeappliesApplicationTheme.HighContrast(Wpf.Ui's SystemColors dictionary) wheneverSystemParameters.HighContrastis set, follows the switch at runtime (SystemParameters.StaticPropertyChanged) and ignores the light/dark toggle while it is on;ThemeTokens.IsHighContrastis the one answer and honoursHMAILCP_HIGH_CONTRAST=1sobuild/capture-cp.ps1 -Launch -HighContrastcan capture the result on an ordinary desktop (source: Tools/ControlPanel/MainWindow.xaml.cs:60,873; build/capture-cp.ps1:11-13).
- "hMailServer fails open when the scanner gives no verdict" (RegressionEnvironment.md:123) — no explicit branch located
-
"Authenticode signing of the installer is still outstanding" (RegulatoryScope.md:160)Settled, and the doc is wrong: the installer has been Authenticode-signed since 6.3.1; the sentence is at RegulatoryScope.md:172 at master540347500(section 9) - The legal determinations in RegulatoryScope.md (CRA/PLD dates, Commission guidance C(2026) 5252) — not code-checkable
- Grafana colour-blind validation claims in the dashboard
__comment— not checkable here - The doc-level statement that the ADO
.tlbfiles are "PE images despite the .tlb extension" — not re-checked
hMailServer 6.3.7, the latest release (17 October 2026, schema 6108) · AGPL-3.0-or-later · Repository · Mirror on GitHub · Report a documentation error (a GitLab issue naming the page)
Home — full index
Start here
1. Install and run
- Before You Install
- Installing hMailServer
- Installing on Linux
- Running in a Container
- Profiles and Environment
- The Control Panel
- The Control Deck
- Your First Domain and Mailbox
- Connecting a Mail Client
- The Webmail
- DNS for Your Domain
- Migration
2. Secure it
- Encryption and Certificates
- Database TLS
- Encryption for People
- Stopping Spam
- Stopping Viruses
- Security Hardening
- Secure Defaults
- The Service Account
- Secrets at Rest
- Sign-in and Identity
- Security Policy
3. Operate it
- Monitoring and Health
- The SNMP Agent
- Backup and Restore
- Message Storage
- Compliance and eDiscovery
- Troubleshooting
- Diagnosing Stalled Mail
- Relocating an Installation
- Upgrading hMailServer
- Upgrading Guide
- The Update Feed
- Migrating the Database Backend
- High Availability Runbook
- Warm Standby
- Runbooks Digest
4. Extend it
- Rules and Sieve
- Aliases Lists and Public Folders
- Rooms and Equipment
- Mailing Lists
- Routes and Relays
- Mail In to a Webhook
- Outbound Shaping
- The COM API and Scripting
- Event Scripts
- The REST API
- Command Line
- APIs Reference
5. Contribute to it
- Project Handbook
- Architecture
- Contributing
- Release Process
- Governance
- Assurance Case
- Regression Test Environment
- Fuzzing
- Regulatory Scope
- Third-Party Binaries
Look it up — from any journey
- Settings Reference
- Settings Overview
- Capabilities and Configuration
- Control Panel Reference
- Control Panel Pages
- Ports Reference
- Changes-Since-6210
- Version-History-to-6210
- Glossary
About this wiki
- hMailServer 6.3.7 is the latest release (17 October 2026, schema 6108)
- Pages say since 6.3.4 where a feature is new in it
- AGPL-3.0-or-later
- Repository, the one place the project is worked on
- Its mirror on GitHub, since 18 October 2026, with no Issues
- Questions: a GitLab issue with the Question template, GitHub Discussions Q&A, or the forum
- Report a documentation error: a GitLab issue naming the page