Skip to content

Settings Reference

chrisholloway5 edited this page Sep 5, 2026 · 40 revisions

Settings Reference

Every key of hMailServer.INI as the server reads it today: section, default, what it does, which code consumes it and whether it is read at startup or live. Generated from IniFileSettings.cpp and the other readers of the file, with the source line for each fact. 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.

Every fact below was checked against the code on disk today. Paths are relative to hmailserver/source/Server/ unless stated. IFS.cpp = Common/Application/IniFileSettings.cpp, IFS.h = Common/Application/IniFileSettings.h, ISS.cpp = Common/Application/IniSettingStore.cpp. Line numbers are from the working tree at commit 4d5da3f.

settings.md at the repo root (dated 2026-06-15, server 6.2.5) was used only as a hint; where it disagrees with the code the code is recorded and the disagreement is listed under "Contradictions".

Timing legend used in the tables:

  • S — latched into a member by IniFileSettings::LoadSettings(); the consumer calls the getter at the moment of use, so the value changes whenever LoadSettings re-runs (see "When the file is read").
  • S! — consumed only when a listener/task is constructed at server (re)start, so a LoadSettings re-run on its own does not apply it; a service restart or Application.Reinitialize does.
  • L — read live from the file by its consumer, not through LoadSettings (own cache rules noted).

1. Where the file is and how it is found

  • File name is literally hMailServer.ini, in the server's Bin directory (source: IFS.cpp:863-878 GetInitializationFile).
  • The Bin directory is HKLM\SOFTWARE\hMailServer\InstallLocation + \Bin if that registry value exists; otherwise the directory of the running hMailServer.exe (source: Common/Util/Utilities.cpp:103-122 GetBinDirectory).
  • The path is computed once and cached in a static (source: IFS.cpp:865-877).
  • The installer writes it to {app}\Bin\hMailServer.INI (source: hmailserver/installation/section_ini.iss:1-8); on upgrade it moves a pre-existing {win}\hMailServer.ini into {app}\Bin (source: installation/hMailServerInnoExtension.iss:1043-1055).
  • COM Application.<ini path> exposes the path: InterfaceApplication.cpp:436 returns GetInitializationFile() (property name unconfirmed here — see the COM facts file).
  • A second, separate ini file lives beside it: hMailServerApiKeys.ini (REST API keys; section 12).
  • Test harness candidate locations: hmailserver/test/RegressionTests/Shared/IniFileSetting.cs:34 CandidateDirectories (not read in detail).

2. Read mechanics (apply to every key)

  • Values are read with GetPrivateProfileString / GetPrivateProfileInt (source: IFS.cpp:831, 858). Consequences that the code itself documents: an absent key yields the caller's default, but Key= (present, empty) reads as 0 through GetPrivateProfileInt (source: IFS.h:81-85; ISS.cpp:463-467); a value such as 5x reads as 5 and x as 0 (source: IFS.cpp:847-853).
  • Windows treats section and key names case-insensitively for the file API; the hm_inisettings mirror matches them case-sensitively (source: ISS.cpp:206-222).
  • String buffer is 4096 chars in ReadIniSettingString_ (source: IFS.cpp:829-831) — chosen because DPAPI-protected secrets are long base64 envelopes. Exception: [Directories] LogFolder is read with a 255-char buffer (IFS.cpp:885-886) and [Settings] UseLanguage with 255 (IFS.cpp:937-939).
  • Booleans are ReadIniSettingInteger_(...) == 1, so only the literal 1 is true; 2, yes, true are false (source: every == 1 in IFS.cpp:103-674).
  • Writes go through WritePrivateProfileString (source: IFS.cpp:692-702) and, for the [Settings] section via the store, are flushed with the null-null-null call (source: ISS.cpp:178-184).

3. When the file is read (startup vs. live)

  • LoadSettings() reads every key in sections 5-9 below into typed members in one pass (source: IFS.cpp:90-679). It runs:
    1. First thing in Application::InitInstance, file-only (source: Common/Application/Application.cpp:132).
    2. Again in InitInstance after the database is open and its schema accepted, this time with the hm_inisettings overlay (source: Application.cpp:177-178, comment 163-176).
    3. On Application::Reinitialize (COM Application.Reinitialize, InterfaceApplication.cpp:93-100), which calls ForgetDatabaseSettings() then InitInstance again (source: Application.cpp:924-942).
    4. On every COM Settings.Directories get (source: COM/InterfaceSettings.cpp:1129) and on Database.CreateInternalDatabase (source: COM/InterfaceDatabase.cpp:488) — these re-latch all ~200 members from the file (+overlay) on a COM thread.
    5. On hMailServer.exe /Register (no database) so ServiceAccountName/Password reach the SCM (source: hMailServer/hMailServer.cpp:216-249).
  • The house contract for [Settings] changes made through COM/Control Panel: persist now, apply on the next start (source: IFS.h:59-65). Nothing in SetIniSetting calls LoadSettings.
  • Keys that are NOT latched but read live: UseLanguage (every call, IFS.cpp:934-942), LogFolder (lazily once, then cached until SetLogDirectory, IFS.cpp:880-891), CalDavRedirectUrl/CardDavRedirectUrl (60-second cache, Common/Util/WebServicesServer.cpp:61,1255-1263), the whole [SendingLimits]/[SendingLimitsOverrides] and [LDAP] sections (file-timestamp check at most every 2 s; section 10, 11), and hMailServerApiKeys.ini (every request; section 12).
  • LoadSettings ends by calling DiskSpace::InvalidateCache() because DataFolder may have changed (source: IFS.cpp:676-678).
  • Constructor defaults differ from LoadSettings defaults for a few members and only matter for an error reported before LoadSettings has run: preferred_hash_algorithm_ 3 vs 4, log_level_ 0 vs 9, no_of_dbconnections_ 0 vs 5, dnsbl_checks_after_mail_from_ false vs 1, quick_retries_Minutes 0 vs 6, add_xauth_user_ip_ false vs 1 (source: IFS.cpp:23-82 vs 144-398). WindowsEventLogEnabled/Level are deliberately identical in both (IFS.cpp:74-78).

