Skip to content

Runbooks Digest

Christopher Holloway edited this page Oct 22, 2026 · 23 revisions

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 Progressiverobot on 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.md and SUPPORT.md have 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.


0. Which runbook do I need?

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"]
Loading

The docs directory, and where each one is mirrored here

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

The documents added since

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.

Area Documents On the wiki
Settings and the configuration SettingsApply.md, SettingLevels.md, EnvironmentConfiguration.md, Profiles.md, ChangedFromStock.md, SetupChecklist.md, SampleDomain.md, Help.md Settings Overview, Settings Reference
Administration surfaces CommandLine.md, ControlPanelDesign.md, DeckParity.md, Roles.md, PackageManagers.md, RelocatingAnInstallation.md Command Line and Relocating an Installation are copies; The Control Panel, APIs Reference §5, Installing hMailServer
Operations and monitoring AlertsAndAuditTrail.md, Backup.md, Reports.md, Syslog.md, FollowConnection.md, Webhooks.md, ReputationWatch.md, DmarcReports.md, ResponseCompression.md Monitoring and Health, Backup and Restore
Anti-spam, anti-abuse and mail flow SenderListsAndDigests.md, Greylisting.md, SmtpPolicy.md, SpamTraps.md, CountryBlocking.md, Complaints.md, BounceSuppression.md, CompromisedAccounts.md, ImpersonationProtection.md, Sandbox.md, MailFlowRules.md, OutboundContentPolicy.md, DomainTransforms.md, Journaling.md, RestrictedDelivery.md, PasswordBreachCheck.md Stopping Spam, Stopping Viruses, Rules and Sieve, Security Hardening
Delivery and routing RemoteDomains.md, OutboundShaping.md, IpWarmup.md, SendApi.md, DkimKeyRotation.md Routes and Relays, DNS for Your Domain
Accounts, identity and sign-in PasskeysAndSingleSignOn.md, Scim.md, AccountRecovery.md, DevicesAndSessions.md, SecondAccount.md, MailboxMove.md, QuotaPolicy.md, OutOfOffice.md, PersonalData.md, Signatures.md Your First Domain and Mailbox, Security Hardening, The REST API §4
Lists and sharing DistributionLists.md, MailingLists.md, Directory.md, Contacts.md, SharedAddressBooks.md, Calendar.md Aliases Lists and Public Folders, Connecting a Mail Client
Encryption for people OpenPGP.md, ZeroAccess.md, SmimeAuthority.md, SealedMail.md, ConfidentialMessages.md Encryption and Certificates
The webmail BrandLogos.md, SendAs.md, MaskedAddresses.md, RecoverDeleted.md, MessageRecall.md, OfflineMail.md, ImportantMail.md, FormattedMessages.md, WebmailShortcuts.md Connecting a Mail Client; the wiki has no page for the webmail yet
Storage, encryption and secrets (6.3.4) MessageStore.md, ObjectStorage.md, EncryptionAtRest.md, SecretsAtRest.md, DatabaseTls.md Secrets at Rest and Database TLS are copies; Settings Overview, Settings Reference, Backup and Restore
Installation, service and updates (6.3.4) SecureDefaults.md, ServiceAccount.md, UpdateFeed.md, WindowsOnArm.md Secure Defaults, The Service Account and The Update Feed are copies; Upgrading hMailServer 18.4c, Installing hMailServer, Ports Reference 3.1
Compliance, integration and migration (6.3.4) Discovery.md, InboundWebhooks.md, RoomsAndEquipment.md, EvidencePack.md APIs Reference §4, Capabilities and Configuration, Assurance Case
Scripting, monitoring and performance (6.3.4) EventScripts.md, Snmp.md, Performance.md, PublishedNumbers.md Event Scripts and The SNMP Agent are copies; The COM API and Scripting 23.6, Ports Reference 4.6, Monitoring and Health
The project's own CI and test lab (6.3.4) ContinuousIntegration.md, TestLab.md Contributing, Regression Test Environment

The migration routes, and what each keeps

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.


1. The docs directory itself

  • hmailserver/docs/ holds 13 files + a Licenses/ 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.md and RELEASE.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: ls of each)
  • build/hmconfig.ps1 apply is 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) plus License 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)

2. Upgrading.md

What it covers

  • 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)

