-
Notifications
You must be signed in to change notification settings - Fork 3
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.
Scope: every file in hmailserver/docs/. 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. Every runbook has a wiki page except
Migration.md, which is the newest and has none yet.
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 40 committed binaries, one by one | 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 |
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 upstream hMailServer | In place, per Upgrading.md; accounts in bulk through the Import Tool's comma-separated account import |
Everything, including the data directory | — |
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 0
turns the mirror into a move. 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_VERSIONis 6031 (source: hmailserver/source/Server/Common/Application/Constants.h:173) - Upgrade chain: scripts
Upgrade0to1100…throughUpgrade6030to6031…exist (source:ls hmailserver/source/DBScripts/); 6025→6030 shipped in 6.2.25, 6030→6031 in 6.2.27 - 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(); last registered step isnew UpgradeScript(6030, 6031)(source: hmailserver/source/Tools/DBUpdater/formMain.cs:159,242). 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 (#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: 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) - 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) - 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) -
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) - Create an empty target DB, edit
[Database], run& "C:\Program Files\hMailServer\Bin\DBSetupQuick.exe"and check$LASTEXITCODE(source: :161-197) -
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 upstream hMailServer in place; 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 0 makes it a move; 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)
- Upstream hMailServer: in place, per Upgrading.md; 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: 2127
[Test]attributes today ("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 in the tree) — 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) -
.github/SECURITY.mdexists;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 now analyses BOTH
csharpandc-cpp(source: .github/workflows/codeql.yml:88,229);scorecard.ymlexists - Authenticode signing of the installer is still outstanding (doc claim; Roadmap-tracked) — not verified here
- None found in the checkable half.
- 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) — Roadmap not checked for this row
- 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.3 · AGPL-3.0-or-later · Repository · Report a documentation error
Hmail Server — full index
Start here
1. Install and run
- Before You Install
- Installing hMailServer
- Installing on Linux
- Running in a Container
- The Control Panel
- Your First Domain and Mailbox
- Connecting a Mail Client
- DNS for Your Domain
2. Secure it
3. Operate it
- Monitoring and Health
- Backup and Restore
- Troubleshooting
- Diagnosing Stalled Mail
- Relocating an Installation
- Upgrading hMailServer
- Upgrading Guide
- Migrating the Database Backend
- High Availability Runbook
- Warm Standby
- Runbooks Digest
4. Extend it
- Rules and Sieve
- Aliases Lists and Public Folders
- Routes and Relays
- The COM API and Scripting
- The REST API
- 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