Repository navigation
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.Reinitializedoes. - L — read live from the file by its consumer, not through LoadSettings (own cache rules noted).
- File name is literally
hMailServer.ini, in the server's Bin directory (source: IFS.cpp:863-878GetInitializationFile). - The Bin directory is
HKLM\SOFTWARE\hMailServer\InstallLocation+\Binif 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.iniinto{app}\Bin(source:installation/hMailServerInnoExtension.iss:1043-1055). - COM
Application.<ini path>exposes the path:InterfaceApplication.cpp:436returnsGetInitializationFile()(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).
- 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, butKey=(present, empty) reads as 0 throughGetPrivateProfileInt(source: IFS.h:81-85; ISS.cpp:463-467); a value such as5xreads as 5 andxas 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] LogFolderis read with a 255-char buffer (IFS.cpp:885-886) and[Settings] UseLanguagewith 255 (IFS.cpp:937-939). - Booleans are
ReadIniSettingInteger_(...) == 1, so only the literal1is true;2,yes,trueare false (source: every== 1in 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).
-
LoadSettings()reads every key in sections 5-9 below into typed members in one pass (source: IFS.cpp:90-679). It runs:- First thing in
Application::InitInstance, file-only (source:Common/Application/Application.cpp:132). - 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).
- On
Application::Reinitialize(COMApplication.Reinitialize,InterfaceApplication.cpp:93-100), which callsForgetDatabaseSettings()then InitInstance again (source: Application.cpp:924-942). - On every COM
Settings.Directoriesget (source:COM/InterfaceSettings.cpp:1129) and onDatabase.CreateInternalDatabase(source:COM/InterfaceDatabase.cpp:488) — these re-latch all ~200 members from the file (+overlay) on a COM thread. - On
hMailServer.exe /Register(no database) soServiceAccountName/Passwordreach the SCM (source:hMailServer/hMailServer.cpp:216-249).
- First thing in
- 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
SetIniSettingcalls LoadSettings. - Keys that are NOT latched but read live:
UseLanguage(every call, IFS.cpp:934-942),LogFolder(lazily once, then cached untilSetLogDirectory, 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), andhMailServerApiKeys.ini(every request; section 12). -
LoadSettingsends by callingDiskSpace::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_Minutes0 vs 6,add_xauth_user_ip_false vs 1 (source: IFS.cpp:23-82 vs 144-398).WindowsEventLogEnabled/Levelare deliberately identical in both (IFS.cpp:74-78).
-
LoadDatabaseSettings()reconciles[Settings]with tablehm_inisettings(columnsinisettingname≤100 chars,inisettingvalue≤4000,inisettingfilevalue) viaIniSettingStore::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 passIsStorableName(non-empty, ≤100, no= [ ]or line break, no surrounding whitespace); value must passIsStorableValue(≤4000, no line break) (ISS.cpp:392-426).SetIniSettingwrites the FILE first and fails without changing anything if that fails (ISS.cpp:428-460, HM5806 if the mirror cannot be read afterwards).DeleteIniSettingremoves the line (notKey=) 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
GetIniSettingover COM otherwise (source:Tools/ControlPanel/Services/IniFeatureStore.cs:120-140;Services/ServerSession.cs:88-101).
| 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).
| 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).
| 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).
| 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 |
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.
| 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 |
| 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 |
| 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 |
| 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! |
| 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 |
| 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! |
| 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 |
| 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 |
"" | 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 |
"" | 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 |
| 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 |
| 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 | |
SmtpAuthenticatedSenderCheck |
0/1 | 0 | Added 5 Sep 2026: with 1, an authenticated SMTP session's MAIL FROM must be the account's own address, an alias resolving to it, or another account's address whose owner granted the account the post (p) right on their INBOX (Send-As); otherwise 550 5.7.1 at MAIL FROM (SMTPConnection::AuthenticatedSenderPermitted_). Null sender always allowed; unauthenticated sessions unaffected |
SMTPConnection.cpp | S | |
MetricsHistoryDays |
int days | 7 | negative -> 0 | Added 5 Sep 2026: MetricsHistoryTask writes one row per metric per minute to hm_metricsamples (schema 6028) and prunes rows older than this hourly; 0 turns the sampler off. Read back by Utilities.GetMetricHistory, GET /api/v1/metrics/history and the dashboard's range selector |
MetricsHistoryTask.cpp, RestApiServer.cpp, InterfaceUtilities.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! |
| 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) |
| 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 |
| 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) |
- Read by
RateLimiter::LoadSettings_directly withGetPrivateProfileString/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).
- 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 PanelViews/LdapSettingsView.cs:61,715,735reads/writes[LDAP]directly with a 4096 buffer.
- Path: same directory as hMailServer.ini, name
hMailServerApiKeys.ini(RestApiServer.cpp:2017-2055; Control PanelServices/ApiKeyStore.cs:81,137). - One section per key,
[Key.<16 lower-hex id>]withLabel,Hash(SHA-256 hex of the token, unsalted, by design),Expires(YYYY-MM-DD HH:MM:SSlocal, required; missing/unreadable = expired),AllowedFrom(IP,lower-upper, or CIDR; empty = any),Scope(fullorreadonly; 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).
- Server COM layer:
InterfaceSettings,InterfaceDatabase,InterfaceDirectories,InterfaceLogging,InterfaceScripting,InterfaceBackupSettingsviaIniFileSettings(files underCOM/). - 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.isswrites [Directories], [GUILanguages], [Security];hMailServerInnoExtension.issrelocates 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-42reads[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.
- 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.
- Exact COM property name that returns the ini path (InterfaceApplication.cpp:436) — check the IDL.
- Whether the REST API refuses to start when
[Security] AdministratorPasswordis empty (stated in installer comment hMailServerInnoExtension.iss:277-278; server-side code not read). - Whether outbound XOAUTH2 is attempted when
OutboundOAuth2Hostsmatches butOutboundOAuth2TokenUrlandFixedTokenare 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.ps1per-key ini behaviour. -
AcmeDomains/AcmeContactEmailparsing 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).
-
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. -
settings.mdlistsSettings.ServiceAccountName/Passwordunder "Intentionally excluded" from the GUI;FeatureSettingsView.xaml.csnow containsServiceAccountName,ServiceAccountPassword,ServiceAccountGrants,ServiceAccountStatus. The exclusion note is stale. -
settings.mdnames OpenTelemetry asOtelEndpoint,OtelServiceNameonly; code also hasOtelMetricsEndpoint,OtelLogsEndpoint,OtelMetricsInterval(and the GUI exposes them). - Name traps (key ≠ getter/member name — a doc writer copying from headers gets the wrong key):
DaneEnforcementEnabled(getterGetDaneEnabled),IndexerFullText(GetIndexerFullTextEnabled),OAuth2PublicKeyFile(GetOAuth2RsaPublicKeyFile),ArchiveHardLinks(GetArchiveHardlinks),AddXOriginalRcptTo(GetAddXOriginalRcptToHeader),GreylistingRecordExpirationInterval(GetGreylistingExpirationInterval),Passwordencryptionread vsPasswordEncryptionwritten,Pop3LoginDelaySeconds(lower-case p3 unlikePOP3DMinTimeout). - "Inert by default" shapes to flag on feature pages:
MtaStsHostingEnabled=1andAutoconfigEnabled=1do nothing untilWebServicesHttpPort/HttpsPort> 0 (Application.cpp:542-557 logs this);IndexerFullText=1does nothing unless COMSettings.MessageIndexingis on;WindowsEventLogEnabled=1writes nothing on a healthy server at level 2;OutboundOAuth2Hosts/FetchOAuth2Hostshave non-empty defaults but need the token settings;[SendingLimits]and[LDAP]are entirely inert untilEnabled/limits are set;MetricsServerPort,RestApiPort,ManageSieveServerPort,WebServices*Portall default to 0 = listener not created. - 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. -
LogFolderandUseLanguageuse 255-char buffers (IFS.cpp:885, 937) while everything else uses 4096. - 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.
-
DNSServeraccepts a single IPv4 address only; docs must not suggesthost:portor IPv6 (DNSResolverWinApi.cpp:255-291 and memory note "DNS custom-server regression"). - No ini key named in
hmailserver/docs/*.mdor the READMEs is absent from the code (token diff against the 243-key list found onlyWebServicesServer, a class name).
hMailServer 6.3.8, the latest release (2 November 2026, schema 6108) · AGPL-3.0-or-later · Repository · Mirror on GitHub · Report a documentation error (a GitLab issue naming the page)
Home — full index
Start here
1. Install and run
- Before You Install
- Installing hMailServer
- Installing on Linux
- Running in a Container
- Profiles and Environment
- The Control Panel
- The Control Deck
- Your First Domain and Mailbox
- Connecting a Mail Client
- The Webmail
- DNS for Your Domain
- Migration
2. Secure it
- Encryption and Certificates
- Database TLS
- Encryption for People
- Stopping Spam
- Stopping Viruses
- Security Hardening
- Secure Defaults
- The Service Account
- Secrets at Rest
- Sign-in and Identity
- Security Policy
3. Operate it
- Monitoring and Health
- The SNMP Agent
- Backup and Restore
- Message Storage
- Compliance and eDiscovery
- Troubleshooting
- Diagnosing Stalled Mail
- Relocating an Installation
- Upgrading hMailServer
- Upgrading Guide
- The Update Feed
- Migrating the Database Backend
- High Availability Runbook
- Warm Standby
- Runbooks Digest
4. Extend it
- Rules and Sieve
- Aliases Lists and Public Folders
- Rooms and Equipment
- Mailing Lists
- Routes and Relays
- Mail In to a Webhook
- Outbound Shaping
- The COM API and Scripting
- Event Scripts
- The REST API
- Command Line
- APIs Reference
5. Contribute to it
- Project Handbook
- Architecture
- Contributing
- Release Process
- Governance
- Assurance Case
- Regression Test Environment
- Fuzzing
- Regulatory Scope
- Third-Party Binaries
Look it up — from any journey
- Settings Reference
- Settings Overview
- Capabilities and Configuration
- Control Panel Reference
- Control Panel Pages
- Ports Reference
- Changes-Since-6210
- Version-History-to-6210
- Glossary
About this wiki
- hMailServer 6.3.8 is the latest release (2 November 2026, schema 6108)
- Pages say since 6.3.4 where a feature is new in it
- AGPL-3.0-or-later
- Repository, the one place the project is worked on
- Its mirror on GitHub, since 18 October 2026, with no Issues
- Questions: a GitLab issue with the Question template, GitHub Discussions Q&A, or the forum
- Report a documentation error: a GitLab issue naming the page