Procedure ("Doing it")

  1. Back up both the database (native dump: mysqldump, pg_dump, SQL Server backup) and the data directory (source: Upgrading.md:81-86)
  2. 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)
  3. Run the new installer over the existing install: stops service, installs, upgrades schema, restarts (source: Upgrading.md:93-94)
  4. Check the service is listening and the ERROR log for the upgrade window is empty (source: Upgrading.md:95-97)
  5. Send a message to yourself end-to-end and read it back over IMAP/POP3 (source: Upgrading.md:98-99)

Facts verified against code

  • REQUIRED_DB_VERSION was 6031 when this line was written (source: hmailserver/source/Server/Common/Application/Constants.h:173); it is 6040 in v6.3.3, was 6045 at 3233b0cc4 (Constants.h:208) and 6096 at master 540347500 (Constants.h:987), was 6103 at 92b8f9f63 (Constants.h:1205), and is 6104 in 6.3.4 (Constants.h:1224 at the tag)
  • Upgrade chain: scripts Upgrade0to1100… through Upgrade6030to6031… existed when this line was written (source: ls hmailserver/source/DBScripts/); 6025→6030 shipped in 6.2.25, 6030→6031 in 6.2.27. At 3233b0cc4 the chain runs to Upgrade6044to6045… in all four dialects: 6031→6038 shipped in 6.3.2, 6038→6040 in 6.3.3, and 6040→6045 came after it; at master 540347500 the chain ran to Upgrade6095to6096…, and in 6.3.4 it runs to Upgrade6103to6104…, 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 at Upgrade5001to5002 (source: ls hmailserver/source/DBScripts/ — first CE/PGSQL files are Upgrade5001to5002MSSQLCE.sql, Upgrade5001to5002PGSQL.sql)
  • DBUpdater registers steps in formMain.LoadScripts(); the last registered step was new UpgradeScript(6030, 6031) when this line was written (source: hmailserver/source/Tools/DBUpdater/formMain.cs:159,242) and is new UpgradeScript(6044, 6045) at 3233b0cc4 (formMain.cs:255) new UpgradeScript(6095, 6096) at 540347500 (formMain.cs:306) and new 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, which hmailserver --upgrade-database on Linux shares). After each step it runs a probe statement; since 6.2.26 the four foreign-key probes for 6030 read update hm_dbversion set value = value / (value - value) where not exists (select 1 from information_schema.table_constraints ...) because the earlier case when exists form crashed the SQL Server Compact provider and the crash oracle then stopped the service (GitHub issue 114); build/check-db-scripts.ps1 now 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.ps1 exists and reads REQUIRED_DB_VERSION from 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 gives select value from hm_dbversion for the operator; column is value) (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.DatabaseExists and launches DBUpdater.exe from its own directory (AppContext.BaseDirectory) with /SilentIfOk, plus /silent if it was given /silent; returns DBUpdater's exit code (source: hmailserver/source/Tools/DBSetupQuick/Program.cs:29-62)
  • Installer runs {app}\Bin\DBSetupQuick.exe when 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 is windowsdesktop-runtime-<channel>-win-x64.exe; installed with /install /quiet /norestart only 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_adsynchronization is 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)

Stale or wrong in the doc

  • "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.exe does not forward password: on the upgrade path and DBUpdater opens a modal dialog regardless of /silent (Upgrading.md:169-188). Code now forwards password:<value> on the upgrade path (source: Tools/DBSetupQuick/Program.cs:49-55), the installer passes password: + admin password to DBSetupQuick (source: hMailServerInnoExtension.iss:936-944, comment: "until 6.2.23 DBSetupQuick forwarded only /silent"), and Authenticator.AuthenticateUser tries empty → the password argument → each raw argument → then under /silent returns 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.

3. DiagnosingStalledMail.md

What it covers

  • 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)

Procedure

  1. Decide which half: Postfix timed out while sending end of data = accepting; message visible in queue and never leaves = delivering (source: :20-26)
  2. Control Panel → Logging → tick "Debug messages"; reproduce once; turn it off afterwards (source: :33-38). Control Panel label confirmed: "Debug messages" bound to Logging.LogDebug (source: hmailserver/source/Tools/ControlPanel/Views/ServerSettingsView.xaml.cs:2045)
  3. Read the accept-pipeline stage lines in sequence; the stuck stage is the one after the last line written (source: :60-69)
  4. If every message stalls, look for the work-queue saturation lines (source: :111-131)
  5. For delivery: check scanner, slow remote, external tool, database; check delivery queue in Control Panel and the SMTP log (source: :133-156)
  6. Report: log from 354 onward with stage lines, ERROR_hmailserver_<date>.log, scanners/scripts enabled, whether the message arrives (source: :190-201)