4. The hm_inisettings mirror (schema 6011+) and the COM surface

  • LoadDatabaseSettings() reconciles [Settings] with table hm_inisettings (columns inisettingname ≤100 chars, inisettingvalue ≤4000, inisettingfilevalue) via IniSettingStore::Synchronize (source: IFS.cpp:704-724; ISS.cpp:113-136, 195-350). Table added in schema 6011 (hmailserver/source/DBScripts/Upgrade6010to6011*.sql; Tools/DBUpdater/SchemaVerification.cs:138-142). Current REQUIRED_DB_VERSION is 6025 (Common/Application/Constants.h:168).
  • Only the [Settings] section is overlaid; [Database], [Directories], [Security], [GUILanguages] always come from the file (source: IFS.cpp:803-820, 836-856; IFS.h:760-766).
  • Merge rules (source: ISS.cpp:227-316): file changed → file wins and row updated (both changed → HM5804 conflict report); row changed but file unchanged → value written INTO the file (HM5803 if the write fails); key deleted from file with row unchanged → row deleted; row changed and key absent from file → written back to file. If the table cannot be read the server runs on the file alone and reports HM5802.
  • Keys >100 chars or values >4000 chars are skipped with HM5800/HM5801 (source: ISS.cpp:86-103). Comment lines (;/#) are ignored (ISS.cpp:64-68).
  • Backups include <IniSettings><Setting Name= Value=/> from the table (ISS.cpp:481-505); restore writes both row and file and is authoritative (ISS.cpp:509-580, HM5805 on file-write failure).
  • COM: Settings.GetIniSetting(Name), Settings.SetIniSetting(Name, Value), Settings.DeleteIniSetting(Name), Settings.IniSettingNames (CRLF-joined) (source: hMailServer/hMailServer.idl:735-741; COM/InterfaceSettings.cpp:2660-2760). Name must pass IsStorableName (non-empty, ≤100, no = [ ] or line break, no surrounding whitespace); value must pass IsStorableValue (≤4000, no line break) (ISS.cpp:392-426). SetIniSetting writes the FILE first and fails without changing anything if that fails (ISS.cpp:428-460, HM5806 if the mirror cannot be read afterwards). DeleteIniSetting removes the line (not Key=) and the row (ISS.cpp:456-479).
  • The Control Panel reads the file directly when it is on the same machine and falls back to GetIniSetting over COM otherwise (source: Tools/ControlPanel/Services/IniFeatureStore.cs:120-140; Services/ServerSession.cs:88-101).

5. [Database] section

Key Type Default What it does Consumer Timing
Type string "" One of MSSQL, MYSQL, PostgreSQL, MSSQLCE (case-insensitive); anything else = TypeUnknown and InitInstance returns false ("database settings do not exist") (IFS.cpp:107-121, 681-688; Application.cpp:134-137) DatabaseConnectionManager, SQLStatement, SqlLogDevice, PersistentAccount/Domain/FetchAccount, SMTPDeliveryManager S! (connect)
Server string "" DB host. Required for every type except MSSQLCE (CheckSettings, IFS.cpp:908-928, HM5005 at Application.cpp:197-203) DatabaseConnectionManager.cpp:96 S!
Database string "" DB name / SQL CE file name. Required for MSSQLCE (IFS.cpp:912-916) DatabaseConnectionManager.cpp:99 S!
Username string "" DB user (IFS.cpp:101) DatabaseConnectionManager.cpp:97; MySQLConnection.cpp:366-369 (internal-DB heuristic) S!
Password string "" DB password, decrypted with Passwordencryption scheme at load (IFS.cpp:102, 109-112) DatabaseConnectionManager.cpp:98 S!
Passwordencryption int 0 Crypt::EncryptionType for Password: 0 none, 1 Blowfish, 6 DPAPI (machine-scoped). Crypt::DeCrypt handles only 0/1/6; any other value hits assert(0)/returns "" (Common/Util/Crypt.cpp:153-185; enum Common/Util/Crypt.h:18-27). Written by the server as PasswordEncryption (different case; harmless) = 6 when ProtectStoredSecretsWithDPAPI=1 and DPAPI succeeds, else 1 (IFS.cpp:1093-1118) IFS.cpp:109-112 S!
Port int 0 DB TCP port (IFS.cpp:123); 3307 + user root/hmailserver + Internal=1 marks the legacy internal MySQL (MySQLConnection.cpp:364-373) DatabaseConnectionManager.cpp:102; MySQLConnection S!
Internal int (0/1) 0 Marks the legacy bundled MySQL; drives the post-install Internal MySQL\HMS4.3-MySQL4.1.18.sql script (IFS.cpp:103; MySQLConnection.cpp:364-380) MySQLConnection S!
ServerFailoverPartner string "" ADO "Failover Partner" for MSSQL mirroring; when set and Provider empty the provider becomes SQLNCLI (IFS.cpp:104; Common/SQL/ADOConnection.cpp:86-110) ADOConnection S!
Provider string "" OLE DB provider name for MSSQL. Empty → MSOLEDBSQL if v18+ installed, else sqloledb; SQLNCLI if a failover partner is set (ADOConnection.cpp:90-110) ADOConnection S!
NumberOfConnections int 5 Pool size; forced to 1 for MSSQLCE (IFS.cpp:144, 148-154) DatabaseConnectionManager.cpp:91 S!
ConnectionAttempts int 6 Connect retries at startup (IFS.cpp:145) DatabaseConnectionManager.cpp:54-59 S!
ConnectionAttemptsDelay int seconds 5 Delay between attempts (multiplied by 1000 ms) (IFS.cpp:146; DatabaseConnectionManager.cpp:55) DatabaseConnectionManager S!
AllowUnencryptedConnection int (0/1) 0 none MySQL/MariaDB only. The bundled MariaDB Connector/C requires TLS from the server and refuses one that has none ("SSL is required, but the server does not support it"); 1 sets MYSQL_OPT_SSL_ENFORCE off so it prefers TLS and falls back to plaintext. 0 keeps TLS required, and the connect error names this key. Certificate verification is the client's default either way (IFS.cpp LoadSettings; MySQLConnection.cpp Connect). Added 5 September 2026 MySQLConnection
PostgreSQLSslMode string "" PostgreSQL only. libpq's sslmode, put into the conninfo: disable, allow, prefer, require, verify-ca or verify-full. Empty leaves libpq's default (prefer: encrypted when the server offers it, verified never). Any other value refuses the connection with HM5563 rather than falling back (Common/SQL/PGConnection.cpp). Added 5 September 2026 PGConnection::Connect S!
PostgreSQLSslRootCert string "" PostgreSQL only. libpq's sslrootcert: the CA file verify-ca/verify-full check the server certificate against. A Windows path; the backslashes are escaped for conninfo by the server. Added 5 September 2026 PGConnection::Connect S!
ConnectionStringOptions string "" MS SQL Server only. Appended verbatim to the OLE DB connection string (a ; is added if missing) - the provider's own keywords, e.g. Encrypt=yes;TrustServerCertificate=no for MSOLEDBSQL. Added 5 September 2026 ADOConnection::Connect S!

Setters that write this section: SetDatabaseServer/Name/Username/Password/Type/Port/IsInternalDatabase (IFS.cpp:1060-1173), reached from COM Database.* (COM/InterfaceDatabase.cpp).

6. [Directories] section

Key Type Default Normalisation Used for Consumer(s) Timing
ProgramFolder path "" trailing \ appended (IFS.cpp:125-127) Bin dir (ProgramFolder\Bin, IFS.cpp:1187-1191), ProgramFolder\DBScripts (IFS.cpp:139-142), ProgramFolder\Languages (IFS.cpp:893-897), ProgramFolder\WebAdmin\index.html (RestApiServer.cpp:2611-2614) Compression.cpp, DatabaseSettings.cpp, MySQLConnection.cpp, Language(s).cpp, RestApiServer.cpp S
DataFolder path "" trailing \ stripped (IFS.cpp:129-131) Message store root; ACME dir default DataFolder\ACME (AcmeClient.cpp:250-255); sending-limits state file DataFolder\hmailserver_sendinglimits.dat (RateLimiter.cpp:34,268-270); quarantine, sieve storage, backups, disk-space checks 16 files (PersistentMessage, PersistentAccount/Domain, QuarantineStore, SieveStorage, BackupExecuter, BackupScheduleTask, DiskSpace, RateLimiter, ServerStatus, MailImporter, AcmeClient, NameChanger, Message, TestDataDirectory, VirusScannerTester) S
TempFolder path "" trailing \ stripped (IFS.cpp:133-135) Temp files for scanners/compression/process launcher ClamWinVirusScanner, VirusScanner, Compression, FileUtilities, ProcessLauncher, Utilities S
EventFolder path "" none (IFS.cpp:137) `EventFolder\EventHandlers.<vbs js>` script (ScriptServer.cpp:166-170, 291-295) ScriptServer.cpp; COM InterfaceScripting
DatabaseFolder path "" trailing \ stripped (IFS.cpp:164-166) Location of the internal SQL CE database (InterfaceDatabase.cpp:488-495) DatabaseConnectionManager, InterfaceDatabase S
LogFolder path "" none Log file directory; read lazily on first GetLogDirectory() with a 255-char buffer and cached (IFS.cpp:880-891) Logger.cpp:36, LogRetentionTask, MessageStoreConsistencyTask, CrashOracle, ExceptionLogger, Mime.cpp, MessageData.cpp, TestErrorLogs; COM InterfaceLogging/BackupSettings L (once)

Installer defaults: ProgramFolder={app}, DatabaseFolder={app}\Database, DataFolder={app}\Data, LogFolder={app}\Logs, TempFolder={app}\Temp, EventFolder={app}\Events (source: installation/section_ini.iss:1-8). Setters: IFS.cpp:995-1058 (COM Settings.Directories).

7. [GUILanguages] section

Key Type Default What it does Consumer Timing
ValidLanguages comma-separated list "" Names of language files allowed to load; case-insensitive match; files are ProgramFolder\Languages\<name>.ini with english.ini as base (IFS.cpp:168-169; Common/Util/Languages.cpp:61-77; Common/Util/Language.cpp:59,84) Languages.cpp S (Languages loaded at InitInstance, Application.cpp:144)

Installer writes ValidLanguages=english,swedish (section_ini.iss:11).

8. [Security] section

Key Type Default What it does Consumer Timing
AdministratorTotpSecret protected string "" Second factor on the administrator credential (added 5 Sep 2026). Stored as a machine-bound DPAPI:-prefixed envelope of a base32 TOTP secret (Crypt::ProtectSecret), unprotected per logon. While set: COMAuthentication refuses Authenticate("administrator") and only AuthenticateWithCode passes; the REST API needs the code in an X-hMailServer-OTP header and 401s with X-hMailServer-OTP: required when it is absent. Enrol/remove via COM Settings.EnrolAdministratorTOTP / DisableAdministratorTOTP; state readable unauthenticated via Application.AdministratorTOTPEnabled. Recovery for a lost authenticator: clear this key. COMAuthentication.cpp, RestApiServer.cpp, InterfaceSettings.cpp S
AdministratorPassword hash string "" COM/REST "Administrator" credential. Empty = no authentication required for COM (COM/COMAuthentication.cpp:119-130) and the REST API refuses to start (per installer comment, hMailServerInnoExtension.iss:277-279; server side unconfirmed by me). Hash type detected by length/prefix: PBKDF2 prefix, Argon2id prefix, 32 chars = MD5, 70 chars = SHA256 (Common/Util/Crypt.cpp:135-151). Written by the server as PBKDF2 (IFS.cpp:982-992); written by the installer as MD5 only when a password was supplied (section_ini.iss:17; hMailServerInnoExtension.iss:264-288) COMAuthentication.cpp:41, RestApiServer.cpp, ScramSha256.cpp S

9. [Settings] section — every key read by IniFileSettings::LoadSettings

195 keys are read in LoadSettings (IFS.cpp:156-674), plus UseLanguage read per call (IFS.cpp:939) = 196 keys owned by IniFileSettings; plus 2 keys read directly by WebServicesServer (section 9.14) = 198 [Settings] keys in total. Booleans are 1/anything-else unless stated. "Consumer" is the file that calls the getter.

9.1 Passwords, hashing, secrets, service account

Key Type Default Validation What it does Consumer Timing
PreferredHashAlgorithm int 4 (PBKDF2) must be 3, 4, 5 or 7 else HM5528 and reset to 4 (IFS.cpp:171-193) Scheme for storing NEW account/app passwords: 3 SHA256, 4 PBKDF2, 5 Argon2id, 7 scrypt (RFC 7914, N=2^17 r=8 p=1, $s2$ hashes; added 5 September 2026; 6 is DPAPI and refused here) InterfaceAccount, AppPassword, PersistentAccount, PasswordValidator, RestApiServer S
MinimumAcceptedHashAlgorithm int 0 none Accounts whose stored hash is weaker than this scheme are refused; 0 disables (IFS.cpp:195-198). Compared by strength (Crypt::StrengthRank, since 5 September 2026), not by number: SHA256 (3) < PBKDF2 (4) < Argon2id (5) = scrypt (7), so a minimum of 5 or of 7 accepts both memory-hard schemes; 6 (DPAPI) is not a password scheme and refuses nothing. The same order decides upgrade-on-logon, so a preference for Argon2id leaves scrypt accounts alone and the other way round AppPassword, PasswordValidator, IMAPCommandAuthenticate, POP3Connection, SMTPConnection S
PasswordPepper string "" none Server-wide secret HMAC-mixed into Argon2id hashes only; empty = off (IFS.cpp:200-203) Crypt.cpp S
PasswordHashIterations int 0 0, or 10000-10000000, else HM5561 and read as 0 PBKDF2 iterations for NEW password hashes; 0 = the built-in 210,000. A stored hash carries its own count, so raising this never breaks a logon: a hash cheaper than the configured value is re-derived on the next successful logon (PasswordValidator::UpgradeStoredPasswordHash_ via HashCreator::NeedsRehash), a costlier one is left alone - lowering it affects new hashes only (IFS.cpp ReadPasswordHashWorkFactor_). Added 5 September 2026 HashCreator, PasswordValidator S
PasswordHashMemoryKB int 0 0, or 4096-1048576, else HM5561 and read as 0 Argon2id memory for new hashes, in KiB; 0 = the built-in 19,456. Same re-derive rule as PasswordHashIterations. Every logon verification allocates this much on a connection thread. Added 5 September 2026 HashCreator, PasswordValidator S
PasswordHashTimeCost int 0 0, or 1-20, else HM5561 and read as 0 Argon2id passes for new hashes; 0 = the built-in 2. Same re-derive rule as PasswordHashIterations. Added 5 September 2026 HashCreator, PasswordValidator S
ProtectStoredSecretsWithDPAPI bool 1 Store the ini DB password and DB-held route/fetch/relayer secrets as machine-bound DPAPI (DPAPI: prefix) instead of Blowfish; legacy values stay readable (IFS.cpp:230-236; Crypt.cpp:188-220) Crypt.cpp, IFS.cpp:1105-1117 S
ServiceAccountName string "" Windows account the service logs on as; empty = LocalSystem. Applied only by hMailServer.exe /Register (CreateService/ChangeServiceConfig) (IFS.cpp:238-247; Common/Util/ServiceManager.cpp:57-62,123-127; hMailServer.cpp:216-249) ServiceManager.cpp S (applied at /Register)
ServiceAccountPassword string "" Password for the above; empty for virtual/managed accounts (IFS.cpp:247) ServiceManager.cpp S (at /Register)
PasswordPolicyMinimumLength int 0 Enforced where a password is CHOSEN (COM Account.Password); 0 = off (IFS.cpp:666; IFS.h:414-418) PasswordPolicy.cpp S
PasswordPolicyRequireMixedCase bool 0 as above (IFS.cpp:667) PasswordPolicy.cpp S
PasswordPolicyRequireDigit bool 0 (IFS.cpp:668) PasswordPolicy.cpp S
PasswordPolicyRequireNonAlphanumeric bool 0 (IFS.cpp:669) PasswordPolicy.cpp S
PasswordPolicyRejectCommon bool 0 Rejects passwords in a compiled-in list (PasswordPolicy.cpp:29,153-159) (IFS.cpp:670) PasswordPolicy.cpp S
PasswordPolicyHistoryCount int 0 Refuse reuse of the last N passwords; 0 = off (IFS.cpp:671; IFS.h:425-432) PasswordHistory.cpp S
PasswordPolicyMaximumAgeDays int 0 Password expiry in days; 0 = off; no self-service change exists so an admin must reset (IFS.cpp:672; IFS.h:427-430) PasswordHistory.cpp S
AccountLockoutThreshold int 0 Failed logons per canonical username before lockout; 0 = mechanism off (IFS.cpp:558; Common/Util/AccountLockout.cpp:136,175) AccountLockout.cpp S
AccountLockoutWindowMinutes int 30 <1 → 30 Window in which failures are counted (IFS.cpp:559-562) AccountLockout.cpp S
AccountLockoutMinutes int 30 <1 → 30 Lock duration (IFS.cpp:560-564) AccountLockout.cpp S
LogonTarpitSeconds int 0 clamped 0–30 Login tarpit (added 5 Sep 2026): each failed logon's refusal waits this × the failures so far on that connection, capped 30s, on SMTP/POP3/IMAP; 0 = off. A pause on the connection's own timer (TCPConnection::EnqueueDelay, BCTDelay), not a thread asleep. A correct password never waits. AccountLogon::TarpitDelaySeconds; SMTP/POP3/IMAPConnection S
SmtpTarpitCount int 0 clamped ≥0 Recipient tarpit (added 5 Sep 2026): recipients an unauthenticated SMTP session may name before the delay applies; 0 = off. Also settable via COM AntiSpam.TarpitCount. SMTPConnection::TarpitRecipient_ S
SmtpTarpitDelaySeconds int 0 clamped 0–30 Recipient tarpit: seconds each RCPT TO past SmtpTarpitCount waits for its reply; 0 = off. Authenticated sessions and ranges with spam protection off are exempt. Also settable via COM AntiSpam.TarpitDelay. SMTPConnection::TarpitRecipient_ S

9.2 OAuth2 inbound (bearer tokens) and outbound/fetch XOAUTH2

Key Type Default What it does Consumer Timing
OAuth2Enabled bool 0 Accept SASL XOAUTH2/OAUTHBEARER JWTs verified locally (IFS.cpp:205-208) OAuth2TokenValidator.cpp S
OAuth2RequireTLS bool 1 Mechanism only offered on TLS (IFS.cpp:209) OAuth2TokenValidator.cpp S
OAuth2AllowedAlgorithms comma list RS256 Accepted JWT alg: HS256, RS256, ES256 recognised; none never honoured (IFS.cpp:218-220; OAuth2TokenValidator.cpp:216-220,390) OAuth2TokenValidator.cpp S
OAuth2HmacSecret string "" HS256 key (IFS.cpp:222) OAuth2TokenValidator.cpp S
OAuth2PublicKeyFile path "" PEM RSA/EC public key for RS256/ES256 (IFS.cpp:224; getter is named GetOAuth2RsaPublicKeyFile) OAuth2TokenValidator.cpp S
OAuth2Issuer string "" Expected iss; empty disables the check (IFS.cpp:226) OAuth2TokenValidator.cpp S
OAuth2Audience string "" Expected aud; empty disables (IFS.cpp:227) OAuth2TokenValidator.cpp S
OAuth2UsernameClaim string email Claim carrying the login name (IFS.cpp:229) OAuth2TokenValidator.cpp S
OAuth2JwksUrl string "" RFC 7517 JWK Set URL: the provider's published signing keys, selected by the token's kid for RS256/ES256, with OAuth2PublicKeyFile as the fallback. https, or plain http to a loopback address only (Common/Util/JwksKeySet.cpp, HttpsClient.cpp). Added 5 September 2026 OAuth2TokenValidator S
OAuth2JwksCacheSeconds int seconds 3600 How long the JWK Set is kept before re-fetching; a token naming an unknown kid re-fetches at once, at most once a minute. Minimum 10. Added 5 September 2026 JwksKeySet S
OAuth2IntrospectionUrl string "" RFC 7662 introspection endpoint, asked after a token verifies locally whether it is still active; empty = no revocation check (Common/Util/TokenIntrospection.cpp). Added 5 September 2026 OAuth2TokenValidator S
OAuth2IntrospectionClientId / OAuth2IntrospectionClientSecret string "" The client this server introspects as (HTTP Basic, RFC 6749 2.3.1). Added 5 September 2026 TokenIntrospection S
OAuth2IntrospectionCacheSeconds int seconds 300 How long a verdict (active or not) is reused, never past the token's exp. Added 5 September 2026 TokenIntrospection S
OAuth2IntrospectionFailOpen int (0/1) 0 1 accepts a token when the endpoint cannot answer (RFC 7662 says treat it as inactive, which 0 does); every such acceptance is written to the application log. Added 5 September 2026 TokenIntrospection S
OutboundOAuth2TokenUrl URL "" client_credentials token endpoint for outbound relay XOAUTH2 (IFS.cpp:210; IFS.h:161-166) OutboundOAuth2TokenClient.cpp, ExternalDelivery.cpp S
OutboundOAuth2ClientId string "" (IFS.cpp:211) OutboundOAuth2TokenClient.cpp S
OutboundOAuth2ClientSecret string "" (IFS.cpp:212) OutboundOAuth2TokenClient.cpp S
OutboundOAuth2Scope string https://outlook.office365.com/.default (IFS.cpp:213-214) OutboundOAuth2TokenClient.cpp S
OutboundOAuth2Hosts comma list smtp.office365.com Relay hosts the token is presented to (IFS.cpp:215) ExternalDelivery.cpp S
OutboundOAuth2FixedToken string "" Used verbatim instead of fetching (tests / externally obtained tokens) (IFS.cpp:216) ExternalDelivery.cpp, POP3ClientConnection.cpp S
FetchOAuth2Hosts comma list outlook.office365.com External (POP3 fetch) servers that authenticate with XOAUTH2 using the outbound credentials (IFS.cpp:217; IFS.h:174-177) POP3ClientConnection.cpp S

9.3 Logging and diagnostics

Key Type Default Validation What it does Consumer Timing
SepSvcLogs bool 0 Separate hmailserver_SMTP_<date>.log / _POP3_ / _IMAP_ files instead of one hmailserver_<date>.log (IFS.cpp:251; Logger.cpp:489-505, re-read at 522) Logger.cpp S
LogLevel int 9 Effects found: line truncation only when LogLevel <= 2 and debug off (Logger.cpp:676-697); IMAP logs SENT: FETCH/STATUS lines only when > 2 or debug (IMAPConnection.cpp:673-677) (IFS.cpp:252) Logger.cpp, IMAPConnection.cpp S
MaxLogLineLen int 500 <100 → 100; truncation also requires ≥80 Long log lines are cut to head+" ... "+tail (never AWStats) (IFS.cpp:253-254; Logger.cpp:664-699) Logger.cpp S
JsonLogging bool 0 JSON-formatted log lines (IFS.cpp:406) Logger.cpp S
LogDeleteDays int days 0 Delete date-stamped log files (and SQL log rows) older than N days; 0 = keep forever (IFS.cpp:407; LogRetentionTask.h:8-11) LogRetentionTask.cpp, SqlLogDevice.cpp S
SlowQueryLogMilliseconds int ms 0 Statements at/over this are counted slow and logged redacted; 0 = off, latency metrics still run (IFS.cpp:409; DatabaseConnectionManager.cpp:290-293) DatabaseConnectionManager.cpp S
WindowsEventLogEnabled bool 1 Windows Application event log sink (IFS.cpp:628; IFS.h:298-308) WindowsEventLog.cpp S
WindowsEventLogLevel int 2 clamped 1..4 Severity written: 1 Critical, 2 +High, 3 +Medium, 4 +Low (IFS.cpp:629-638) WindowsEventLog.cpp S
OtelEndpoint URL "" OTLP/HTTP traces endpoint (e.g. http://127.0.0.1:4318/v1/traces); empty = tracing off (IFS.cpp:448; IFS.h:569-571) OtelTracer.cpp S! (Start)
OtelServiceName string hmailserver service.name resource (IFS.cpp:449) OtelTracer/OtelMetricsExporter/OtelLogExporter S!
OtelMetricsEndpoint URL "" OTLP metrics push endpoint; empty = off (IFS.cpp:450) OtelMetricsExporter.cpp S!
OtelLogsEndpoint URL "" OTLP logs endpoint; empty = off (IFS.cpp:451) OtelLogExporter.cpp S!
OtelMetricsInterval int seconds 60 clamped 5..3600 by exporter Push interval (IFS.cpp:452; OtelMetricsExporter.cpp:27-31,78-82) OtelMetricsExporter.cpp S!
MessageStoreConsistencyCheck bool 0 Scheduled read-only walk comparing DB to disk; publishes hmailserver_messagestore_missing_files (IFS.cpp:425; MessageStoreConsistencyTask.h:8-15) MessageStoreConsistencyTask.cpp S
MetricsPerDomainEnabled bool 0 Per-domain counters on /metrics (cardinality decision) (IFS.cpp:656; IFS.h:383-389) MetricsServer.cpp, SMTPConnection.cpp S

9.4 Metrics / health listener

Key Type Default What it does Consumer Timing
MetricsServerPort int 0 Prometheus /metrics + /livez /readyz /healthz listener; 0 = not started (IFS.cpp:426; Application.cpp:496-501) Application.cpp S!
MetricsServerBindAddress IP 127.0.0.1 Bind address (IFS.cpp:427). Non-loopback bind with no credential → /metrics answers 503, probes still served (MetricsServer.cpp:375-400) Application.cpp, MetricsServer.cpp S!
MetricsServerAuthToken string "" Bearer token for /metrics (IFS.cpp:433) MetricsServer.cpp S!
MetricsServerAuthUsername string "" Basic auth user (both user+password needed) (IFS.cpp:434) MetricsServer.cpp S!
MetricsServerAuthPassword string "" (IFS.cpp:435) MetricsServer.cpp S!
MetricsServerCertificateFile path "" Both cert+key needed for HTTPS on the metrics port; never inferred (IFS.cpp:436; IFS.h:551-556) MetricsServer.cpp S!
MetricsServerPrivateKeyFile path "" (IFS.cpp:437) MetricsServer.cpp S!

9.5 REST API, web services, ACME, autoconfig, MTA-STS hosting

Key Type Default What it does Consumer Timing
RestApiPort int 0 REST admin API + Web Admin page listener; 0 = not started (IFS.cpp:591; Application.cpp:510-533) Application.cpp S!
RestApiBindAddress IP 127.0.0.1 (IFS.cpp:592) Application.cpp S!
RestApiCertificateFile path "" HTTPS cert; if empty and ACME fullchain.pem+privkey.pem exist in the ACME dir they are used (IFS.cpp:593; Application.cpp:515-528) Application.cpp S!
RestApiPrivateKeyFile path "" (IFS.cpp:594) Application.cpp S!
WebServicesHttpPort int 0 Public web services (MTA-STS policy, autoconfig, ACME HTTP-01, DAV redirects) listener; started only if this or the HTTPS port > 0 (IFS.cpp:602; Application.cpp:537-563) Application.cpp, WebServicesServer.cpp S!
WebServicesHttpsPort int 0 (IFS.cpp:603) Application.cpp S!
WebServicesBindAddress IP 0.0.0.0 (IFS.cpp:604) Application.cpp, RestApiServer.cpp S!
WebServicesCertificateFile path "" (IFS.cpp:605) Application.cpp S!
WebServicesPrivateKeyFile path "" (IFS.cpp:606) Application.cpp S!
MtaStsHostingEnabled bool 1 Serve /.well-known/mta-sts.txt — inert unless a WebServices port is set; ReportUnreachableFeatures logs this (IFS.cpp:607; Application.cpp:542-557) WebServicesServer.cpp S!
MtaStsPolicyMode string enforce Must be enforce, testing or none; anything else → enforce (IFS.cpp:608; WebServicesServer.cpp:1128-1131) WebServicesServer.cpp S
MtaStsPolicyMaxAge int seconds 604800 (IFS.cpp:609) WebServicesServer.cpp S
MtaStsPolicyMx comma list "" MX hosts in the policy; empty → derived from MX lookup (mx_cache) (IFS.cpp:610; WebServicesServer.cpp:1030-1040, 95-97) WebServicesServer.cpp S
AutoconfigEnabled bool 1 Thunderbird/Outlook autoconfig endpoints — also inert without a WebServices port (IFS.cpp:611) WebServicesServer.cpp, RestApiServer.cpp S
AutoconfigClientHost host "" Host advertised to clients; empty → COM Settings.HostName (IFS.cpp:612; WebServicesServer.cpp:1352-1358) WebServicesServer.cpp, RestApiServer.cpp S
AcmeEnabled bool 0 Let's Encrypt issuance/renewal (IFS.cpp:595; Application.cpp:791) Application.cpp, AcmeClient.cpp, WebServicesServer.cpp S!
AcmeDirectoryUrl URL https://acme-v02.api.letsencrypt.org/directory (IFS.cpp:596) AcmeClient.cpp S
AcmeContactEmail string "" (IFS.cpp:597) AcmeClient.cpp S
AcmeDomains comma list "" (IFS.cpp:598) AcmeClient.cpp S
AcmeCertificateDirectory path "" Empty → DataFolder\ACME (IFS.cpp:599; AcmeClient.cpp:250-255) AcmeClient.cpp, Application.cpp S
AcmeHttpPort int 80 Port expected for HTTP-01 (IFS.cpp:600) AcmeClient.cpp, WebServicesServer.cpp S
AcmeReuseKey bool 1 Keep the private key across renewals (IFS.cpp:601) AcmeClient.cpp S

9.6 ManageSieve, TLS

Key Type Default Validation What it does Consumer Timing
ManageSieveServerPort int 0 RFC 5804 listener; 0 = not started (IFS.cpp:453; Application.cpp:566-571) Application.cpp S!
ManageSieveServerBindAddress IP 127.0.0.1 (IFS.cpp:454) Application.cpp S!
TlsKeyExchangeGroups OpenSSL group list X25519MLKEM768:SecP256r1MLKEM768:X25519:secp384r1:secp256r1 empty/rejected → classical fallback Key-exchange preference for SMTP/POP3/IMAP/ManageSieve listeners and outbound SMTP; NOT the REST/Web Services listeners (IFS.cpp:566-572; IFS.h:668-681; SslContextInitializer.cpp:870) SslContextInitializer.cpp S! (context build)
TlsCipherSuites13 colon list "" TLS 1.3 suites (RFC 8446 names); empty = OpenSSL default; same scope as above; distinct from COM SslCipherList which governs TLS ≤1.2 only (IFS.cpp:574-578; IFS.h:682-702; SslContextInitializer.cpp:715) SslContextInitializer.cpp S!
TlsSessionTicketsEnabled bool 1 0 sets SSL_OP_NO_TICKET and num_tickets 0 (IFS.cpp:584; IFS.h:704-712; SslContextInitializer.cpp:589) SslContextInitializer.cpp S!
TlsSessionCacheSize int 0 0 = OpenSSL default; negative = server-side cache off (IFS.cpp:585; IFS.h:714-717) SslContextInitializer.cpp:571 S!
TlsSessionTimeoutSeconds int 0 0 = OpenSSL default (IFS.cpp:586) SslContextInitializer.cpp:584 S!
TlsTicketKeyRotationSeconds int 0 <0 → 0 0 = no ticket-key callback; >0 rotates keeping previous key (IFS.cpp:588-590; IFS.h:723-728) SslContextInitializer.cpp:613 S!

9.7 DNS, DNSSEC, DANE, MTA-STS (outbound)

Key Type Default What it does Consumer Timing
UseDNSCache bool 1 0 adds DNS_QUERY_BYPASS_CACHE (IFS.cpp:400; DNSResolverWinApi.cpp:184-187) DNSResolverWinApi.cpp S
DNSServer IPv4 address "" Custom resolver, IPv4 only, no port syntax (inet_addr); invalid → HM4401 and system DNS; port field deliberately 0 (IFS.cpp:401; DNSResolverWinApi.cpp:252-291) DNSResolverWinApi.cpp, DnssecResolver.cpp, TlsPolicy.cpp S
DNSQueryTimeout int seconds 10 Bound on a resolver call; 0 = INFINITE (IFS.cpp:293; DNSResolverWinApi.cpp:120-124) DNSResolverWinApi.cpp S
DnssecValidationEnabled bool 1 Built-in DNSSEC validation (IFS.cpp:404) DNSResolver.cpp, DnssecResolver.cpp, TlsPolicy.cpp S
DnssecTrustAnchors ;-separated DS records keytag alg digesttype hexdigest "" Overrides the built-in IANA root KSKs (20326 …) when non-empty (IFS.cpp:405; DnssecResolver.cpp:1003-1040) DnssecResolver.cpp S
DaneEnforcementEnabled bool 1 DANE/TLSA enforcement on outbound delivery. Getter is GetDaneEnabled; the key is NOT DaneEnabled (IFS.cpp:403; ExternalDelivery.cpp) ExternalDelivery.cpp S
MtaStsEnabled bool 1 Outbound MTA-STS policy fetching/enforcement (IFS.cpp:402) ExternalDelivery.cpp S
SpfVoidLookupLimit int 2 RFC 7208 4.6.4 void-lookup limit; 0 disables (IFS.cpp:640; IFS.h:317-321) SPF/SPF.cpp S
DmarcTreeWalkEnabled bool 1 RFC 9989 tree walk for organisational domain; 0 = compiled-in PSL (IFS.cpp:639; IFS.h:310-315) DMARC/DMARC.cpp S

9.8 DKIM, ARC, Authentication-Results, TLS-RPT/DMARC reporting

Key Type Default Validation What it does Consumer Timing
ArcSealingEnabled bool 0 ARC seal on outbound (IFS.cpp:455) DKIMSigner.cpp S
DKIMSignatureValiditySeconds int 0 <0 → 0 Emit x= on our signatures; 0 = none (IFS.cpp:464-466; IFS.h:587-595) DKIM.cpp S
DKIMEnforceSignatureExpiry bool 1 Refuse verified signatures whose x= has passed (IFS.cpp:468) DKIM.cpp S
DKIMExpiryClockSkewSeconds int 300 <0 → 0 Skew allowance for x= (IFS.cpp:470-472) DKIM.cpp S
DkimOversignHeaders :/,/;-separated names "" Header fields listed once more than present in h= (IFS.cpp:474; IFS.h:606-613) DKIM.cpp S
DkimAcceptSha1 bool 0 Restore rsa-sha1 signing and verification (RFC 8301 forbids) (IFS.cpp:642; IFS.h:330-335) DKIM.cpp, DKIMSigner.cpp S
AuthenticationResultsEnabled bool 0 Add RFC 8601 Authentication-Results to inbound mail (IFS.cpp:480) AuthenticationResultsWriter.cpp, SMTPConnection.cpp S
ReceivedSpfHeaderEnabled bool 0 Add RFC 7208 9.1 Received-SPF (IFS.cpp:481) AuthenticationResultsWriter.cpp, SMTPConnection.cpp S
AuthenticationResultsIdentity string "" authserv-id; empty = computer name (IFS.cpp:482; IFS.h:623-627) AuthenticationResults.cpp S
TlsRptFromAddress e-mail "" Sender for TLS-RPT reports; empty = statistics collected and discarded, nothing sent (IFS.cpp:483, 486-489) TlsRptReporterTask.cpp, InterfaceUtilities.cpp S
TlsRptOrganizationName string hMailServer (IFS.cpp:484) TlsRptReporterTask.cpp S
DmarcRptFromAddress e-mail "" DMARC aggregate report sender; empty = never sent (IFS.cpp:490) DmarcRptReporterTask.cpp, InterfaceUtilities.cpp S
DmarcRptOrganizationName string hMailServer (IFS.cpp:491) DmarcRptReporterTask.cpp S
DmarcRptSchemaVersion int 1 must be 1 or 2 else HM6210 and reset to 1 1 = RFC 7489 App. C, 2 = RFC 9990/DMARCbis (IFS.cpp:493-525) DmarcRptReporterTask.cpp S

9.9 SMTP inbound behaviour, headers, rate shaping, proxy/XCLIENT

Key Type Default What it does Consumer Timing
DNSBLChecksAfterMailFrom bool 1 1 = pre-transmission spam checks (DNSBL etc.) run at MAIL FROM; 0 = at RCPT TO (IFS.cpp:249; SMTPConnection.cpp:839-848, 1326-1335) SMTPConnection.cpp S
AddXAuthUserHeader bool 0 Add X-AuthUser: for authenticated senders if absent (IFS.cpp:157; SMTPMessageHeaderCreator.cpp:91-95) SMTPMessageHeaderCreator.cpp S
AddXAuthUserIP bool 1 Include the authenticated sender's IP in the Received header handling (IFS.cpp:398; SMTPMessageHeaderCreator.cpp:61-69) SMTPMessageHeaderCreator.cpp S
AuthUserReplacementIP IP string "" If set, authenticated senders' real IP in Received is replaced by this value (IFS.cpp:353; SMTPMessageHeaderCreator.cpp:60-69) SMTPMessageHeaderCreator.cpp S
AddXOriginalRcptTo bool 0 Add X-Original-Rcpt-To (getter GetAddXOriginalRcptToHeader) (IFS.cpp:399; SMTPMessageHeaderCreator.cpp:97-110) SMTPMessageHeaderCreator.cpp S
DaemonAddressDomain domain "" Domain used for the mailer-daemon address (IFS.cpp:159) MailerDaemonAddressDeterminer.cpp S
DisableAUTHList comma list of ports "" SMTP AUTH not offered/accepted on these local ports (IFS.cpp:674, 1193-1212; SMTPConnection.cpp:4791-4796) SMTPConnection.cpp S
SMTPDMaxSizeDrop int KB 0 Hard drop: 552 5.3.4 when the received size ≥ this; 0 = off (IFS.cpp:396; SMTPConnection.cpp:1618-1628, 3400-3412) SMTPConnection.cpp S
FinalizationTimeout int seconds 240 Ceiling on post-DATA accept/save work; past it a temporary 451 is sent; 0 = off (IFS.cpp:276-281; SMTPConnection.cpp:2010-2018) SMTPConnection.cpp S
MaxSubmissionsPerIPPerMinute int 0 421 "Too many messages from your IP" when exceeded; 0 = unlimited (IFS.cpp:618; SMTPConnection.cpp:728-738) SMTPConnection.cpp S
MaxOutboundPerDestinationPerMinute int 0 Defers delivery to a destination domain beyond N/min; 0 = unlimited (IFS.cpp:673; ExternalDelivery.cpp:180-192) ExternalDelivery.cpp S
RejectFullMailboxAtRcpt bool 1 Refuse at RCPT TO when mailbox at/over quota instead of accept-then-bounce (IFS.cpp:641; IFS.h:323-328) RecipientParser.cpp S
QuotaWarningPercent int 90 Warn the account holder when a delivery crosses this % of quota; 0 = off (IFS.cpp:643; IFS.h:337-342) QuotaWarner.cpp S
SMTPProxyProtocolEnabled bool 0 HAProxy PROXY v1/v2 on the SMTP listener, before TLS/banner (IFS.cpp:620; IFS.h:270-283) TCPServer.cpp, SMTPConnection.cpp S
SMTPProxyProtocolTrustedIPs comma list of IP/CIDR "" Real TCP peers allowed to send PROXY; empty = nobody (IFS.cpp:621) TCPServer.cpp S
SMTPXClientEnabled bool 0 Postfix XCLIENT verb (IFS.cpp:622) SMTPConnection.cpp S
SMTPXClientTrustedIPs comma list of IP/CIDR "" (IFS.cpp:623) SMTPConnection.cpp S
BlockedIPHoldSeconds int seconds 0 Hold a connection from a blocked IP before closing (no longer a Sleep on the I/O thread) (IFS.cpp:395; TCPServer.cpp:388-400) TCPServer.cpp S
RewriteEnvelopeFromWhenForwarding bool 0 Rewrite envelope sender on forwards; also settable via COM Settings.RewriteEnvelopeFromWhenForwarding which writes the file AND mirror (IFS.cpp:613, 1175-1185) RuleApplier.cpp, SMTPForwarding.cpp S
SRSEnabled bool 0 Sender Rewriting Scheme on forwards (IFS.cpp:614) RecipientParser.cpp, SMTPForwarding.cpp S
SRSSecret string "" (IFS.cpp:615) RecipientParser.cpp, SMTPForwarding.cpp S
BATVEnabled bool 0 prvs bounce-address tagging (IFS.cpp:616) RecipientParser.cpp, SMTPClientConnection.cpp S
BATVSecret string "" (IFS.cpp:617) RecipientParser.cpp, SMTPClientConnection.cpp S
Pop3LoginDelaySeconds int 0 RFC 2449 LOGIN-DELAY advertised in CAPA and enforced; 0 = neither (IFS.cpp:619; POP3Connection.cpp:527-534) Pop3LoginDelay.cpp, POP3Connection.cpp S

9.10 Delivery queue, retries, external fetch

Key Type Default Validation What it does Consumer Timing
QuickRetries int 0 First N failed deliveries are rescheduled after QuickRetriesMinutes instead of the normal interval (greylisting aid) (IFS.cpp:255; ExternalDelivery.cpp:868-918) ExternalDelivery.cpp S
QuickRetriesMinutes int minutes 6 (IFS.cpp:256) ExternalDelivery.cpp S
QueueRandomnessMinutes int minutes 0 ≤0 → 0 Random 1..N minutes added to every retry delay (IFS.cpp:257-259; ExternalDelivery.cpp:876-889) ExternalDelivery.cpp S
MXTriesFactor int 0 ≤0 → 0 Limits MX hosts tried per attempt to (retries+1) * factor; 0 = all (IFS.cpp:260-261; ExternalDelivery.cpp:397-403) ExternalDelivery.cpp S
ClientSessionCeiling int seconds 1800 Absolute ceiling on an outbound SMTP client session (IFS.cpp:295-299) SMTPClientConnection.cpp S
OutboundPipelining 0/1 1 RFC 2920 on the delivery client (added 5 Sep 2026): when the remote advertises PIPELINING, MAIL FROM, every RCPT TO and the data command go in one flight and the replies are counted back in order (SMTPClientConnection::SendPipelinedEnvelope_ / ProtocolEnvelopePipelined_); 0 = one command per reply SMTPClientConnection.cpp S
OutboundChunking 0/1 1 RFC 3030 on the delivery client (added 5 Sep 2026): when the remote advertises CHUNKING the message goes as one BDAT <size> LAST chunk streamed raw (StartBdat_), no 354 and no dot-stuffing; also what lets a BINARYMIME message be relayed (BODY=BINARYMIME needs BINARYMIME + CHUNKING advertised); 0 = DATA SMTPClientConnection.cpp S
MaxNumberOfExternalFetchThreads int 15 POP3 external-account fetch concurrency (IFS.cpp:156) ExternalFetchManager.cpp S
GreylistingEnabledDuringRecordExpiration bool 1 0 temporarily disables greylisting while expired records are purged (IFS.cpp:161; GreyListCleanerTask.cpp:47-58) GreyListCleanerTask.cpp S
GreylistingRecordExpirationInterval int minutes 240 Minutes between cleaner runs (getter GetGreylistingExpirationInterval) (IFS.cpp:162; Application.cpp:613) Application.cpp S!

9.11 Protocol timeouts, external processes, work queue

Key Type Default What it does Consumer Timing
SMTPDMinTimeout / SMTPDMaxTimeout int seconds 10 / 1800 SMTP server idle timeout bounds (IFS.cpp:270-271) SMTPConnection.cpp S
SMTPCMinTimeout / SMTPCMaxTimeout int seconds 30 / 600 SMTP client (outbound) idle timeout bounds (IFS.cpp:272-273) SMTPClientConnection.cpp S
POP3DMinTimeout / POP3DMaxTimeout int seconds 10 / 600 POP3 server (IFS.cpp:266-267) POP3Connection.cpp S
POP3CMinTimeout / POP3CMaxTimeout int seconds 30 / 900 POP3 fetch client (IFS.cpp:268-269) POP3ClientConnection.cpp S
SAMinTimeout / SAMaxTimeout int seconds 30 / 90 SpamAssassin client (IFS.cpp:274-275) SpamAssassinClient.cpp S
ClamMinTimeout / ClamMaxTimeout int seconds 15 / 90 ClamAV (clamd) client (IFS.cpp:282-283) ClamAVVirusScanner.cpp S
SAMoveVsCopy bool 0 Move rather than copy the SpamAssassin result file (IFS.cpp:352) SpamAssassinClient.cpp S
SpamAssassinUser string empty Sent to spamd as the User: header of every PROCESS request, so spamd applies that user's preferences (user_prefs or its SQL preference store) instead of its global configuration. Empty sends no header. Control characters are refused and read as empty (added 5 September 2026) SpamTestSpamAssassin.cpp, SpamAssassinClient.cpp S
SpamAssassinUserFromRecipient bool 0 When 1 and the message has exactly one recipient, that recipient's address is sent as the User: header instead of SpamAssassinUser. A scan runs once per message, so a message to several recipients falls back to SpamAssassinUser (or to no header). Off by default so no existing spamd sees a header it was never sent (added 5 September 2026) SpamTestSpamAssassin.cpp S
ScriptTimeout int seconds 60 Bound on an event script run; 0 = none (IFS.cpp:285-307) ScriptServer.cpp S
ExternalProcessTimeout int seconds 300 Bound on external virus scanner / process; 0 = none (IFS.cpp:308) ProcessLauncher.cpp S
DBConnectionAcquireTimeout int seconds 60 Bound on waiting for a pooled DB connection; 0 = unbounded (IFS.cpp:300-306; DatabaseConnectionManager.cpp:436-440) DatabaseConnectionManager.cpp S
DatabaseStatementTimeout int seconds 30 <0 → 0; per-statement timeout on all four backends; 0 = none (IFS.cpp:645-651; IFS.h:350-363) DALConnection.cpp S
AsyncQueueStallThreshold int seconds 120 How long all async workers may be busy before the server reports what holds them (IFS.cpp:310-314) WorkQueue.cpp S
AsyncQueueReservedThreads int 2 Threads kept free of scanning/scripting for short work (IFS.cpp:315) WorkQueue.cpp S
ShutdownDrainSeconds int seconds 0 Wait up to N s for sessions to finish on stop; ≤0 = no wait (IFS.cpp:408; Application.cpp:822-850) Application.cpp S! (stop)

9.12 IMAP search, indexing, expunge tombstones, message loading

Key Type Default Validation What it does Consumer Timing
IMAPSearchTimeout int seconds 60 <0 → 0 Ceiling on one SEARCH/SORT; 0 = off (IFS.cpp:317-348) IMAPCommandSearch.cpp S
IMAPSearchMaxMegabytes int MB 2048 <0 → 0 Ceiling on message content examined by one search; 0 = off (IFS.cpp:349-350) IMAPCommandSearch.cpp S
IndexerFullMinutes int minutes 720 Full metadata-index pass cadence (IFS.cpp:354) MessageIndexer.cpp S
IndexerFullLimit int 25000 Max messages per full pass (IFS.cpp:355) PersistentMessageMetaData.cpp S
IndexerQuickLimit int 1000 Max messages per quick pass (IFS.cpp:356) PersistentMessageMetaData.cpp S
IndexerFullText bool 0 Full-text term index (hm_messageindexterms) for SEARCH BODY/TEXT; also requires COM Settings.MessageIndexing (getter GetIndexerFullTextEnabled) (IFS.cpp:358-365; IFS.h:228-236; MessageIndexer.cpp:87,109,169) MessageIndexer.cpp, IMAPCommandSearch.cpp S
IndexerFullTextBatchSize int 250 1..100000 Backfill id-range width per pass (IFS.cpp:367-374) MessageIndexer.cpp S
IndexerFullTextMinTokenLength int 3 3..64 Shortest run the index answers for (IFS.cpp:376-384) IMAPCommandSearch.cpp S
IndexerFullTextMaxTokensPerMessage int 2048 64..1000000 Terms per message before it is marked always-scanned (IFS.cpp:386-392) MessageIndexer.cpp S
IMAPExpungeRetentionRecords int 5000 <0 → 0 QRESYNC tombstones kept per mailbox; 0 = keep all (IFS.cpp:646-654; IFS.h:365-381) IMAPExpungeRetentionTask.cpp S
LoadHeaderReadSize int bytes 4000 Initial read size when loading a message header (IFS.cpp:393) PersistentMessage.cpp S
LoadBodyReadSize int bytes 4000 (IFS.cpp:394) PersistentMessage.cpp S

9.13 Storage, archive, backup, quarantine, trace, disk space, filter hook, AV policy, fault injection

Key Type Default Validation What it does Consumer Timing
ArchiveDir path "" trailing \ stripped Copy of every message received by SMTP into ArchiveDir\<domain>\<user>\...; empty = off (IFS.cpp:262-264; SMTPConnection.cpp:1756-1775) SMTPConnection.cpp, ArchiveRetentionTask.cpp, AddressTraceEraser.cpp S
ArchiveHardLinks bool 0 Hard-link instead of copy (getter GetArchiveHardlinks) (IFS.cpp:265) SMTPConnection.cpp S
ArchiveRetentionDays int days 0 Delete archived messages older than N; 0 = keep (IFS.cpp:644; IFS.h:344-348) ArchiveRetentionTask.cpp S
ArchiveDomains comma-separated domains "" trimmed, lower-cased Scope of the archive (added 5 Sep 2026): empty = every message; otherwise a message is archived only when its local sender or a recipient belongs to a listed domain, and only the copies for listed domains are made (IsArchiveDomain, SMTPConnection::ArchiveScopeIncludesMessage_) SMTPConnection.cpp S
BackupMessagesDBOnly bool 0 Backup/restore skips message files but keeps DB message rows (IFS.cpp:397; BackupExecuter.cpp:361-362, 736) BackupExecuter.cpp, BackupRestorer.cpp S
ScheduledBackupTime HH:MM local "" strict 24-h parse; unparsable → logged, no daily backup Daily backup time; wins over interval (IFS.cpp:444; IFS.h:558-564; BackupScheduleTask.cpp:68-87,146-149) BackupScheduleTask.cpp S!
ScheduledBackupIntervalMinutes int 0 Interval schedule; 0 = none (IFS.cpp:445) BackupScheduleTask.cpp S!
ScheduledBackupKeepCount int 0 Archives to keep; 0 = never delete (IFS.cpp:446) BackupExecuter.cpp, BackupScheduleTask.cpp S
BackupVerifyRestore 0/1 1 Verified restore (added 5 Sep 2026): after every backup that includes messages, BackupExecuter::VerifyRestore_ extracts the message store to the temp directory through BackupRestorer::Prepare, holds it to exactly the files staged (count and bytes; a mismatch discards the archive), and names every message row without a file in the backup log (never fails the backup). Skipped with a log line when the temp volume cannot hold the store; 0 skips it altogether BackupExecuter.cpp S
ScheduledBackupMaxAgeDays int 0 0 = never delete (IFS.cpp:447) BackupExecuter.cpp, BackupScheduleTask.cpp S
QuarantineEnabled bool 0 Store spam that would have been REFUSED instead (IFS.cpp:624; IFS.h:285-290) QuarantineStore.cpp S
QuarantineRetentionDays int days 30 0 = never sweep (IFS.cpp:625) QuarantineStore.cpp S
MessageTraceEnabled bool 0 Queryable who-corresponded-with-whom table (privacy default) (IFS.cpp:626; IFS.h:292-296) MessageTrace.cpp S
MessageTraceRetentionDays int days 30 (IFS.cpp:627) MessageTrace.cpp S
MessageStoreFsync bool 0 Flush spool file to disk before 250; failure refuses the message (IFS.cpp:410; TransparentTransmissionBuffer.cpp:311,433) TransparentTransmissionBuffer.cpp S
MinimumFreeDiskSpaceMB int MB 100 <0 → 0 Below this new mail gets SMTP 452 4.3.1 / IMAP APPEND NO [UNAVAILABLE]; 0 = off (IFS.cpp:412-418; IFS.h:449-470) DiskSpace.cpp S
DiskSpaceWarningThresholdMB int MB 1024 <0 → 0 Application-log warning threshold (IFS.cpp:419-420) DiskSpace.cpp S
FilterHookUrl URL "" External filter engine (rspamd-style) for every accepted message; empty = off (IFS.cpp:658; IFS.h:391-394) SpamTestFilterHook.cpp S
FilterHookTimeoutSeconds int 10 <1 → 1 (IFS.cpp:659, 664-665) SpamTestFilterHook.cpp S
FilterHookFailClosed bool 0 1 = refuse mail when the engine does not answer (IFS.cpp:660) SpamTestFilterHook.cpp S
FilterHookRejectScore int 100 Spam score assigned to the engine's "reject" (IFS.cpp:661) SpamTestFilterHook.cpp S
FilterHookMaxMessageSizeKB int KB 10240 Larger messages bypass the hook; 0 = no ceiling (IFS.cpp:662) SpamTestFilterHook.cpp S
AVFailAction int 0 0..1 else 0 0 deliver unscanned; 1 hold+retry then bounce (IFS.cpp:527-539; IFS.h:637-641) SMTPDeliverer.cpp S
AVFailRetryMinutes int 15 <1 → 15 Hold retry interval (IFS.cpp:544-546) SMTPDeliverer.cpp S
AVFailMaxHolds int 16 <0 → 16 Hold attempts before bounce; 0 = bounce immediately (IFS.cpp:551-553) SMTPDeliverer.cpp S
SimulateSpoolWriteFailure int 0 Test-only: 1 every spool write fails, 2 first succeeds then fail (IFS.cpp:422; IFS.h:482-504; TransparentTransmissionBuffer.cpp:492-495) TransparentTransmissionBuffer.cpp S
SimulateDatabaseFailureFor string "" Test-only: fail every SQL statement containing this substring; reported as HM6119 (High) on every start/reinitialise while set (IFS.cpp:423-424; Application.cpp:205-224) DatabaseConnectionManager.cpp, Application.cpp S

9.14 [Settings] keys read outside LoadSettings

Key Type Default What it does Where read Timing
UseLanguage string English COM Settings.UserInterfaceLanguage; file read on every get; set writes the file and the mirror (IFS.cpp:934-955; InterfaceSettings.cpp:978,995) IFS.cpp:939 L
CalDavRedirectUrl http(s) URL ≤512 printable ASCII "" 3xx redirect target for /.well-known/caldav; invalid values treated as unset and reported once (WebServicesServer.cpp:110-160, 1255-1263, 1309, 1341) WebServicesServer.cpp:125 L (60 s cache)
CardDavRedirectUrl same "" /.well-known/carddav WebServicesServer.cpp:125 L (60 s cache)

10. [SendingLimits] and [SendingLimitsOverrides] sections (per-account sending quotas)

  • Read by RateLimiter::LoadSettings_ directly with GetPrivateProfileString/Section, NOT through IniFileSettings and NOT mirrored to hm_inisettings (source: Common/Util/RateLimiter.cpp:30-31, 1025-1160).
  • Re-read live when the file's last-write time changes, checked at most every 2 s (kSettingsRecheckSeconds, RateLimiter.cpp:61, 1166-1181). Whole mechanism inert until configured (RateLimiter.h:15).
Section Key Type Default Clamp Meaning (source RateLimiter.cpp)
[SendingLimits] MaxMessagesPerAccountPerPeriod int 0 <0 → 0 Messages an authenticated account may submit per period; 0 = unlimited (1050-1051, 1062)
[SendingLimits] MaxRecipientsPerAccountPerPeriod int 0 <0 → 0 Recipients per period; 0 = unlimited (1053-1054, 1063)
[SendingLimits] PeriodHours int 24 1..168 Rolling period (53-54, 1056-1057, 1064-1069)
[SendingLimits] StateSaveIntervalSeconds int 10 <1 → 10; >3600 → 3600 Persist counters to DataFolder\hmailserver_sendinglimits.dat (34, 64-66, 1059-1074, 268-270)
[SendingLimitsOverrides] <address>=<messages>:<recipients>[:<hours>] per-line — hours 1..168 Per-account override; address lower-cased/trimmed; malformed lines counted and logged (982-1020, 1093-1146)
  • Enforced in SMTPConnection.cpp:999-1065 (TryConsumeAccountMessage/Recipients).

11. [LDAP] section (directory authentication and directory sync)

  • Read by LdapSettings::Load_ directly (Common/LDAP/LdapSettings.cpp:206-345), re-read when the file timestamp changes, checked at most every 2 s (LdapSettings.cpp:20-24, 159-204). Not mirrored to hm_inisettings. Strings are trimmed; 4096-char buffer (350-362).
Key Type Default Clamp/validation Meaning (source LdapSettings.cpp / .h)
Enabled int (≠0 = on) 0 Master switch (229)
Server host "" required Directory host (230; IsComplete 125-145)
Port int 0 1..65535 else derived 0 → 636 for LDAPS, 389 otherwise (231; .h EffectivePort 84-90)
Security int 2 0 plain, 1 StartTLS, anything else → 2 LDAPS (233-247; .h:18-23)
VerifyCertificate int (≠0) 1 (251)
AllowUnprotectedPassword int (≠0) 0 Permit simple bind over plain transport (252)
BindMethod int 0 1 = Negotiate (SSPI Kerberos/NTLM), else Simple (254-255; .h:38-42)
SearchBase DN "" required unless UserDnTemplate set or Negotiate (257; 125-145)
UserSearchFilter LDAP filter (&(objectCategory=person)(objectClass=user)(sAMAccountName=%u)) empty → default (30-32, 258, 265-267)
UserDnTemplate DN template "" Bind directly without searching (259)
ServiceUsername / ServiceDomain / ServicePassword string "" Search-bind credentials (260-262)
TimeoutSeconds int 10 1..90 else 10 (38-42, 269-273)
FallbackToWindowsLogon int (≠0) 0 Fall back to LogonUser (275)
SyncFilter LDAP filter (&(objectClass=user)(objectCategory=person)(mail=*)(!(userAccountControl:1.2.840.113556.1.4.803:=2))) empty → default Directory-sync selection (56-60, 277, 285-286)
SyncMailAttribute attr mail empty → default (62, 278)
SyncUsernameAttribute attr sAMAccountName empty → default (63, 279)
SyncDisplayNameAttribute attr displayName empty → default (64, 280)
SyncMaxUsers int 5000 ≤0 → 5000; >100000 → 100000 (66-72, 297-309)
SyncScheduleMinutes unsigned int 0 0 = off; 1..14 → 15; >10080 → 10080 Read unsigned to avoid INT wrap (76-82, 311-338)
  • Consumers: Common/LDAP/LdapDirectoryAuthenticator.cpp, LdapClient.cpp, DirectorySync.cpp; Control Panel Views/LdapSettingsView.cs:61,715,735 reads/writes [LDAP] directly with a 4096 buffer.

12. Companion file: hMailServerApiKeys.ini (REST API keys)

  • Path: same directory as hMailServer.ini, name hMailServerApiKeys.ini (RestApiServer.cpp:2017-2055; Control Panel Services/ApiKeyStore.cs:81,137).
  • One section per key, [Key.<16 lower-hex id>] with Label, Hash (SHA-256 hex of the token, unsalted, by design), Expires (YYYY-MM-DD HH:MM:SS local, required; missing/unreadable = expired), AllowedFrom (IP, lower-upper, or CIDR; empty = any), Scope (full or readonly; anything else/missing = readonly), Domains (comma list; empty = all) (RestApiServer.cpp:88-111, 2033-2050, 2484-2508).
  • Parsed from the file bytes on every request (no profile cache) so revocation by editor takes effect immediately; written with WritePrivateProfileString, hash last (RestApiServer.cpp:2081-2090, 2512-2531). Default key lifetime 90 days (2417-2424, const at 105). No file = no keys, not an error (2094-2097).
  • No key of any scope can create/revoke keys; that needs the administrator password (preamble 2497-2505).

13. Other readers/writers of hMailServer.ini (for the "who touches this file" page)

  • Server COM layer: InterfaceSettings, InterfaceDatabase, InterfaceDirectories, InterfaceLogging, InterfaceScripting, InterfaceBackupSettings via IniFileSettings (files under COM/).
  • Control Panel hMailCP: Services/IniFeatureStore.cs ([Settings], 2048-char read buffer, direct file when local else COM), Views/FeatureSettingsView.xaml.cs (exposes the keys listed there), Views/LdapSettingsView.cs ([LDAP]), Services/ApiKeyStore.cs, Services/WindowsServiceInfo.cs, Views/BackupView.xaml.cs, Views/DirectorySyncView.cs, Views/QuarantineView.cs, Views/MessageTraceView.cs, Views/ApiKeysView.cs.
  • Installer: installation/section_ini.iss writes [Directories], [GUILanguages], [Security]; hMailServerInnoExtension.iss relocates the file on upgrade.
  • build/hmconfig.ps1 (header says it configures COM objects "and hMailServer.ini"; key-level behaviour not verified here).
  • hmailserver/tools/PasswordDisplayer/Form1.cs:39-42 reads [Database] password.
  • Regression tests: test/RegressionTests/Shared/IniFileSetting.cs, ServerIniFile.cs; 50 distinct keys are written by tests and all 50 exist in the code (checked by diff).
  • Windows service registration (/Register) — section 3 item 5.

14. Error / log codes emitted by the ini layer

  • HM5005 ini check failed (Server/Database missing) — Application.cpp:197-203.
  • HM5528 PreferredHashAlgorithm out of range — IFS.cpp:190.
  • HM5561 PasswordHashIterations, PasswordHashMemoryKB or PasswordHashTimeCost out of range — IFS.cpp ReadPasswordHashWorkFactor_ (read as the default).
  • HM6210 DmarcRptSchemaVersion out of range — IFS.cpp:522.
  • HM6119 SimulateDatabaseFailureFor set (High) — Application.cpp:221.
  • HM4401 invalid DNSServer — DNSResolverWinApi.cpp:261.
  • HM5800/5801 name/value too long for mirror; HM5802 table unreadable; HM5803 DB-side change could not be written to file; HM5804 both-sides conflict; HM5805 restore could not write file; HM5806 written to file but mirror unreadable — ISS.cpp:89, 96, 200, 264, 320, 445, 563.

15. Unconfirmed (not verified in this pass — record as such, do not publish as fact)

  • Exact COM property name that returns the ini path (InterfaceApplication.cpp:436) — check the IDL.
  • Whether the REST API refuses to start when [Security] AdministratorPassword is empty (stated in installer comment hMailServerInnoExtension.iss:277-278; server-side code not read).
  • Whether outbound XOAUTH2 is attempted when OutboundOAuth2Hosts matches but OutboundOAuth2TokenUrl and FixedToken are both empty (ExternalDelivery.cpp not read for this).
  • Whether SslContextInitializer::InitClient (outbound) rebuilds its context per connection or once (affects whether TLS keys are S or S! for outbound).
  • build/hmconfig.ps1 per-key ini behaviour.
  • AcmeDomains/AcmeContactEmail parsing and requiredness inside AcmeClient.cpp.
  • The exact archive sub-path layout under ArchiveDir (only the from-address split at SMTPConnection.cpp:1767-1771 was read).

16. Contradictions and gaps found (docs vs. code)

  1. settings.md §B says "All 104 INI keys read by IniFileSettings.cpp". Today IniFileSettings reads 196 [Settings] keys (195 in LoadSettings + UseLanguage); with the two DAV keys and the [SendingLimits]/[LDAP] sections there are 198 + 4 + 20 keys outside [Database]/[Directories]/[Security]/[GUILanguages]. settings.md is a 6.2.5 snapshot.
  2. settings.md lists Settings.ServiceAccountName/Password under "Intentionally excluded" from the GUI; FeatureSettingsView.xaml.cs now contains ServiceAccountName, ServiceAccountPassword, ServiceAccountGrants, ServiceAccountStatus. The exclusion note is stale.
  3. settings.md names OpenTelemetry as OtelEndpoint, OtelServiceName only; code also has OtelMetricsEndpoint, OtelLogsEndpoint, OtelMetricsInterval (and the GUI exposes them).
  4. Name traps (key ≠ getter/member name — a doc writer copying from headers gets the wrong key): DaneEnforcementEnabled (getter GetDaneEnabled), IndexerFullText (GetIndexerFullTextEnabled), OAuth2PublicKeyFile (GetOAuth2RsaPublicKeyFile), ArchiveHardLinks (GetArchiveHardlinks), AddXOriginalRcptTo (GetAddXOriginalRcptToHeader), GreylistingRecordExpirationInterval (GetGreylistingExpirationInterval), Passwordencryption read vs PasswordEncryption written, Pop3LoginDelaySeconds (lower-case p3 unlike POP3DMinTimeout).
  5. "Inert by default" shapes to flag on feature pages: MtaStsHostingEnabled=1 and AutoconfigEnabled=1 do nothing until WebServicesHttpPort/HttpsPort > 0 (Application.cpp:542-557 logs this); IndexerFullText=1 does nothing unless COM Settings.MessageIndexing is on; WindowsEventLogEnabled=1 writes nothing on a healthy server at level 2; OutboundOAuth2Hosts/FetchOAuth2Hosts have non-empty defaults but need the token settings; [SendingLimits] and [LDAP] are entirely inert until Enabled/limits are set; MetricsServerPort, RestApiPort, ManageSieveServerPort, WebServices*Port all default to 0 = listener not created.
  6. Buffer mismatch: Control Panel reads [Settings] values with a 2048-char buffer (IniFeatureStore.cs:133; ProfileApi.cs:44-48 does not grow it) while the server uses 4096 (IFS.cpp:829) and [LDAP] in the CP uses 4096 — a value of 2049-4096 chars (a long DPAPI secret) would be silently truncated on the CP side.
  7. LogFolder and UseLanguage use 255-char buffers (IFS.cpp:885, 937) while everything else uses 4096.
  8. The hm_inisettings reconciliation compares key names case-sensitively while the Windows ini API is case-insensitive; a case-only rename of a key causes a one-start insert collision that self-heals (ISS.cpp:206-222) — documented in code, not in settings.md.
  9. DNSServer accepts a single IPv4 address only; docs must not suggest host:port or IPv6 (DNSResolverWinApi.cpp:255-291 and memory note "DNS custom-server regression").
  10. No ini key named in hmailserver/docs/*.md or the READMEs is absent from the code (token diff against the 243-key list found only WebServicesServer, a class name).

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