Log lines (all verified verbatim)

  • SMTPConnection - accept: start spam-protection. (source: hmailserver/source/Server/SMTP/SMTPConnection.cpp:1732)
  • SMTPConnection - accept: done <stage> in <n> ms (session <id>). for stages spam-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-modifications line — the middle stage only has a done (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 <= 0 disables 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 two DatabaseUnavailableMarker::Scope blocks (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 table — every default verified in IniFileSettings::LoadSettings

  • [Settings] FinalizationTimeout default 240 (source: hmailserver/source/Server/Common/Application/IniFileSettings.cpp:281); consumed at SMTPConnection.cpp:2012
  • SAMaxTimeout default 90; SAMinTimeout default 30 (source: IniFileSettings.cpp:274-275)
  • ClamMaxTimeout default 90; ClamMinTimeout default 15 (source: IniFileSettings.cpp:282-283)
  • DNSQueryTimeout default 10 (source: IniFileSettings.cpp:293); consumed at Common/TCPIP/DNSResolverWinApi.cpp:120
  • ScriptTimeout default 60 (source: IniFileSettings.cpp:307); consumed in Common/Scripting/ScriptServer.cpp
  • ExternalProcessTimeout default 300 (source: IniFileSettings.cpp:308); consumed in Common/Util/ProcessLauncher.cpp
  • ClientSessionCeiling default 1800 (source: IniFileSettings.cpp:299); consumed at SMTP/SMTPClientConnection.cpp:65 via SetSessionCeiling
  • AsyncQueueStallThreshold default 120 (source: IniFileSettings.cpp:314); consumed at WorkQueue.cpp:451
  • DBConnectionAcquireTimeout default 60 (source: IniFileSettings.cpp:306); consumed at Common/SQL/DatabaseConnectionManager.cpp:436
  • DNSServer default "" (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 in Common/Application/, not Common/Util/)

DNSServer warning (verified)

  • DNSResolverWinApi.cpp sets serverAddress.sin_port = 0 with 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)

Stale or wrong in the doc

  • Nothing contradicted. Minor: Logger.cpp is under Common/Application/, TimeoutCalculator under Common/Application/ (neither is in Common/Util/).

4. HighAvailabilityRunbook.md

What it covers

  • 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)

Prerequisites

  • External database (MSSQL/MySQL/PostgreSQL), not SQL CE (source: :50-51)
  • Both nodes: identical [Database] INI settings and the same [Directories] DataFolder on shared storage; there is no setting called DataDirectory (source: :52,59-64). Confirmed: key is ("Directories","DataFolder") and the accessor is data_directory_ (source: IniFileSettings.cpp:129)
  • Other [Directories] keys that exist: ProgramFolder, TempFolder, EventFolder, DatabaseFolder, LogFolder (source: IniFileSettings.cpp:125-164,886)

Settings named (all in [Settings], defaults verified)

  • MetricsServerPort default 0 (off) (source: IniFileSettings.cpp:426)
  • MetricsServerBindAddress default 127.0.0.1 (source: IniFileSettings.cpp:427)
  • MetricsServerAuthToken, MetricsServerAuthUsername, MetricsServerAuthPassword, MetricsServerCertificateFile, MetricsServerPrivateKeyFile all default "" (source: IniFileSettings.cpp:433-437)
  • ShutdownDrainSeconds default 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)

Probes (verified)

  • /livez → 200 alive whenever the listener is up; /readyz → 200 ready or 503 not ready: <reason>; /healthz → JSON, 200/503; all three are answered BEFORE any refusal branch (source: hmailserver/source/Server/Common/Util/MetricsServer.cpp:925-950)
  • /readyz is 200 only when running AND the database answered a probe within DatabaseProbeStalenessMilliseconds = 20000 (source: MetricsServer.cpp:108,2345)
  • /metrics answers 503 (not 401) on a non-loopback bind with no credential; the log line names MetricsServerAuthToken, 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/8 and 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: StopServers sets StateStopping first, then waits up to ShutdownDrainSeconds, 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)

Procedures

  • Planned failover: (1) stop service on A — /readyz 503 then drain; (2) verify /readyz on A refused; (3) move VIP; (4) start B, wait for /readyz 200; (5) send test message, check /healthz shows database: 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)

Stale or wrong in the doc

  • IPv4-only bind address: doc says MetricsServerBindAddress "takes an IPv4 literal and nothing else" and IPv6 is rejected (HighAvailabilityRunbook.md:112-117). Code: ParseBindAddress accepts IPv4 or IPv6 literals (family chosen by presence of a colon; scoped fe80::1%3 rejected) and IsLoopbackAddress_ accepts ::1 (source: MetricsServer.cpp:195-231,632-660). Host names are still rejected.
  • /healthz body: doc says it includes sessions per protocol (HighAvailabilityRunbook.md:130). Code: body is exactly {"status","state","database","uptime_seconds"}; session counts were deliberately removed from /healthz because it is unauthenticated (source: MetricsServer.cpp:2395-2412)
  • Doc's verification section (:220-235) predates both changes.

5. WarmStandby.md

What it covers

  • 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)

Constraints (verified)

  • PersistentMessage::UnlockAll() runs update hm_messages set messagelocked = 0 where messagetype = 1 and messagelocked = 1 and is called from SMTPDeliveryManager::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 asc in LoadPendingMessageList_ (source: SMTPDeliveryManager.cpp:115-127)
  • PersistentFetchAccount::UnlockAll() called from ExternalFetchManager at 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: IniSettingStore mirrors the [Settings] section into hm_inisettings with 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 - inisettingfilevalue is 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 at 3233b0cc4) 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), PasswordPepper stays in each machine's file only, and [SettingsOverride] forces keys per machine (HM5807) (source: IniSettingStore.cpp Synchronize, IsFileOnlyName; IniFileSettings.cpp LoadDatabaseSettings)
  • Certificate/DKIM key columns hold PATHS: hm_sslcertificates.sslcertificatefile, sslprivatekeyfile (CreateTablesMSSQL.sql:696-697); hm_domains.domaindkimprivatekeyfile (CreateTablesMSSQL.sql:251)
  • DatabaseStatementTimeout default 30 (source: IniFileSettings.cpp:645)
  • /readyz 200 = running and database answered within 20 s (source: MetricsServer.cpp:108,2345)
  • Schema pin refuses mismatch in both directions (source: Application.cpp:257-265)

Procedures

  • 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 /MIR every 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 /readyz 200; 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)

Warnings

  • 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] Password is DPAPI-per-machine (source: :109-113). Confirmed: INI password written via protectedValue (source: IniFileSettings.cpp:1110)
  • SQL Server Compact is excluded from everything (source: :171-174)

Stale or wrong in the doc

  • None found. The SMB/CSV section is declared "operational judgement" not code fact (source: :263-267).

6. MigratingDatabaseBackend.md

What it covers

  • 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)

Why it works (verified)

  • Neither BackupExecuter nor BackupRestorer branches on database type (source: grep DatabaseType|MSSQL|MySQL|PostgreSQL over both files = no hits)
  • Account::XMLStore writes no id; PersistentAccount treats GetID() == 0 as 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.cpp GetFileName(...FileLocation); IMAP/IMAPConfiguration.cpp:207-210 returns "#Public")
  • Backup refuses with All messages are not located in the data folder. if any row points outside DataFolder (source: Common/Application/BackupExecuter.cpp:369)
  • Archive name HMBackup <time>.7z; index hMailServerBackup.xml (source: BackupExecuter.cpp:432,443; BackupRestorer.cpp:24; BackupRetention.cpp:23 documents HMBackup 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)

Settings and commands (verified)

  • [Database] Type accepts MSSQL, MYSQL, PostgreSQL, MSSQLCE case-insensitively; Internal (0/1), Server, Database, Username, Password, Port (0 = default) (source: IniFileSettings.cpp:99-123)
  • [Settings] BackupMessagesDBOnly default 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/BackupMessagesDBOnly or hmctl 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 - under NT SERVICE\hMailServer the 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 marked now
  • 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.exe creates the schema when Database.DatabaseExists is 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 name hMailServer (source: build/preflight-tests.ps1 uses hMailServer; hMailServerInnoExtension.iss:709 IsServiceRunning('hMailServer'))
  • SQL log device writes to table hm_log, created at runtime by SqlLogDevice (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), PostgreSQLSslMode and PostgreSQLSslRootCert (""; 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=1 for 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).

Procedure (doc's steps)

  1. Stop-Service hMailServer; restart for the backup itself; hold inbound traffic (source: :104-117)
  2. Backup with domains + settings + messages, via Control Panel or the COM script (source: :119-143); optionally BackupMessagesDBOnly=1 before both backup and restore, remove afterwards (source: :145-159); since 6.3.4, stored rather than edited into the file, and removed with DELETE /api/v1/settings/ini/BackupMessagesDBOnly
  3. 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 runs DBSetup.exe instead - the wizard's Create a new hMailServer database, or DBSetup.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, Not DBSetupQuick.exe)
  4. Start-Service hMailServer, LoadBackup(...), set RestoreDomains/RestoreMessages/RestoreSettings, StartRestore() (source: :199-215)
  5. 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)

Stale or wrong in the doc

  • 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 writes ERROR_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 use SQLLogging" (:267-268). No setting by that name exists; the SQL log device is selected by the database-held Logging.LogDevice property (PROPERTY_LOGDEVICE, COM hLogDeviceSQL) (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).

6a. Migration.md (6.2.27)

What it covers

  • 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)

Routes (verified)

  • Another IMAP server: an external account with Mirror every folder on — FetchAccount.ServerType 1 (IMAP) plus FetchAccount.MirrorFolders (schema 6031, hm_fetchaccounts.famirrorfolders); LIST "" "*", then per selectable mailbox SELECT, 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 and OnExternalAccountDownload does 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/tmp and 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 imap from 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)

Stale or wrong in the doc

  • None found; the doc is newer than every other runbook.

7. RegressionEnvironment.md

What it covers

  • Building a machine on which build/run-tests.ps1 passes; build/preflight-tests.ps1 is 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)

Pieces and steps (verified)

  • Suite size: 2,302 [Test] attributes at v6.3.3, 3,390 at master 540347500 (24 September 2026) 3,698 at 92b8f9f63 and 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 -l at f088336c2; v6.2.28 body)
  • Step 1: build/build.ps1 -Configuration Release, then build/post-build.ps1 -Configuration Release (self-elevates via Start-Process powershell -Verb runAs); service must point at hmailserver\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 InstallLocation in both views must FAIL (source: preflight-tests.ps1:54-59)
  • Provisioning list is in IMPLEMENTATION-NOTES.md under "Test-environment recipe (dev tree, no installer)"; sc sdset grant at line 334 (source: IMPLEMENTATION-NOTES.md:288,334)
  • build/make-hmailserver-writable.ps1 exists (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); COM Database.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.ps1 and build/check-db-scripts.ps1 exist; RELEASE.md step 6 is build\check-db-scripts.ps1 (source: RELEASE.md:32)
  • Step 3: data folder C:\HMTest\Data is hard-coded in the preflight (source: preflight-tests.ps1:261)
  • Step 4: preflight expects Domains.Count -eq 1 and Settings.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; CustomAsserts starts C:\clamav\clamd.exe if no clamd process (source: preflight-tests.ps1:128-129; test/RegressionTests/Infrastructure/CustomAsserts.cs:179-184 — note the file is under Infrastructure/, not Shared/)
  • Step 6: service name SpamAssassinJAM checked by preflight; used by AntiSpam/SpamAssassin.cs and Infrastructure/CustomAsserts.cs (source: preflight-tests.ps1:133-134; grep)
  • Step 8: build/build-tests.ps1 restores via MSBuild /t:Restore /p:RestorePackagesConfig=true into hmailserver\test\packages and 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; assembly hmailserver\test\RegressionTests\bin\x64\Debug\RegressionTests.dll; -Where maps to --where; -StopOnError maps 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 -Clean removes a stale ERROR log, leftover test-only INI keys, and empty orphan directories; the INI it inspects is hmailserver\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)

Traps listed (doc claims; environmental, not code-checkable)

  • 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)

Stale or wrong in the doc

  • Doc says "the suite's CustomAsserts hard-codes C:\clamav\clamd.exe" — true, but the file is test/RegressionTests/Infrastructure/CustomAsserts.cs (no Shared/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.cpp returns NoVirusFound at :412 when nothing found; error paths not traced).

8. Fuzzing.md

What it covers

  • 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 contains Find-ClangCl.ps1, README.md, artifacts/, bin/, build/, build-fuzz.ps1, corpus/, dict/, findings/, harness/, make-corpus.ps1, regression/, run-fuzz.ps1 (source: ls fuzz/)

Why it waited (verified)

  • 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)

Build (verified)

  • Prerequisite: clang-cl with libFuzzer + ASan runtimes; portable LLVM to %LOCALAPPDATA%\Programs\LLVM-22.1.8 (doc example, external) or VS component Microsoft.VisualStudio.Component.VC.Llvm.Clang (source: Fuzzing.md:88-121)
  • .\fuzz\build-fuzz.ps1 [-Target <name>] [-BoostInclude <dir>] [-RuntimeLibrary MT|MD, default MT] (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 lowercase assert sites were renamed to it on 5 September 2026) maps onto ASSERT in the shim, so -Asserts governs both; the suite counterpart is build\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)

Run (verified)

  • .\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 5 smoke; -Replay; -Minutes 480 -Jobs 4 overnight; -merge=1 corpus 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 = 8 in the harness (source: fuzz/harness/fuzz_mime_common.h:151)
  • mime_header_fuzzer reproduces Utilities::GetMimeHeader's call as shape 3 (source: fuzz/harness/mime_header_fuzzer.cpp:38,57)
  • BEncode/QEncode guard if (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}; no mime_header_fuzzer reproducers 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)

Warnings

  • Exclude fuzz\ from AV real-time scanning; do not run on the mail server; a stack-overflow in hm_fuzz::ExerciseBody is a harness limit, not a finding (source: Fuzzing.md:249-259,290-295)

Stale or wrong in the doc

  • 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/ or make-corpus.ps1 by name (only "regenerates the seed corpus").

9. RegulatoryScope.md

What it covers

  • 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)

"What we do anyway" — each item checked

  • SBOM workflow .github/workflows/sbom.yml exists, runs on release and manual release_tag dispatch; build/merge-native-dependencies-into-sbom.ps1 exists (source: ls; sbom.yml:27-33)
  • Binary inventory: 40 entries in third-party-binaries.json; verify-binary-provenance.yml runs on push and pull_request, warns (not fails) on remove-* dispositions (source: json artifact count; verify-binary-provenance.yml:35-38,89-90,152-153)
  • Signing: sign-release.yml uses sigstore/cosign-installer (pinned to v3.10.1 SHA), sign-blob/verify-blob bundles (source: sign-release.yml:32-33,113-114)
  • SECURITY.md exists, at the repository root since 24 September 2026 (it was .github/SECURITY.md before), and since 6.3.4 the served file's Policy: line names it on GitLab; security.txt served at /.well-known/security.txt by WebServicesServer — "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 csharp and c-cpp while it ran on GitHub (source: .github/workflows/codeql.yml:88,229); scorecard.yml exists
  • "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.md still says outstanding at master 540347500 (RegulatoryScope.md:172)

Stale or wrong in the doc

  • None found in the checkable half.

10. ThirdPartyBinaries.md and third-party-binaries.json

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-tree finds no .dll, .exe, .msi or .tlb - and third-party-binaries.json lists 35 artifacts, marked fetched (22), gathered (10), system (2) or generated (1), beside 3 build inputs that build/get-installer-binaries.ps1 downloads 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 the Binary provenance check 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.

What they cover

  • Inventory and policy for the 40 committed binaries; JSON is the only place hashes live; CI (Binary provenance workflow) fails on hash change, missing file, or unlisted binary (source: ThirdPartyBinaries.md:4-19,152-171)

Inventory facts (verified against the JSON and the tree)

  • 40 artifacts; dispositions: retain 13, retain-review 13, remove-duplicate 10, remove-unused 3, retain-generated 1 (source: python count over third-party-binaries.json)
  • Authenticode: 12 Valid (10 signed CN=Microsoft Windows Software Compatibility Publisher = MSVC v145 CRT 14.51.36231.0; 2 CN=Microsoft Corporation, OU=MOPR = the SQL CE MSIs), 28 NotSigned (source: json authenticode/authenticode_signer counts)
  • 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 10 remove-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; policy field points at hmailserver/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.cpp builds <exe dir>\libmysql.dll and loads plugins via MYSQL_PLUGIN_DIR (source: Common/SQL/MySQLInterface.cpp:75,113)
  • ADO type libraries #imported at hmailserver/source/Server/hMailServer/stdafx.h:48 (x64) and :52 (x32) (doc says 47/51)
  • installation/section_files_common.iss:11-12 says atl70.dll "looks vestigial on a v145 build" and installs it to {sys}; :33,37 ship Extras\libmysql.dll and Extras\plugin\*.dll (source: section_files_common.iss)
  • hMailServerInnoExtension.iss: CheckPorts external from ISC.DLL at :107-108, called at :709; the eight isxdl_* externals at :111-133; x86 branch comment at :852 (doc says 94/695, 97–119, 839 — line-number drift only)
  • RMSPF.cpp calls LoadLibrary(_T("DNSAPI.DLL")) at hmailserver/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.ps1 and build-pgsql.ps1 (with build-common.ps1) replaced build-dependencies.ps1 in 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.dll regenerated by the 6.2.25 release build (sha256 f74c3e95…, 195,072 bytes) (source: third-party-binaries.json:412-414; commit f7418b81d)
  • hmailserver/source/Server/hMailServer/post-build.bat and hmailserver/source/Tools/Interop/README.md ("Regenerating" section, tlbimp) exist (source: ls; Interop/README.md:3-11)
  • No scorecard.yml / .github/scorecard.yml annotation 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/)

Procedure: updating the manifest

  1. 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)

Stale or wrong in the doc

  • Only line-number drift (listed above). Substantive claims hold.

11. grafana-dashboard.json

Structure (verified)

  • Title hMailServer, uid hmailserver-overview, schemaVersion 39, version 2, refresh 1m, default range now-24h; templating: datasource (prometheus) and instance from label_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)

Every metric the dashboard references exists in MetricsServer.cpp

  • 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_up is NOT emitted (only mentioned in a comment at :1667) — the Database panel description's "_connected rather than _up" remains accurate

Metrics emitted but NOT on the dashboard (gaps a page writer may want to list)

  • 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)

12. Cross-document contradictions and doc-vs-code drift (summary)

  • 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 /silent fails 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 ::1 loopback (MetricsServer.cpp:195-231,632-660); /healthz has no sessions field (MetricsServer.cpp:2405-2412)
  • MigratingDatabaseBackend.md vs code: public folder dir is #Public not .Public_Folder (IMAPConfiguration.cpp:209); error log is ERROR_hmailserver_<date>.log not hmailserver_ERROR_<date>.log (Logger.cpp:477); Backup is under Utilities not Settings > Advanced in hMailCP (NavigationMap.cs:279); no setting named SQLLogging — it is Logging.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)

12a. Since 6.2.24 (shipped in 6.2.25; all confirmed in the tree)

  • 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 to logs/analyze-Release.log, one line per distinct finding to logs/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, so build/build.ps1 -Configuration Release is 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 CASCADE constraints (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 gets ENGINE=InnoDB on 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/ReferentialIntegrity proves refusal and cascade against the SQL CE file (source: DBScripts/Upgrade6029to6030MSSQL.sql, 17 FOREIGN KEY lines; 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) or ACLManager::CheckDelegatedRight (a right one account grants another - l under #Users, p for Send-As; enforcement off = nothing granted). GetPermissionForFolder only reports rights (MYRIGHTS, SELECT). build/check-authz-choke-point.py in 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.ApplySavedTheme applies ApplicationTheme.HighContrast (Wpf.Ui's SystemColors dictionary) whenever SystemParameters.HighContrast is set, follows the switch at runtime (SystemParameters.StaticPropertyChanged) and ignores the light/dark toggle while it is on; ThemeTokens.IsHighContrast is the one answer and honours HMAILCP_HIGH_CONTRAST=1 so build/capture-cp.ps1 -Launch -HighContrast can capture the result on an ordinary desktop (source: Tools/ControlPanel/MainWindow.xaml.cs:60,873; build/capture-cp.ps1:11-13).

13. Unconfirmed (not found in code by grep; record as claims, not facts)

  • "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 master 540347500 (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 .tlb files are "PE images despite the .tlb extension" — not re-checked

Home — full index

Start here

1. Install and run

2. Secure it

3. Operate it

4. Extend it

5. Contribute to it

Look it up — from any journey


About this wiki

Clone this wiki locally