-
Notifications
You must be signed in to change notification settings - Fork 3
Capabilities and Configuration
The capability inventory as the server advertises it today (every SMTP, IMAP, POP3 and ManageSieve keyword), transport security, sender authentication, operations, and the configuration reference checked key by key against the code. File and line references point into the repository at the commit this page was written from (6.2.24, master of 4 September 2026); the passages marked 6.2.25, 6.2.27 or "new in 6.2.28" were re-checked against master 40ae9491d on 8 September 2026 and their line numbers are from that tree; the only change after it is the HttpProxy line at IniFileSettings.cpp:492 in the tree 6.2.28 was cut from, which moves what follows it down by one. Sections headed Unconfirmed or Contradictions record what could not be verified or where documents disagreed, and are left in on purpose.
Scope: README.md (581 lines) at repo root, checked line by line against the server source under hmailserver/source/Server, the installer under hmailserver/installation, the DB scripts, the Tools solutions and .github/workflows. All paths below are relative to c:/Users/chris/Documents/projects/hmailserver/; Server/ means hmailserver/source/Server/. "README:N" is a README line number.
Method note for page writers: [Settings] keys are read through IniFileSettings::ReadIniSettingString_/Integer_, which first consults a database-reconciled overlay and only then hMailServer.INI; every OTHER section ([Database], [Directories], [Security], [LDAP], [GUILanguages]) is read from the file only (source: Server/Common/Application/IniFileSettings.cpp:803-833). The [LDAP] section has its own reader (Server/Common/LDAP/LdapSettings.cpp:17,357,368).
The annotations throughout are not editorial noise; they are the finding of a line-by-line comparison between the README's claims and the code, and each one tells you which document to trust.
| Marker | Meaning | What to do with it |
|---|---|---|
| CONFIRMED | The README says it and the code does it | Nothing. Cite either |
| GAP | The code does it and the README does not say so, or a setting exists with no README entry | Trust the code and the Settings Reference. This is the largest category, and most of it is settings added faster than the README was updated |
| UNCONFIRMED | A claim that cannot be checked from the source tree — usually a statement about testing against external infrastructure | Treat as a claim, not a fact |
| Contradiction | Two documents disagree, or a document disagrees with the code | The code wins. Section 13 lists them |
The reason this page exists in this shape is a specific failure recorded in the project's own history: the August 2026 audit found fifteen overclaims, two of which had already shipped in release notes. The failure shape was always the same — a capability that exists in the code but is inert by default. So this page records what is advertised and enabled, not what is compiled in.
- Product: hMailServer, SMTP/IMAP/POP3 server for Windows, maintained fork by Christopher Holloway / Progressive Robot Ltd (source: README:7-9)
- Version stamp is 6.2.28, build 38 (source: Server/Common/Application/Version.h:5-7
HMAILSERVER_VERSION "6.2.28",HMAILSERVER_BUILD 38; hmailserver/installation/section_setup_64.iss:6,10AppVersion=6.2.28,VersionInfoVersion=6.2.28.0; the installer ishMailServer-6.2.28-x64.exe). Published releases since this page was first written: v6.2.25 (6 Sep 2026), v6.2.26 (6 Sep 2026), v6.2.27 (7 Sep 2026), v6.2.28 (8 Sep 2026, latest). Work that reached a published release for the first time in v6.2.28 is marked "new in 6.2.28" where it appears below. - Required database schema version is 6031 (source: Server/Common/Application/Constants.h:173
#define REQUIRED_DB_VERSION 6031; DBScripts/CreateTablesMSSQL.sql:1213insert into hm_dbversion values (6031); last DBUpdater stepnew UpgradeScript(6030, 6031), Tools/DBUpdater/formMain.cs:242). README:110 says "Database version 6027" — STALE (6.2.25 took the schema to 6030, 6.2.27 to 6031). - Schema history 6006-6010 documented in the same header: 6006 folderspecialuse; 6007 secondary DKIM selector/key; 6008 per-port client-certificate policy; 6009 encrypted private-key passphrase column; 6010 seeds
ASArcFilteringEnabled(0) andASArcTrustedSealers('') (source: Constants.h:157-175). Later bumps (6013 SAVEDATE column, 6014 hm_imap_metadata) are referenced from IMAPCommandCapability.cpp:142-149. - Later bumps still: 6026 seeds
hm_settingsrowCreateDefaultSpecialUseFolders(0); 6027 addshm_domains.domainmessageretentiondaysandhm_accounts.accountmessageretentiondays(0 = no policy); 6028 createshm_metricsamples; 6029 createshm_archiveindex; 6030 adds seventeenFOREIGN KEY ... ON DELETE CASCADEconstraints after deleting the orphan rows they would refuse (all five shipped in 6.2.25); 6031 addshm_fetchaccounts.famirrorfolders(6.2.27) (source: DBScripts/Upgrade6025to6026MSSQL.sql through Upgrade6030to6031MSSQL.sql, with MSSQLCE, MySQL and PGSQL variants). - Installer is a single x64 Inno Setup exe;
ArchitecturesAllowed=x64,ArchitecturesInstallIn64BitMode=x64(source: section_setup_64.iss:4-5) - License AGPL-3.0-or-later (source: SPDX header on every source file, e.g. Server/IMAP/IMAPCommandCapability.cpp:4;
LICENSEexists at repo root)
Unconditional, in this order:
-
SIZE <n>(n = MaxMessageSize KB x 1024; bareSIZEwhen 0) (2453-2461) -
8BITMIME(2464) -
PIPELINING(2469) -
CHUNKING— BDAT, RFC 3030 (2473) -
BINARYMIME— RFC 3030; local delivery only: a recipient needing onward relay is refused at RCPT TO with 554 5.6.3 (2474-2481, 1322); DATA is refused with BODY=BINARYMIME (3123). README:34 does NOT list BINARYMIME — GAP. -
SMTPUTF8(2484) -
ENHANCEDSTATUSCODES(2487) -
DSN— RET/ENVID on MAIL, NOTIFY/ORCPT on RCPT, NOTIFY=NEVER honoured (2489-2491) -
HELPas the terminating line (2555)
Conditional:
-
XCLIENT ADDR NAME PORT PROTO HELO LOGINonly whenXClientPermitted_()(real TCP peer is inSMTPXClientTrustedIPs); untrusted peers never see it and get 550 if they try (2493-2499) -
STARTTLSon a non-TLS connection when the port is STARTTLS-optional or -required (2501-2507) -
AUTH LOGIN [PLAIN] SCRAM-SHA-256 [SCRAM-SHA-256-PLUS] [XOAUTH2 OAUTHBEARER]only when AUTH is enabled and (TLS, or cleartext auth is not refused). PLAIN requires the SMTP "allow plain text" setting; SCRAM-SHA-256 is always offered when AUTH is;-PLUSonly on TLS; XOAUTH2/OAUTHBEARER only whenOAuth2Enabled=1and (OAuth2RequireTLS=0or TLS) (2525-2553);EXTERNALonly when the connection's client certificate verified against the port's CA and names an address (shipped in 6.2.25) - AUTH is withheld entirely on cleartext when the port is STARTTLS-required OR the IP range has RequireTLSForAuth (2525-2530)
- AUTH and the SASL continuation lines that follow it accept up to 12288 octets (RFC 5034); every other command line keeps 510 (source: SMTPConnection.cpp:476-483
maxLength = saslLine ? 12288 : 510; POP3Connection.cpp:295-299 for POP3, 500 otherwise) — 6.2.25 - MaxSubmissionsPerIPPerMinute exceeded ->
421 Too many messages from your IP address...(source: SMTPConnection.cpp:736) - Acceptance deadline exceeded after end-of-data -> temporary
451and the message is not accepted (source: SMTPConnection.cpp:2020);FinalizationTimeoutdefault 240 s is the deadline (IniFileSettings.cpp:281) - Legacy HELO sessions get plain non-enhanced replies (README:495; SMTPConnection.cpp:1227 comment) — consistent
- HAProxy PROXY protocol v1/v2 accepted only when
SMTPProxyProtocolEnabled=1and the peer is inSMTPProxyProtocolTrustedIPs(README:395-404; IniFileSettings.cpp:620-621). PROXY parsing code itself not read for this fact base — the setting names and defaults are confirmed.
Always: IMAP4 IMAP4rev1 IMAP4rev2 CHILDREN (24), THREAD=ORDEREDSUBJECT THREAD=REFERENCES (42), NAMESPACE RIGHTS=texk MOVE ID SPECIAL-USE CREATE-SPECIAL-USE UNSELECT UIDPLUS ENABLE STATUS=SIZE ESEARCH CONDSTORE QRESYNC LIST-EXTENDED SEARCHRES WITHIN UTF8=ACCEPT (114), LIST-STATUS (118), PREVIEW (122), MULTIAPPEND (126), REPLACE (130), SAVEDATE (135), METADATA (139), OBJECTID (144), BINARY (148), LITERAL- (163), APPENDLIMIT (bare before auth, APPENDLIMIT=<bytes> after) (170-181)
Gated by database settings (hm_settings rows, all seeded 1 = on): IDLE (enableimapidle), QUOTA QUOTA=RES-STORAGE (enableimapquota), SORT (enableimapsort), ACL (enableimapacl) (source: IMAPCommandCapability.cpp:28-46; hmailserver/source/DBScripts/CreateTablesMSSQL.sql:910-918)
Connection-dependent: STARTTLS (49-51); LOGINDISABLED when auth is refused on cleartext (76-77); AUTH=PLAIN, AUTH=SCRAM-SHA-256 when EnableImapSASLPlain is on and auth is available (79-91); AUTH=SCRAM-SHA-256-PLUS only on TLS (96-97); AUTH=XOAUTH2 AUTH=OAUTHBEARER when OAuth2 enabled (101-103); AUTH=EXTERNAL when auth is available and the client certificate verified against the port's CA and names an address (shipped in 6.2.25); SASL-IR when EnableImapSASLInitialResponse (105-106); UNAUTHENTICATE only once authenticated (155-156); COMPRESS=DEFLATE (RFC 4978) when IMAPCompressionEnabled=1 (the default) and the session is not yet compressed, withdrawn once COMPRESS is active — both directions of the session compressed on request (source: IMAPCommandCapability.cpp:61-62; IniFileSettings.cpp:497; Server/IMAP/IMAPCommandCompress.cpp; zlib 1.3.1 vendored under Server/zlib). New in 6.2.28.
-
EnableImapSASLPlainandEnableImapSASLInitialResponseare hm_settings rows seeded 0 (source: CreateTablesMSSQL.sql:1037-1039). This is the "one setting whose shipped default is off" in README:66 — CONFIRMED. Note it gates SCRAM on IMAP too, not only PLAIN (IMAPCommandCapability.cpp:79-97). -
ENABLE IMAP4rev2switches the session to rev2 semantics and implies UTF8=ACCEPT;ENABLE UTF8=ACCEPTalone is also honoured (source: Server/IMAP/StaticIMAPCommandHandlers.cpp:203-218) - rev2 deltas implemented: RECENT/\Recent removed, [UNSEEN] removed on SELECT/EXAMINE, ESEARCH-by-default SEARCH, STATUS RECENT removed (source: IMAPCommandSelect.cpp:134,148; IMAPCommandExamine.cpp:121,135; IMAPCommandSearch.cpp:344; IMAPCommandStatus.cpp:105)
- README:35 now lists LIST-STATUS, non-synchronising literals and BINARY as implemented, matching the code (LIST-STATUS:141, BINARY:171, LITERAL-:187 in the current tree). LITERAL- rather than LITERAL+ is deliberate (4096-byte non-sync cap) (181-187). The earlier contradiction is resolved.
- README:35 also omits the following advertised capabilities: CHILDREN, RIGHTS=texk, UNSELECT, ENABLE, STATUS=SIZE, LIST-EXTENDED, WITHIN, UTF8=ACCEPT, PREVIEW, MULTIAPPEND, REPLACE, SAVEDATE, METADATA, OBJECTID, UNAUTHENTICATE, APPENDLIMIT, QUOTA=RES-STORAGE, LOGINDISABLED, SASL-IR — GAP.
- IMAP SEARCH bounded by
IMAPSearchTimeout=60s andIMAPSearchMaxMegabytes=2048(source: IniFileSettings.cpp:347-349) - Expunge history for QRESYNC/CONDSTORE pruned to
IMAPExpungeRetentionRecords=5000, task runs every 12 x 60 minutes (source: IniFileSettings.cpp:646; Server/Common/Application/Application.cpp:679SetMinutesBetweenRun(12 * 60))
- Always:
UIDL,TOP(440),RESP-CODES(485),AUTH-RESP-CODE(493),IMPLEMENTATION hMailServer(499),PIPELINING(508),UTF8(511),EXPIRE NEVER(520) - Conditional:
SASL PLAIN SCRAM-SHA-256 SCRAM-SHA-256-PLUSon TLS,SASL PLAIN SCRAM-SHA-256on cleartext when permitted (461-463), plusEXTERNALwhen the client certificate verified against the port's CA and names an address (shipped in 6.2.25);STLSwhen applicable (480);LOGIN-DELAY <n>whenPop3LoginDelaySeconds> 0 (532) -
Pop3LoginDelaySeconds=0default (source: IniFileSettings.cpp:619) — not in README (GAP) - External account fetching exists over POP3 and, since 6.2.25, IMAP:
FetchAccount.ServerType0 = POP3, 1 = IMAP (the INBOX collected once by UID, LOGIN or XOAUTH2, STARTTLS or TLS; left on the server unlessDaysToKeepMessagessays otherwise);FetchAccount.MirrorFolders(6.2.27, schema 6031) makes an IMAP account collect every remote folder into a local folder of the same name, byte for byte with flags and internal date, filed rather than delivered — the imapsync-shaped migration route.MaxNumberOfExternalFetchThreads=15(source: hMailServer.idl:1854,1889; Server/Common/BO/FetchAccount.h:21-24; Server/ExternalFetcher/IMAPClientConnection.h:32, ExternalFetchClientBase.h:27; DBScripts/Upgrade6030to6031MSSQL.sql; IniFileSettings.cpp:175) — README:36 now says the same, CONFIRMED.
- Commands handled: LOGOUT, CAPABILITY, NOOP, STARTTLS, AUTHENTICATE, UNAUTHENTICATE, LISTSCRIPTS, PUTSCRIPT, GETSCRIPT, SETACTIVE, DELETESCRIPT, RENAMESCRIPT, CHECKSCRIPT, HAVESPACE (1164-1489)
- Capability response:
"IMPLEMENTATION" "hMailServer ManageSieve","SIEVE" "<extensions>","STARTTLS"only pre-handshake with a certificate,"SASL" "PLAIN"(or"SASL" ""when cleartext auth is refused),"UNAUTHENTICATE","VERSION" "1.0"(909-946) - Only SASL mechanism on ManageSieve is PLAIN (934) — no SCRAM there.
- README:468 lists the commands but omits RENAMESCRIPT, UNAUTHENTICATE, NOOP, LOGOUT — GAP.
- Listener off by default:
ManageSieveServerPort=0,ManageSieveServerBindAddress=127.0.0.1(source: IniFileSettings.cpp:453-454); standard port 4190 is README's statement (README:461), not enforced by code.
The unconditional keywords are the same on every connection. The conditional ones are the interesting part, because a keyword that is absent is the visible symptom of a configuration decision made somewhere else entirely — and "my client will not authenticate" is nearly always this diagram rather than a bug.
flowchart TD
CONN["A connection reaches EHLO"] --> UNCOND["Always advertised:<br/>SIZE, 8BITMIME, PIPELINING, CHUNKING,<br/>BINARYMIME, SMTPUTF8, ENHANCEDSTATUSCODES, DSN<br/>- and HELP as the terminating line"]
UNCOND --> X{"Is the real TCP peer in<br/>SMTPXClientTrustedIPs?"}
X -- yes --> XADV["XCLIENT ADDR NAME PORT PROTO HELO LOGIN"]
X -- no --> XNO["No XCLIENT line at all.<br/>An untrusted peer that tries it gets 550"]
XADV --> T
XNO --> T{"Already on TLS?"}
T -- yes --> TNO["No STARTTLS - it would be meaningless"]
T -- no --> T2{"Is the port STARTTLS-optional<br/>or STARTTLS-required?"}
T2 -- yes --> TADV["STARTTLS"]
T2 -- "no, plain port" --> TNO
TADV --> A
TNO --> A{"Is SMTP AUTH enabled?"}
A -- no --> ANONE["No AUTH line"]
A -- yes --> A2{"On cleartext, is the port<br/>STARTTLS-required, or does the<br/>IP range set RequireTLSForAuth?"}
A2 -- yes --> ANONE2["AUTH withheld ENTIRELY on cleartext.<br/>This is the one that looks like a bug<br/>and is not"]
A2 -- no --> LIST["AUTH LOGIN<br/>+ PLAIN if the SMTP 'allow plain text'<br/>setting is on<br/>+ SCRAM-SHA-256, always when AUTH is<br/>+ SCRAM-SHA-256-PLUS only on TLS<br/>+ XOAUTH2 OAUTHBEARER when OAuth2Enabled=1<br/>and (OAuth2RequireTLS=0 or TLS)<br/>+ EXTERNAL when the client certificate verified<br/>against the port's CA and names an address"]
Two line-length facts that belong beside that diagram, because they are the cause of
a failure that looks like a truncation bug: AUTH and the SASL continuation lines
that follow it accept up to 12,288 octets (RFC 5034); every other SMTP command line
keeps 510, and POP3 keeps 500. A long base64 token is therefore fine on the AUTH line
and refused anywhere else.
| Listener | Advertised unconditionally | The conditional part, and what gates it |
|---|---|---|
| IMAP |
IMAP4 IMAP4rev1 IMAP4rev2 CHILDREN, both THREAD= variants, NAMESPACE RIGHTS=texk MOVE ID SPECIAL-USE CREATE-SPECIAL-USE UNSELECT UIDPLUS ENABLE STATUS=SIZE ESEARCH CONDSTORE QRESYNC LIST-EXTENDED SEARCHRES WITHIN UTF8=ACCEPT LIST-STATUS PREVIEW MULTIAPPEND REPLACE SAVEDATE METADATA OBJECTID BINARY LITERAL- APPENDLIMIT
|
IDLE, QUOTA, SORT and ACL are database settings (enableimapidle, enableimapquota, enableimapsort, enableimapacl), all seeded on. AUTH=PLAIN and AUTH=SCRAM-SHA-256 need EnableImapSASLPlain, which is seeded off — and note it gates SCRAM too, not only PLAIN. SASL-IR needs EnableImapSASLInitialResponse, also seeded off. COMPRESS=DEFLATE appears while IMAPCompressionEnabled=1 and the session is not yet compressed, and is withdrawn once it is |
| POP3 |
UIDL TOP RESP-CODES AUTH-RESP-CODE IMPLEMENTATION PIPELINING UTF8, EXPIRE NEVER
|
SASL mechanisms depend on TLS; STLS on the port's security; LOGIN-DELAY <n> only when Pop3LoginDelaySeconds > 0 |
| ManageSieve |
IMPLEMENTATION, SIEVE, VERSION "1.0", UNAUTHENTICATE
|
STARTTLS only pre-handshake and only with a certificate; SASL "PLAIN" — PLAIN is the only mechanism here, there is no SCRAM on ManageSieve. The whole listener is off until ManageSieveServerPort is non-zero |
EnableImapSASLPlain seeded 0 is the one shipped default that is off where a
reader might expect on. It is deliberate, and it is why an IMAP client configured for
"encrypted password" can fail against a stock install until you turn it on.
- TLS versions are a bitmask
SslVersionsin hm_settings: TLS1.0=2, 1.1=4, 1.2=8, 1.3=16; seeded value 24 = TLS 1.2 + 1.3 (source: Server/Common/TCPIP/SocketConstants.h:69-75; CreateTablesMSSQL.sql:1033) — README:42 "TLS 1.2 and 1.3 by default" CONFIRMED. -
TlsKeyExchangeGroupsdefaultX25519MLKEM768:SecP256r1MLKEM768:X25519:secp384r1:secp256r1(source: IniFileSettings.cpp:572) — hybrid PQ first, CONFIRMED. -
TlsCipherSuites13default empty = OpenSSL defaults; TLS<=1.2 list is the DB settingSslCipherList(source: IniFileSettings.cpp:578; Constants.h:139; CreateTablesMSSQL.sql:1031 seeds the legacy long list) -
AEAD-ONLYpreset: settingSslCipherListto exactly that name (case-insensitive) expands to 9 ECDHE/DHE GCM and CHACHA20 suites; misspellings are reported as error 5511, not ignored (source: Server/Common/TCPIP/SslContextInitializer.cpp:649-690) - Session tickets/cache/timeout/rotation:
TlsSessionTicketsEnabled=1,TlsSessionCacheSize=0,TlsSessionTimeoutSeconds=0,TlsTicketKeyRotationSeconds=0(source: IniFileSettings.cpp:584-588) -
MtaStsEnabled=1,DaneEnforcementEnabled=1,DnssecValidationEnabled=1,DnssecTrustAnchors=""(source: IniFileSettings.cpp:402-405) - DANE validates the MX RRset under DNSSEC before applying TLSA to a host; an MX chain that fails validation skips that host (RFC 7672 s2.2) (source: Server/SMTP/ExternalDelivery.cpp:251-320; resolver in Server/Common/TCPIP/DnssecResolver.cpp) — README:44 CONFIRMED.
- DNSSEC denial of existence (6.2.27): a DS query answered with nothing is no longer treated as an unsigned delegation; the resolver keeps the authority section and requires the parent's NSEC (NS set, DS clear), matching NSEC3, or Opt-Out NSEC3 proof, signed by the parent's key; a delegation under a signed parent without such a proof is Bogus, an unsigned parent still makes everything below Insecure.
Diagnostics.DnssecChainStatusexposes the verdict (0 secure, 1 insecure, 2 bogus) (source: Server/Common/TCPIP/DnssecResolver.cpp, nine NSEC3 sites; hMailServer.idl:3223). - TLS-RPT:
TlsRptFromAddress=""(off),TlsRptOrganizationName=hMailServer; with the address empty the task logs that statistics are collected but never sent (source: IniFileSettings.cpp:483-484; Server/SMTP/TlsRptReporterTask.cpp:52) — README:49 CONFIRMED. - ACME:
AcmeEnabled=0,AcmeDirectoryUrl=https://acme-v02.api.letsencrypt.org/directory,AcmeContactEmail="",AcmeDomains="",AcmeHttpPort=80,AcmeReuseKey=1, plusAcmeCertificateDirectory=""(not in README) which defaults to<DataFolder>\ACME(source: IniFileSettings.cpp:595-601; Server/Common/Util/AcmeClient.cpp:248-256) - ACME applies the certificate as an SSL certificate record named "ACME (automatic)" (
fullchain.pem/privkey.pem), assigns it to TLS-enabled ports with no certificate, then restarts the TCP servers to load it — the Windows service itself is not restarted (source: AcmeClient.cpp:1524-1592) — README:50,342 CONFIRMED. - ACME (6.2.25, #93): the TLSA-line and key-reuse paths read files through OpenSSL's own BIOs rather than a CRT
FILE*— 6.2.24 terminated the process via the missingOPENSSL_Applinkat every issuance and renewal; deployment now runs before the TLSA line, and a certificate pair underData\ACMEthat no "ACME (automatic)" record names is deployed at the first check after start ("issued but never deployed" in the log) (source: AcmeClient.cpp:85,316,768BIO_new_file; :1581). - Encrypted private-key passphrase column
hm_sslcertificates.sslprivatekeypassword(schema 6009) (source: Constants.h:167-169) — README:45 CONFIRMED by schema comment only. - Per-port inbound client-certificate policy exists (schema 6008;
InterfaceTCPIPPortClientCertificatePolicy) (source: Constants.h:163-166; Server/COM/InterfaceTCPIPPort.cpp) — NOT in README (GAP). - SNI: symbol hits in Server/Common/TCPIP/TCPConnection.cpp and Server/SMTP/SMTPClientConnection.h only; not read in depth — treat README:42 "with SNI" as plausible, unconfirmed here.
- DKIM
a=tag:ed25519-sha256,rsa-sha256, orrsa-sha1(source: Server/Common/AntiSpam/DKIM/DKIM.cpp:372);DkimAcceptSha1=0default refuses SHA-1 on verification (IniFileSettings.cpp:642) - DKIM
t=is always emitted andx=whenDKIMSignatureValiditySeconds> 0 (source: DKIM.cpp:1284" t=%I64d; x=%I64d;");DKIMSignatureValiditySeconds=0,DKIMEnforceSignatureExpiry=1,DKIMExpiryClockSkewSeconds=300,DkimOversignHeaders=""(IniFileSettings.cpp:464-474) — README:56 CONFIRMED. - ARC sealing
ArcSealingEnabled=0(IniFileSettings.cpp:455); ARC seal algorithms rsa-sha256 / ed25519-sha256 (Server/Common/AntiSpam/DKIM/Arc.cpp:129) - Inbound ARC filtering is NOT an INI key:
ASArcFilteringEnabledandASArcTrustedSealersare hm_settings rows seeded 0 / '' and exposed as COMAntiSpam.ArcTrustedSealers(source: Constants.h:118-119; CreateTablesMSSQL.sql:995-997; Server/COM/InterfaceAntiSpam.cpp:1237-1261; Server/Common/AntiSpam/DKIM/SpamTestArc.cpp:275). README:57 names the setting without saying where it lives. - DMARC organisational domain from a compiled-in Public Suffix List (source: Server/Common/AntiSpam/DMARC/PublicSuffixList.cpp, PublicSuffixListData.h) — README:55 CONFIRMED.
DmarcTreeWalkEnabled=1(IniFileSettings.cpp:639) — not in README. - DMARC aggregate reports:
DmarcRptFromAddress=""(off),DmarcRptOrganizationName=hMailServer,DmarcRptSchemaVersion=1(2 = RFC 9990; other values -> error 6210 and 1) (source: IniFileSettings.cpp:490-527). README config lists only DmarcRptSchemaVersion — the sender address key is not in README (GAP). -
SpfVoidLookupLimit=2(IniFileSettings.cpp:640) — not in README. - SRS:
SRSEnabled=0,SRSSecret=""; addresses valid forkMaxAgeDays = 21(source: IniFileSettings.cpp:614-615; Server/Common/Util/SRS.cpp:24) — README:497 CONFIRMED. LegacyRewriteEnvelopeFromWhenForwarding=0still exists (IniFileSettings.cpp:613). - BATV:
BATVEnabled=0,BATVSecret="";prvs=implementation in Server/Common/Util/BATV.cpp (source: IniFileSettings.cpp:616-617). README:58 mentions BATV but the config section omits both keys (GAP). - SpamAssassin, DNSBL, SURBL, greylisting, HELO host check, PTR check, MX check all present (source: Server/COM/InterfaceAntiSpam.cpp; Server/COM/InterfaceDNSBlackLists.cpp; Server/Common/AntiSpam/SpamTestHeloHost.cpp) — README:59 CONFIRMED in outline.
- SpamAssassin per-user preferences and learning (6.2.25):
SpamAssassinUser="",SpamAssassinUserFromRecipient=0tell spamd whose preferences to apply;SpamAssassinLearnOnMove=0makes an IMAP move into the Junk folder teach spam and a move out teach ham through spamc's TELL, off the IMAP thread. All off by default (source: IniFileSettings.cpp:341-354; Server/Common/AntiSpam/SpamAssassin/SpamAssassinClient.cpp:69-73TELL SPAMC/1.4). - Anti-virus: ClamAV (
ClamAVEnabled/Host/PortDB settings), clamscan and custom scanner viaInterfaceAntiVirus(source: Constants.h:141-143; Server/COM/InterfaceAntiVirus.cpp); scanner-failure policyAVFailAction=0(deliver),AVFailRetryMinutes=15,AVFailMaxHolds=16(IniFileSettings.cpp:537-551) — the AVFail keys are NOT in README (GAP). - The ClamAV connection test asks PING and VERSION first and streams its EICAR sample from memory, so Defender cannot remove the sample file before clamd reads it (source: Server/Common/AntiVirus/ClamAVVirusScanner.cpp:136-157) — 6.2.25.
-
Auto-ban reaches the operating system's firewall (in the tree since 11 September 2026; unreleased until 6.3.2). An auto-ban is an expiring IP range the listeners consult at accept time, so the operating system still accepted the address and the server dropped it. Three
[Settings]keys, off as shipped, with editors on the Control Panel's Auto-ban page:AutoBanFirewall=1- on Windows an inbound block rule per banned address in Windows Defender Firewall (TCP, the server's listening ports only, rule group hMailServer auto-ban), on Linux the packaged/usr/lib/hmailserver/autoban-hookkeeping an nftables set with a timeout;AutoBanCommand- any program, run as<command> ban <address> <minutes> <ports>and<command> unban <address>;AutoBanNeverBan- addresses and CIDR blocks that are never counted, which matters once a ban can lock an administrator out of every mail port. The firewall is reconciled against the ranges at start-up, on ban, on range delete and every minute, so a rule never outlives its ban. Two fixed-shape application-log lines (Auto-ban: logon failure N of M from <ip> for <user>,Auto-ban: <ip> is banned for N minutes ...) are what the fail2ban filter the Linux packages install matches - see Installing on Linux. Errors 6521-6525. - Attachment blocking (
BlockedAttachment), auto-ban (AutoBanOnLogonFailureEnabledseeded 1) (source: Server/COM/InterfaceAntiVirus.cpp; CreateTablesMSSQL.sql:1001) - Login tarpit
LogonTarpitSeconds(SMTP/POP3/IMAP) and recipient tarpitSmtpTarpitCount/SmtpTarpitDelaySeconds(COMAntiSpam.TarpitCount/TarpitDelay), both off by default, shipped in 6.2.25 (source: IniFileSettings.cpp:675-679); a pause on the connection's own timer (TCPConnection::EnqueueDelay,BCTDelay), never a worker thread (source: AccountLogon::TarpitDelaySeconds; SMTPConnection::TarpitRecipient_) - Rate limits:
MaxSubmissionsPerIPPerMinute=0,MaxOutboundPerDestinationPerMinute=0(source: IniFileSettings.cpp:618,673) - Trace headers off by default:
AuthenticationResultsEnabled=0,ReceivedSpfHeaderEnabled=0,AuthenticationResultsIdentity=""(IniFileSettings.cpp:480-482) — not in README (GAP). - External filter hook:
FilterHookUrl="",FilterHookTimeoutSeconds=10,FilterHookFailClosed=0,FilterHookRejectScore=100,FilterHookMaxMessageSizeKB=10240(IniFileSettings.cpp:658-662) — not in README (GAP). - Quarantine:
QuarantineEnabled=0,QuarantineRetentionDays=30(IniFileSettings.cpp:624-625) — not in README (GAP).
- Password hash scheme ids: 0 None, 1 Blowfish, 2 MD5, 3 SHA256, 4 PBKDF2, 5 Argon2id, 6 DPAPI, 7 scrypt (source: Server/Common/Util/Crypt.h:18-30; scrypt added in 6.2.25, numbered 7 because DPAPI took 6). Schemes are compared by strength, not by number: Argon2id and scrypt are peers.
-
PreferredHashAlgorithm=4(PBKDF2) is the shipped default; a value that is not 3, 4, 5 or 7 is reported as error 5528 and reset to 4 (source: IniFileSettings.cpp:190-209). - Work factor is a setting since 6.2.25:
PasswordHashIterations,PasswordHashMemoryKB,PasswordHashTimeCost(read with a floor and a ceiling byReadPasswordHashWorkFactor_, IniFileSettings.cpp:233-235); a stored hash derived under a cheaper factor is re-derived at the next logon, upward only. - README:70 lists "Argon2id and PBKDF2-HMAC-SHA256" without stating the default; a header comment claiming Argon2id is the default (Server/Common/Util/Pop3LoginDelay.h:9) is inconsistent with the INI default.
-
MinimumAcceptedHashAlgorithm=0(policy off),PasswordPepper=""(only affects Argon2id) (source: IniFileSettings.cpp:198-203) - Password policy keys (all off/0):
PasswordPolicyMinimumLength,PasswordPolicyRequireMixedCase,PasswordPolicyRequireDigit,PasswordPolicyRequireNonAlphanumeric,PasswordPolicyRejectCommon,PasswordPolicyHistoryCount,PasswordPolicyMaximumAgeDays(IniFileSettings.cpp:666-672) — not in README (GAP). - Account lockout:
AccountLockoutThreshold=0(off),AccountLockoutWindowMinutes=30,AccountLockoutMinutes=30(IniFileSettings.cpp:558-564) — not in README (GAP). - SCRAM-SHA-256 with deterministic anti-enumeration salts (source: Server/Common/Util/Hashing/ScramSha256.cpp, symbol hit "deterministic"); SASLprep in Server/Common/Util/Parsing/StringParser.cpp — README:66,71 CONFIRMED in outline.
- OAuth2 bearer:
OAuth2Enabled=0,OAuth2RequireTLS=1,OAuth2AllowedAlgorithms=RS256,OAuth2HmacSecret="",OAuth2PublicKeyFile="",OAuth2Issuer="",OAuth2Audience="",OAuth2UsernameClaim=email(source: IniFileSettings.cpp:208-229) — README:67 names the feature; none of the keys appear in the README config section (GAP). - OAuth2 key sources (6.2.25):
OAuth2JwksUrl=""fetches the provider's JWK Set as the signing-key source (rotation handled, cachedOAuth2JwksCacheSeconds=3600);OAuth2IntrospectionUrl=""withOAuth2IntrospectionClientId/OAuth2IntrospectionClientSecret="",OAuth2IntrospectionCacheSeconds=300,OAuth2IntrospectionFailOpen=0adds token introspection for revocation. Both off until configured (source: IniFileSettings.cpp:265-275); both fetches shareHttpsClientwith the update check, so from 6.2.28 they go through[Settings] HttpProxywhen one is set. -
SmtpAuthenticatedSenderCheck=0(6.2.25): when 1, an authenticated session may only send as an address its account owns or has been granted; the post right on a mailbox's INBOX is the Send-As grant (source: IniFileSettings.cpp:734). - Outbound XOAUTH2 to smart hosts:
OutboundOAuth2TokenUrl,OutboundOAuth2ClientId,OutboundOAuth2ClientSecret,OutboundOAuth2Scope=https://outlook.office365.com/.default,OutboundOAuth2Hosts=smtp.office365.com,OutboundOAuth2FixedToken,FetchOAuth2Hosts=outlook.office365.com(IniFileSettings.cpp:210-217) — not in README at all (GAP). -
DisableAUTHList=""(IniFileSettings.cpp:674) — not in README. - TOTP for administrative logon: Server/COM/COMAuthentication.cpp; Control Panel Tools/ControlPanel/Views/ConnectView (source: grep hits) — README:72,115 CONFIRMED in outline.
- SERVER-enforced administrator second factor (shipped in 6.2.25):
[Security] AdministratorTotpSecret(DPAPI-protected). COMAuthentication refuses password-only once enrolled,AuthenticateWithCodeis the way in; REST API needsX-hMailServer-OTPheader and 401s withX-hMailServer-OTP: required. Enrol/remove viaSettings.EnrolAdministratorTOTP/DisableAdministratorTOTP; state viaApplication.AdministratorTOTPEnabled. CP connect flow prompts and retries;AdministratorTwoFactorDialogenrols. Distinct from and stronger than the older HKLM Control-Panel-only TOTP (source: COMAuthentication.cpp; RestApiServer.cpp AuthenticateBasic_/BuildUnauthorizedResponse_; InterfaceSettings.cpp; ServerSession.cs). - Control Panel AD pickers: Tools/ControlPanel/Services/ActiveDirectoryService.cs (System.DirectoryServices) — README:114 CONFIRMED in outline.
Three settings interact, and they do different jobs at different times. Getting them confused is the usual cause of "I set Argon2id and the hashes are still PBKDF2".
flowchart TD
subgraph write["When a password is SET"]
W1["PreferredHashAlgorithm<br/>default 4 = PBKDF2"] --> W2{"Is it 3, 4, 5 or 7?<br/>SHA256, PBKDF2, Argon2id, scrypt"}
W2 -- no --> W3["Error 5528 and reset to 4.<br/>None, Blowfish and MD5 are<br/>never used for a NEW secret"]
W2 -- yes --> W4["Derive with the work factor:<br/>PasswordHashIterations,<br/>PasswordHashMemoryKB,<br/>PasswordHashTimeCost"]
W4 --> W5["PasswordPepper is mixed in -<br/>Argon2id only"]
end
subgraph read["When a password is CHECKED at logon"]
R0["Compare against whatever scheme<br/>the stored hash uses"] --> R1{"Comparison<br/>succeeded?"}
R1 -- no --> RFAIL["Refused"]
R1 -- yes --> R2{"MinimumAcceptedHashAlgorithm > 0<br/>and the stored scheme ranks<br/>weaker than it?"}
R2 -- yes --> R3["Refused, AFTER a correct comparison -<br/>deliberately, so a policy refusal cannot<br/>be reached by a wrong password.<br/>Application log: 'the password must be reset'"]
R2 -- no --> R4{"Was the row still clear text,<br/>pending an upgrade?"}
R4 -- yes --> RUP["Re-derive under the preferred scheme"]
R4 -- no --> R5{"Is the preferred scheme STRONGER<br/>by StrengthRank than the stored one?"}
R5 -- yes --> RUP
R5 -- no --> R6{"Same scheme, but derived under<br/>a CHEAPER work factor than<br/>the ini asks for now?"}
R6 -- yes --> RUP
R6 -- no --> R7["Left exactly as it is"]
RUP --> R8{"Could the new hash<br/>be persisted?"}
R8 -- no --> R9["The login still succeeds on the old hash.<br/>Logged to the APPLICATION log, not ErrorManager -<br/>an unwritable database would otherwise report<br/>this on every login by every affected account"]
R8 -- yes --> R10["Stored"]
end
Read the right-hand column as one rule: upgrades happen at a successful logon, and
only upward. Raising PreferredHashAlgorithm migrates each account the next time
its owner signs in; lowering it, or lowering a work factor, leaves stored hashes
alone and applies only to new ones — so an administrator who backs out of an
expensive setting is not rewarded with a rewrite of every password to something
weaker.
Two consequences worth planning for:
-
MinimumAcceptedHashAlgorithmis a lockout with extra steps if you set it too high. The refusal happens after a correct comparison, and the upgrade that would satisfy the policy can only happen on a successful login — so an account whose stored hash is below the minimum can never climb out on its own. Its password has to be reset. - The scheme ids are 0 None, 1 Blowfish, 2 MD5, 3 SHA256, 4 PBKDF2, 5 Argon2id,
6 DPAPI, 7 scrypt, and they are compared by
StrengthRank, not by number — which is why Argon2id (5) and scrypt (7) are peers rather than one outranking the other, and why scrypt could be numbered 7 after DPAPI took 6 without changing any comparison.
- Section absent by default;
Enabled=0(229) -
Server=""(230, required when enabled: 119),Port=0-> 636 for LDAPS else 389 (231, 68-71) -
Security=2— 0 plain, 1 StartTLS, 2 LDAPS (235; LdapSettings.h:20-22) -
VerifyCertificate=1(252),AllowUnprotectedPassword=0(253) -
BindMethod=0— 0 Simple, 1 Negotiate (SSPI Kerberos/NTLM; Windows only - on Linux 1 is refused with HM6420) (255; LdapSettings.h:40-41) -
SearchBase=""(258),UserSearchFilter=(&(objectCategory=person)(objectClass=user)(sAMAccountName=%u))(259, 18-19),UserDnTemplate=""(260) -
ServiceUsername,ServiceDomain,ServicePasswordall "" (261-263) -
TimeoutSeconds=10, clamped 1-90 (270, 25-27) -
FallbackToWindowsLogon=0(276) — Windows only; on Linux the fallback refuses (HM6404) - Provisioning:
SyncFilter=(&(objectClass=user)(objectCategory=person)(mail=*)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))(278, 40-42),SyncMailAttribute=mail,SyncUsernameAttribute=sAMAccountName,SyncDisplayNameAttribute=displayName(279-281, 44-46),SyncMaxUsers=5000max 100000 (299, 48-54) -
SyncScheduleMinutes=0(off), clamped 15-10080 when set; the task is registered only when set (322-334, 62-63; Server/Common/LDAP/DirectorySyncTask.h:33) — README:69 CONFIRMED. - README:68-69 claim "verified against a live Windows Server 2025 DC" is not verifiable from code — UNCONFIRMED.
- Actions: keep, fileinto, discard, redirect, stop, setflag/addflag/removeflag, vacation, reject/ereject, include/return/global, addheader/deleteheader (editheader), notify (SieveEvaluator.cpp:329-553; SieveParser.cpp:536-542)
- Tests: header, address, envelope, exists, size, allof/anyof/not/true, hasflag, body, mailboxexists, ihave, environment, date, currentdate, string, duplicate, spamtest (SieveParser.cpp:561-562)
- Supported extensions (complete): fileinto, envelope, imap4flags, body, mailbox, vacation, vacation-seconds, copy, relational, subaddress, regex, ihave, environment, date, index, spamtest, spamtestplus, duplicate, editheader, variables, reject, ereject, include, enotify, comparator-i;ascii-casemap, comparator-i;octet, comparator-i;ascii-numeric (SieveParser.cpp:144-213)
- ManageSieve advertises:
fileinto copy relational subaddress vacation vacation-seconds imap4flags body mailbox regex ihave environment date index spamtest spamtestplus duplicate editheader variables reject ereject include enotify envelope(ManageSieveServer.cpp:331;envelopeadded to the line in 6.2.25) - README:77,468 lists only copy, relational, subaddress, imap4flags, vacation (+ :seconds) — the other 19 extensions are UNDERCLAIMED (GAP).
- virustest deliberately absent (SieveParser.cpp:176-181)
- Script storage:
<DataFolder>\Sieve\<account>\active.sieve; global include scripts in<DataFolder>\Sieve\_global\<name>.sieve(source: Server/Common/Sieve/SieveStorage.cpp:58-112) — README:468 "stored as files under the data directory" CONFIRMED. - Only the five system flags can be stored (README:468) — not separately verified here.
- Event scripts implemented: OnAcceptMessage, OnBackupCompleted, OnBackupFailed, OnClientConnect, OnClientLogon, OnClientValidatePassword, OnDeliverMessage, OnDeliveryFailed, OnDeliveryStart, OnError, OnExternalAccountDownload, OnHELO, OnRecipientUnknown, OnSMTPData, OnTooManyInvalidCommands (source: Server/Common/Scripting/*.cpp string literals). README:79 lists only five categories — GAP.
- Script engine timeout
ScriptTimeout=60s;ExternalProcessTimeout=300s (IniFileSettings.cpp:307-308) - Smart-host failover: a relayer host string containing
|is split and each host tried in order (source: Server/SMTP/ExternalDelivery.cpp:444-449) — README:81 CONFIRMED. - Plus-addressing (
InterfaceDomain), catch-all, distribution lists, routes, aliases: present in COM layer (source: Server/COM/InterfaceDomain.cpp, InterfaceCache.cpp) — README:80 CONFIRMED in outline. -
RejectFullMailboxAtRcpt=1,QuotaWarningPercent=90(IniFileSettings.cpp:641,643) — not in README. - Outbound delivery pipelines the envelope and sends the message as one BDAT chunk when the remote advertises PIPELINING and CHUNKING:
OutboundPipelining=1,OutboundChunking=1(each used only when the remote advertises the extension); a BINARYMIME message is relayed as-is to a remote that accepts BINARYMIME (source: IniFileSettings.cpp:732-733) — 6.2.25. -
DeliveryHardLinks=0: when 1, a message to several local recipients is one file with a name in each folder; every rewrite of a message file is a temporary file renamed into place (source: IniFileSettings.cpp:312) — 6.2.25. - Event scripts can create only the COM classes
ScriptAllowedObjectsnames; default*= anything, as before; a list makesCreateObjectof anything else fail with error 429 (source: IniFileSettings.cpp:389; Server/Common/Scripting/ScriptObjectPolicy.cpp) — 6.2.25. - A Message-ID is added only to submissions (authenticated, or from a local domain on a range that does not require authentication); relayed mail keeps its headers (source: Server/SMTP/SMTPMessageHeaderCreator.cpp:83-100
is_message_submission_) — 6.2.25. - Message retention (schema 6027):
Domain.MessageRetentionDaysandAccount.MessageRetentionDays(0 = no policy; on an account -1 = keep forever) remove delivered mail older than the policy in every folder, by stored creation time;MailboxRetentionTaskruns at start and every 6 h,Utilities.RunMessageRetentionruns it now (source: hMailServer.idl:854,988,1239; DBScripts/Upgrade6026to6027MSSQL.sql; Application.cpp:673-680) — 6.2.25. - Default special-use folders at account creation: hm_settings
CreateDefaultSpecialUseFoldersseeded 0 (COMSettings.CreateDefaultSpecialUseFoldersEnabled); off, a new account gets an inbox only (source: DBScripts/Upgrade6025to6026MSSQL.sql; Constants.h:153PROPERTY_CREATE_DEFAULT_SPECIAL_USE_FOLDERS; hMailServer.idl:808) — 6.2.25, schema 6026.
- Paths:
/livez,/readyz,/healthz,/metrics(930-951); readiness staleness ceilingDatabaseProbeStalenessMilliseconds = 20000(108) — README:470 "within the last 20 seconds" CONFIRMED. -
/metricsanswers 503 on a non-loopback bind with no credential, and 503 when TLS was requested but could not be prepared; probes never authenticate (391-397, 1417-1458) — README:369-375 CONFIRMED. - Metric names exported: hmailserver_auth_failures_total, hmailserver_auth_success_total, hmailserver_build_info, hmailserver_command_processing_seconds, hmailserver_database_connected, hmailserver_database_probe_age_seconds, hmailserver_database_probe_success_timestamp_seconds, hmailserver_database_up, hmailserver_db_connections, hmailserver_db_query_seconds, hmailserver_db_slow_queries_total, hmailserver_delivery_queue_messages, hmailserver_delivery_queue_oldest_message_age_seconds, hmailserver_domain_messages_received_total, hmailserver_domain_messages_sent_total, hmailserver_messages_bounced_total, hmailserver_messages_deferred_total, hmailserver_messages_delivered_total, hmailserver_messagestore_missing_files, hmailserver_metrics_unauthorized_requests_total, hmailserver_processed_messages_total, hmailserver_sessions, hmailserver_spam_messages_total, hmailserver_start_time_seconds, hmailserver_state, hmailserver_tls_certificate_expiry_seconds, hmailserver_tls_handshake_failures_total, hmailserver_tls_handshakes_total, hmailserver_viruses_removed_total, hmailserver_workqueue_blocking_tasks_waiting, hmailserver_workqueue_depth (grep of MetricsServer.cpp)
- Per-domain counters need
MetricsPerDomainEnabled=1(default 0) (IniFileSettings.cpp:656) — not in README (GAP). - Settings:
MetricsServerPort=0,MetricsServerBindAddress=127.0.0.1,MetricsServerAuthToken/Username/Password="",MetricsServerCertificateFile/PrivateKeyFile=""(IniFileSettings.cpp:426-437) - Metric history (6.2.25, schema 6028): one row per metric per minute in
hm_metricsamples, keptMetricsHistoryDays=7;MetricsHistoryTasksamples at start and every minute; read back over COMUtilities.GetMetricHistory/SampleMetricsNowandGET /api/v1/metrics/history; shown on the Control Panel dashboard for a day, a week or a month (source: IniFileSettings.cpp:735; Application.cpp:685-692; RestApiServer.cpp:1847; hMailServer.idl:1240-1241; DBScripts/Upgrade6027to6028MSSQL.sql).
-
OtelEndpoint=""traces/v1/traces(OtelTracer.cpp:72);OtelMetricsEndpoint=""/v1/metrics(OtelMetricsExporter.cpp:64);OtelLogsEndpoint=""/v1/logs(OtelLogExporter.cpp:51); default port 4318 (OtelExportChannel.cpp:21,60,67);OtelServiceName=hmailserver;OtelMetricsInterval=60clamped 5-3600 (IniFileSettings.cpp:448-452; OtelMetricsExporter.cpp:30-31,79-80) — README:376-386 CONFIRMED. -
traceparentread from message headers on delivery and emitted onward (source: Server/Common/Util/OtelTraceContext.cpp:188,210,266; Server/SMTP/SMTPDeliverer.cpp; Server/SMTP/SMTPMessageHeaderCreator.cpp; also HTTP in RestApiServer.cpp, WebServicesServer.cpp, HTTPClient.cpp) — README:87 CONFIRMED.
-
WindowsEventLogEnabled=1,WindowsEventLogLevel=2clamped 1-4 (IniFileSettings.cpp:628-636); throttle 5 events per id per 10-minute window, hard-coded (Server/Common/Application/WindowsEventLog.cpp:95-96) — README:387-394 CONFIRMED. -
JsonLogging=0,LogDeleteDays=0,SepSvcLogs=0(per-service log files),LogLevel=9,MaxLogLineLen=500,SlowQueryLogMilliseconds=0(IniFileSettings.cpp:406-409,251-253). Slow-query log and SepSvcLogs keys are not in README (GAP; README:88 describes the features). -
MessageStoreFsync=0;MessageStoreConsistencyCheck=0writes<LogFolder>\hMailServer_messagestore_consistency.report(IniFileSettings.cpp:410,425; Server/Common/Application/MessageStoreConsistencyTask.cpp:74) — CONFIRMED. -
MinimumFreeDiskSpaceMB=100,DiskSpaceWarningThresholdMB=1024, negatives clamped to 0 (IniFileSettings.cpp:411-420) -
ShutdownDrainSeconds=0(408);ScheduledBackupTime="",ScheduledBackupIntervalMinutes=0,ScheduledBackupKeepCount=0,ScheduledBackupMaxAgeDays=0(444-447) -
DatabaseStatementTimeout=30, negative -> 0; script runner asks for 1800 s (IniFileSettings.cpp:645-650; Server/Common/SQL/PGConnection.cpp:304; MySQLConnection.cpp:657) — README:429-433 CONFIRMED. - Bounded waits:
DNSQueryTimeout=10,ClientSessionCeiling=1800,DBConnectionAcquireTimeout=60,ScriptTimeout=60,ExternalProcessTimeout=300,AsyncQueueStallThreshold=120,AsyncQueueReservedThreads=2,FinalizationTimeout=240(IniFileSettings.cpp:281-315) — README:89 describes these ceilings but names none of the keys (GAP). - Message trace:
MessageTraceEnabled=0,MessageTraceRetentionDays=30(626-627); archive:ArchiveDir,ArchiveHardLinks=0,ArchiveRetentionDays=0, retention task every 12 h (IniFileSettings.cpp:262-265,644; Application.cpp:661) — not in README (GAP). - Full-text index:
IndexerFullText=0,IndexerFullTextBatchSize=250(1-100000),IndexerFullTextMinTokenLength=3(3-64),IndexerFullTextMaxTokensPerMessage=2048(64-1000000); also requires DBMessageIndexing(seeded 0) (IniFileSettings.cpp:365-392; CreateTablesMSSQL.sql:1013) — README:444-456 CONFIRMED. - Archive index (6.2.25, schema 6029): every archive copy is a row in
hm_archiveindex; searched over COMUtilities.SearchArchiveandGET /api/v1/archive;POST/DELETE /api/v1/archive/{id}/holdandUtilities.SetArchiveHoldplace a legal hold that survives the retention sweep andEraseAddressTraces;ArchiveDomains=""(comma list) scopes the archive to named domains (source: IniFileSettings.cpp:317; RestApiServer.cpp:2031; hMailServer.idl:1242-1243; DBScripts/Upgrade6028to6029MSSQL.sql). -
BackupVerifyRestore=1(6.2.25): every backup extracts its message store to a scratch directory through the restore code before it is called complete (source: IniFileSettings.cpp:480).
- The server reads the project's release feed once a day when
UpdateCheckEnabled=1(default 0) and on demand always (Status.CheckForUpdate,POST /api/v1/update/check); theUpdateCheckTaskruns at start and every 15 minutes so anUpdateWindowan hour long is not missed; the feed is read everyUpdateCheckHours=24(source: IniFileSettings.cpp:489-494; Application.cpp:709-715). - Verify: the newer release's installer and its Sigstore bundle are downloaded to
<DataFolder>\Updatesand the installer kept only when the bundle proves it is the one the release workflow signed (digest, Fulcio chain at log time, identity, signature, log signature) — no cosign, no network beyond the download (Status.DownloadUpdate; Common/Util/SigstoreVerifier.cpp, UpdateDownloader.cpp). Trust roots can be overridden:UpdateTrustRootsFile,UpdateLogPublicKeyFile,UpdateSigningIdentity,UpdateSigningIssuer,UpdateSourceRepository,UpdateRequireAuthenticode=0(IniFileSettings.cpp:498-503). - Apply:
Status.InstallUpdate/POST /api/v1/update/installhands the verified installer tohMailServer.Updater.exe, which runs it, waitsUpdateServiceWaitSeconds=180for the service to come back, reinstalls the previous version if it does not, and reports the outcome at the next start (Status.UpdateApplyOutcome); the database upgrade authenticates with a single-use token instead of the administrator password (Common/Util/UpdateApplyToken);UpdateBackupBeforeApply=1,UpdateAutoDownload=0,UpdateWindow="",UpdateChannel=stable,UpdateFeedUrl=""(source: IniFileSettings.cpp:489-509; hMailServer.idl:540-551; installation/section_files_64.iss:6; Server/hMailServer.Updater/). - Surfaces: COM
Status.UpdateState(0 unchecked, 1 latest, 2 newer available, 3 downloaded and verified, 4 installing, 5 failed),AvailableVersion,AvailableVersionPublished,AvailableVersionUrl,UpdateLastChecked,UpdateLastError,UpdateInstallerPath,UpdateSignerIdentity; RESTGET /api/v1/update; the Control Panel's Updates card (FeatureSettingsView) and the three status-page buttons (source: hMailServer.idl:540-551; RestApiServer.cpp:1853-1871; Tools/ControlPanel/Views/StatusView.xaml.cs:42-44; FeatureSettingsView.xaml.cs:1932-1934). - Through a proxy:
HttpProxy=""([Settings]) is a forward proxy for the web requests the server makes as a client throughHttpsClient- the feed, the installer and its bundle - ashost:portor[ipv6]:port; empty is a direct connection, a value without a port is an error rather than a silent direct connection, and there are no proxy credentials. An https target is reached with CONNECT and the TLS handshake runs inside the tunnel under the same certificate verification as a direct connection; plain http gets the absolute URL in the request line; a proxy that says no is reported as "The proxy refused CONNECT to host:port: ". The setting belongs toHttpsClient, so the OAuth2 JWKS fetch and token introspection take the same route - and those are all of it: ACME, the outbound XOAUTH2 token request and the MTA-STS policy fetch build their own connections and go direct whatever this is set to. The editor is on the Control Panel's Updates card beside the feed URL (source: Common/Util/HttpsClient.cpp:86-198; IniFileSettings.cpp:492; FeatureSettingsView.xaml.cs:1936).
- C++ server,
<PlatformToolset>v145(source: Server/hMailServer/hMailServer.vcxproj) — README:97 CONFIRMED. - OpenSSL 4.0.2, Boost 1.92.0, PostgreSQL 18.3 libpq referenced from the project/build files (source: hMailServer.vcxproj:76
boost_1_92_0,openssl-4.0.2,postgresql-18.3; build/Get-LibraryVersions.ps1:24-25; fuzz/build-fuzz.ps1:194) — both bumped in 6.2.25 (OpenSSL 4.0.2 is a security patch release); built bylibraries/build-openssl.ps1,build-boost.ps1,build-pgsql.ps1. README:98-100 CONFIRMED (README says "4.0.x"). - Database types accepted in
[Database] Type: MSSQL, MYSQL, PostgreSQL, MSSQLCE (source: IniFileSettings.cpp:114-120); SQL CE forced to one connection (147-153) -
libmysql.dllshipped fromExtras\intoBinas the MySQL/MariaDB client, loaded at runtime (source: hmailserver/installation/section_files_common.iss:31-33; Server/Common/SQL/MySQLInterface.cpp:75). That it is MariaDB Connector/C is stated by the .iss comment and hmailserver/docs/ThirdPartyBinaries.md; the shipped auth plugin DLLs were NOT verified here — UNCONFIRMED. - Tools and Control Panel target
net10.0-windows(9 csproj files) (source: grep of hmailserver/source/Tools/**/*.csproj) — README:102 CONFIRMED. - Quality gates: workflows present — ci.yml, codeql.yml (languages csharp AND c-cpp, weekly cron), dco.yml, dependency-review.yml, installer-smoke.yml, sbom.yml (Syft), scorecard.yml, server-build.yml, sign-release.yml, style.yml, upstream-watch.yml (monthly cron
0 6 1 * *), verify-binary-provenance.yml;.github/dependabot.ymlexists (source:.github/workflows/listing; codeql.yml:88,229; upstream-watch.yml:17) — README:106 CONFIRMED.
- Control Panel
hMailCPis WPF, talks over COM; Ctrl+K search button exists ("Search pages, settings and tasks (Ctrl+K)") (source: hmailserver/source/Tools/ControlPanel/MainWindow.xaml:80) — README:111-113 CONFIRMED in outline. - Control Panel localisation (new in 6.2.28): resx catalogues with the English text as the key and complete translations in Czech, Danish, German, Spanish, Finnish, French, Italian, Japanese, Norwegian bokmål, Dutch, Polish, Brazilian Portuguese, Russian, Swedish, Turkish, Ukrainian and Simplified Chinese; a language picker; CI checkers that fail on an unmarked or untranslated caption (source: hmailserver/source/Tools/ControlPanel/Resources/Strings.*.resx, 17 catalogues beside Strings.resx; Services/LanguageChoice.cs; .github/workflows/style.yml:72-87
check-mnemonics.py,check-localisation.py,check-catalogues.py). - Every
[Settings]key the server reads has a Control Panel editor, checked bybuild/check-ini-coverage.pyinstyle.yml(source: .github/workflows/style.yml:95) — new in 6.2.28. - REST API settings:
RestApiPort=0,RestApiBindAddress=127.0.0.1,RestApiCertificateFile="",RestApiPrivateKeyFile=""; falls back to the ACME certificate (IniFileSettings.cpp:591-594; Application.cpp:516-526) - REST routes actually served (source: Server/Common/Util/RestApiServer.cpp:1281-2045):
/api/v1/apikeys(GET, POST),/api/v1/apikeys/<id>(DELETE),/api/v1/status,/api/v1/domains,/api/v1/domains/<name>/accounts,/api/v1/domains/<name>/aliases,/api/v1/accounts/<address>,/api/v1/queue,/api/v1/queue/<id>and/retry,/api/v1/tlsa,/api/v1/srv,/api/v1/quarantine,/api/v1/quarantine/<id>and/release,/api/v1/openapi.json; since 6.2.25:/api/v1/ipranges(GET, POST),/api/v1/ipranges/<id>(DELETE),/api/v1/domains/<name>/lists(GET, POST),/api/v1/lists/<id>(DELETE),/api/v1/domains/<name>/dkim(GET),/api/v1/certificates(GET, never the private-key password),/api/v1/rules(GET),/api/v1/logsand/api/v1/logs/<name>(GET),/api/v1/backup(POST, GET),/api/v1/settings(GET snapshot),/api/v1/archive(GET) and/api/v1/archive/<id>/hold(POST, DELETE),/api/v1/metrics/history(GET) (:1847, 1930-2045). Writing settings, rules, certificates and DKIM stays COM-only on purpose. New in 6.2.28:/api/v1/update(GET) and/api/v1/update/check,/download,/install(POST) (:1853-1871); the self-service surface/api/v1/session(POST, DELETE),/api/v1/meand/api/v1/me/password,/vacation,/quarantine,/folders,/folders/<id>/messages,/messages,/messages/<id>(flags, move, delete, attachments),/drafts,/settings,/filters,/search(:1581-1738), and the pages/portal,/portal.js(:1281-1284). - README:472 omits
/srv,/quarantine*,/domains/<name>/aliases,/openapi.json— GAP. - The earlier internal inconsistency (router
/api/v1/apikeys, OpenAPI document/api/v1/api-keys) is resolved: the served document now spells/api/v1/apikeys(grep -c api-keys RestApiServer.cpp= 0; router at :1554). - REST auth: Bearer (scoped API key, SHA-256 hex digest stored, 32-byte secret, 8-byte id,
hmapi_prefix, stored inhMailServerApiKeys.inibeside the INI) preferred, Basic still accepted; key scope default "readonly"; 200 requests per 10 s per credential (source: RestApiServer.cpp:158,223,2416). Request limits: 64 KB per request, 16 MB and 300 s for the two message-submission routes only, 30 s per request, 300 s per connection, 64 concurrent connections, 4 worker threads — the listener is the shared Boost.AsioHttpServerwith its own io_context and absolute deadlines, and adds a TLS 1.2 floor of its own afterSslContextInitializer::InitServer(source: RestApiServer.cpp:128-136, 618-636; Common/Util/HttpServer.h:79-101). The HttpServer hosting is new in 6.2.28; the earlier "socket timeout 10 s" has no counterpart in the current code. README:118 CONFIRMED; README:363 comment "HTTP Basic auth, administrator password" understates (Bearer keys also work). - Web services:
WebServicesHttpPort=0,WebServicesHttpsPort=0,WebServicesBindAddress=0.0.0.0, plusWebServicesCertificateFile/PrivateKeyFile=""(not in README) (IniFileSettings.cpp:602-606); paths served:/.well-known/mta-sts.txt,/.well-known/security.txt(hosted domains with a postmaster address;/security.txttoo),/mail/config-v1.1.xml,/.well-known/autoconfig/mail/config-v1.1.xml,/autodiscover/autodiscover.xml, and/email.mobileconfig(alias/mail/config.mobileconfig; Apple profile — not in README). Since 6.2.25 the profile is HTTPS-only: over plain HTTP it answers 301 to theWebServicesHttpsPortlistener when one is configured, 403 when none is, unless a TLS-terminating proxy sendsX-Forwarded-Proto: https(source: Server/Common/Util/WebServicesServer.cpp:337-340, 630, 653-659, 758, 807). The listener is hosted on the sharedHttpServeras of 6.2.28. -
CardDAV (6.3.3) and CalDAV (in the tree since 15 September 2026, unreleased) on the same listener, under
/dav/:/dav/addressbooks/<address>/contacts/is the account's address book (RFC 6352) and/dav/calendars/<address>/calendar/its calendar (RFC 4791,VEVENTandVTODOin one collection), each authenticated as the account with its password or an application password, each made the first time it is asked for. Discovery is/.well-known/carddavand/.well-known/caldav, which redirect to this server's own/dav/unlessCardDavRedirectUrl/CalDavRedirectUrlnames another - and a configured value that cannot be used is refused with 404 rather than quietly falling back here (HM5780 names it). Both are served over HTTPS only: HTTP Basic would put an account's password on the wire in clear, so a client reaching/dav/on the plain-HTTP listener is refused with that reason unless a TLS-terminating proxy setsX-Forwarded-Proto: https; withWebServicesHttpsPort=0the start-up report says so by name. The calendar side isCommon/Util/CalDavServer.cppoverCommon/Util/CalendarStore.cpp(schema 6041) andCommon/Util/ICalendar.cpp, the project's own RFC 5545 reader, writer and recurrence expander; it does not do iMIP, free-busy, a per-calendar time zone, partialcalendar-data, or a time-range filter on an alarm (source: Server/Common/Util/WebServicesServer.cpp:359-366, 386-390, 655-694, 1220-1240; CalDavServer.cpp:62; CardDavServer.cpp). -
MtaStsHostingEnabled=1,MtaStsPolicyMode=enforce,MtaStsPolicyMaxAge=604800,MtaStsPolicyMx="",AutoconfigEnabled=1,AutoconfigClientHost=""(IniFileSettings.cpp:607-612) — README:350-355 CONFIRMED. Note these are inert until a WebServices port is non-zero. - Self-service portal (new in 6.2.28; a webmail since 6.3.0 — routed on the fragment so a folder, a message, a search and a draft each have an address, new mail noticed by polling
GET /api/v1/me/changesevery six seconds and not at all while the tab is hidden, folders created, renamed and deleted throughPOST/PUT/DELETE /api/v1/me/foldersunder IMAP's own rules, and inline images fetched by the page and handed to its sandboxed frame asdata:URLs):GET /portalon the REST listener serves a sign-in page for an account's own credentials, backed by the/api/v1/meroutes — state, password change, automatic reply, own quarantine (release to self / give up own copy), folders and messages (read, flags, move, delete, attachments as downloads, search, drafts, send through the SMTP-submission pipeline with a Sent copy), settings (name, forwarding, signature) and the active Sieve script checked as ManageSieve checks it. Browser sessions:POST /api/v1/sessionturns the password into a cookie the server knows only by its hash, bounded in idleness, life and number, ended byDELETE /api/v1/sessionor a password change; writes need a header a browser never adds on its own. An account that has enrolled a TOTP secret signs in with an app password, because its own password stops being a mailbox credential the moment the secret is set (PasswordValidator.cpp:189-191); the code itself is asked for only byPOST /api/v1/me/password, inX-hMailServer-OTP(RestApiServer.cpp:4479-4485). Inert whileRestApiPort=0(source: RestApiServer.cpp:1281-1284, 1581-1738, 7660-7677; IniFileSettings.cpp:706RestApiPort). - Secrets:
ProtectStoredSecretsWithDPAPI=1; Blowfish values always still readable; on Windows a DPAPI failure falls back to Blowfish "so a secret is never lost" (IniFileSettings.cpp:236; Server/Common/Util/Crypt.cpp:198-206) — README:482 CONFIRMED. On Linux the store is a key file (<DataFolder>/.hmailserver-secret-key, AES-256-GCM, envelopes prefixedLINUX1:) and a store failure refuses rather than falling back (HM6416). -
ServiceAccountName="",ServiceAccountPassword="";NT SERVICE\hMailServeris the recommended value per code comment (IniFileSettings.cpp:241-247) — README:484 CONFIRMED. - Other INI sections read by the server:
[Security] AdministratorPassword;[Database] Server, Database, Username, Password, Internal, ServerFailoverPartner, Provider, Type, Passwordencryption, Port, NumberOfConnections=5, ConnectionAttempts=6, ConnectionAttemptsDelay=5;[Directories] ProgramFolder, DataFolder, TempFolder, EventFolder, DatabaseFolder, LogFolder;[GUILanguages] ValidLanguages;[Settings] UseLanguage=English(IniFileSettings.cpp:97-168,886,939) -
[Database]also, since 6.2.25:AllowUnencryptedConnection=0(MySQL: 1 lets the client fall back from TLS; the default refuses a server without it),PostgreSQLSslMode=""andPostgreSQLSslRootCert=""(libpq sslmode/sslrootcert; an unknown mode refuses the connection with the reason),ConnectionStringOptions=""(appended verbatim to the MS SQL OLE DB connection string, e.g.Encrypt=yes;TrustServerCertificate=no) (source: IniFileSettings.cpp:162-165) — verified by reading only, per the 6.2.25 release notes' known limitation 1. -
[Security] AdministratorTotpSecret(DPAPI-protected; written bySettings.EnrolAdministratorTOTP, cleared to recover) (source: IniFileSettings.cpp:112,1171-1190) — 6.2.25.
-
MinVersion=10.0.14393(Windows 10 1607 / Server 2016), error text confirms 64-bit (source: section_setup.iss:24-28; section_messages.iss:3) — README:130 CONFIRMED. - Components:
server,admintools("registers the COM API for scripts"),controlpanel("requires .NET 10 Desktop Runtime") (source: section_components.iss) — README:146 CONFIRMED. - Custom switch
/useinternaldbmsread as{param:useinternaldbms|true}(source: hMailServerInnoExtension.iss:616) — README:147 CONFIRMED. - .NET Desktop Runtime bundled as
windowsdesktop-runtime-10.0-win-x64.exe, run/install /quiet /norestart, failure surfaced by exit code (hMailServer64.iss:17-18; hMailServerInnoExtension.iss:432-446) — README:116,125 CONFIRMED. - Registers COM via
hMailServer.exe /RegisterTypeLiband the service via/Register, checking exit codes; sets service recovery via sc.exe; runs DBSetupQuick.exe and checks ResultCode (hMailServerInnoExtension.iss:735-805, 959-961) — README:161 CONFIRMED in outline. - Reads 32-bit
InstallLocationunderhMailServer_is1(hMailServerInnoExtension.iss:242,605) — README:163 CONFIRMED. - README:158-159 (admin password not settable on the command line; silent upgrade needs it) — not verified against the Pascal; UNCONFIRMED.
- All referenced files exist: hMailServer.sln,
hMailServer Tools.sln, ControlPanel.sln,hMailServer Tests.sln, build/build.ps1, post-build.ps1, build-tests.ps1, run-tests.ps1, build-tools.ps1, hMailServer64.iss (checked with[ -e ]). - Build steps, OpenSSL/Boost/libpq recipes (README:207-295) are procedural and were not executed — treat as README's claims.
-
AddXOriginalRcptTo=0default; README:547 says set to 1 for the X-Original-Rcpt-To tests (IniFileSettings.cpp:399) — CONFIRMED.
Every key README names was found; defaults match README in every case. Line numbers are IniFileSettings.cpp.
| Key | Code default | Line |
|---|---|---|
| MtaStsEnabled | 1 | 402 |
| DaneEnforcementEnabled | 1 | 403 |
| DnssecValidationEnabled | 1 | 404 |
| DnssecTrustAnchors | "" | 405 |
| TlsKeyExchangeGroups | X25519MLKEM768:SecP256r1MLKEM768:X25519:secp384r1:secp256r1 | 572 |
| TlsCipherSuites13 | "" | 578 |
| TlsSessionTicketsEnabled | 1 | 584 |
| TlsSessionCacheSize | 0 | 585 |
| TlsSessionTimeoutSeconds | 0 | 586 |
| TlsTicketKeyRotationSeconds | 0 | 588 |
| DkimOversignHeaders | "" | 474 |
| DKIMSignatureValiditySeconds | 0 (neg->0) | 464 |
| DKIMEnforceSignatureExpiry | 1 | 468 |
| DKIMExpiryClockSkewSeconds | 300 (neg->0) | 470 |
| ArcSealingEnabled | 0 | 455 |
| TlsRptFromAddress | "" | 483 |
| TlsRptOrganizationName | hMailServer | 484 |
| AcmeEnabled | 0 | 595 |
| AcmeContactEmail | "" | 597 |
| AcmeDomains | "" | 598 |
| AcmeDirectoryUrl | https://acme-v02.api.letsencrypt.org/directory | 596 |
| AcmeHttpPort | 80 | 600 |
| AcmeReuseKey | 1 | 601 |
| WebServicesHttpPort | 0 | 602 |
| WebServicesHttpsPort | 0 | 603 |
| WebServicesBindAddress | 0.0.0.0 | 604 |
| MtaStsHostingEnabled | 1 | 607 |
| MtaStsPolicyMode | enforce | 608 |
| MtaStsPolicyMaxAge | 604800 | 609 |
| MtaStsPolicyMx | "" | 610 |
| AutoconfigEnabled | 1 | 611 |
| AutoconfigClientHost | "" | 612 |
| RestApiPort | 0 | 591 |
| RestApiBindAddress | 127.0.0.1 | 592 |
| RestApiCertificateFile | "" | 593 |
| RestApiPrivateKeyFile | "" | 594 |
| MetricsServerPort | 0 | 426 |
| MetricsServerBindAddress | 127.0.0.1 | 427 |
| MetricsServerAuthToken | "" | 433 |
| MetricsServerAuthUsername | "" | 434 |
| MetricsServerAuthPassword | "" | 435 |
| MetricsServerCertificateFile | "" | 436 |
| MetricsServerPrivateKeyFile | "" | 437 |
| OtelEndpoint | "" | 448 |
| OtelServiceName | hmailserver | 449 |
| OtelMetricsEndpoint | "" | 450 |
| OtelLogsEndpoint | "" | 451 |
| OtelMetricsInterval | 60 (clamp 5-3600) | 452 |
| WindowsEventLogEnabled | 1 | 628 |
| WindowsEventLogLevel | 2 (clamp 1-4) | 629 |
| SMTPProxyProtocolEnabled | 0 | 620 |
| SMTPProxyProtocolTrustedIPs | "" | 621 |
| SMTPXClientEnabled | 0 | 622 |
| SMTPXClientTrustedIPs | "" | 623 |
| ScheduledBackupTime | "" | 444 |
| ScheduledBackupIntervalMinutes | 0 | 445 |
| ScheduledBackupKeepCount | 0 | 446 |
| ScheduledBackupMaxAgeDays | 0 | 447 |
| LogDeleteDays | 0 | 407 |
| ShutdownDrainSeconds | 0 | 408 |
| MessageStoreFsync | 0 | 410 |
| MessageStoreConsistencyCheck | 0 | 425 |
| MinimumFreeDiskSpaceMB | 100 (neg->0) | 417 |
| DiskSpaceWarningThresholdMB | 1024 (neg->0) | 419 |
| DatabaseStatementTimeout | 30 (neg->0) | 645 |
| IMAPExpungeRetentionRecords | 5000 (neg->0) | 646 |
| DmarcRptSchemaVersion | 1 (else->1, error 6210) | 506 |
| IndexerFullText | 0 | 365 |
| IndexerFullTextBatchSize | 250 (1-100000) | 372 |
| IndexerFullTextMinTokenLength | 3 (3-64) | 382 |
| IndexerFullTextMaxTokensPerMessage | 2048 (64-1000000) | 390 |
| IMAPSearchTimeout | 60 | 347 |
| IMAPSearchMaxMegabytes | 2048 | 349 |
| ManageSieveServerPort | 0 | 453 |
| ManageSieveServerBindAddress | 127.0.0.1 | 454 |
| JsonLogging | 0 | 406 |
| ProtectStoredSecretsWithDPAPI | 1 | 236 |
| ServiceAccountName | "" | 246 |
| ServiceAccountPassword | "" | 247 |
| SRSEnabled | 0 | 614 |
| SRSSecret | "" | 615 |
| MaxSubmissionsPerIPPerMinute | 0 | 618 |
| MaxOutboundPerDestinationPerMinute | 0 | 673 |
| AddXOriginalRcptTo | 0 | 399 |
| [LDAP] SyncScheduleMinutes | 0 (15-10080 when set) | LdapSettings.cpp:322 |
README names these, which are NOT INI keys: ASArcFilteringEnabled, ASArcTrustedSealers (hm_settings rows; Constants.h:118-119), SslCipherList (hm_settings; Constants.h:139), "Enable message indexing" = MessageIndexing (hm_settings; Constants.h:126), the IMAP SASL gate = EnableImapSASLPlain (hm_settings; Constants.h:146).
Since 6.2.25 README:379-384 declares its [Settings] block the operator's shortlist ("about ninety further keys ... are documented one row each ... on the wiki's Settings Reference"), so absence from README is no longer a gap in the README; the list is kept for completeness. Keys not in the README block: AcmeCertificateDirectory (599), WebServicesCertificateFile/WebServicesPrivateKeyFile (605-606), BATVEnabled/BATVSecret (616-617), RewriteEnvelopeFromWhenForwarding (613), Pop3LoginDelaySeconds (619), QuarantineEnabled/QuarantineRetentionDays (624-625), MessageTraceEnabled/MessageTraceRetentionDays (626-627), DmarcTreeWalkEnabled (639), SpfVoidLookupLimit (640), RejectFullMailboxAtRcpt (641), DkimAcceptSha1 (642), QuotaWarningPercent (643), ArchiveRetentionDays (644), MetricsPerDomainEnabled (656), FilterHook* x5 (658-662), PasswordPolicy* x7 (666-672), DisableAUTHList (674), AuthenticationResultsEnabled/ReceivedSpfHeaderEnabled/AuthenticationResultsIdentity (480-482), DmarcRptFromAddress/DmarcRptOrganizationName (490-491), AVFailAction/AVFailRetryMinutes/AVFailMaxHolds (537-551), AccountLockout* x3 (558-560), SlowQueryLogMilliseconds (409), PreferredHashAlgorithm/MinimumAcceptedHashAlgorithm/PasswordPepper (171-203), OAuth2* x8 (208-229), OutboundOAuth2* x6 and FetchOAuth2Hosts (210-217), the timeout family (DNSQueryTimeout, ClientSessionCeiling, DBConnectionAcquireTimeout, ScriptTimeout, ExternalProcessTimeout, AsyncQueueStallThreshold, AsyncQueueReservedThreads, FinalizationTimeout, POP3/SMTP/SA/Clam min/max timeouts, 266-315), and the classic keys (MaxNumberOfExternalFetchThreads, AddXAuthUserHeader, AddXAuthUserIP, DaemonAddressDomain, Greylisting*, DNSBLChecksAfterMailFrom, SepSvcLogs, LogLevel, MaxLogLineLen, QuickRetries*, QueueRandomnessMinutes, MXTriesFactor, ArchiveDir, ArchiveHardLinks, SAMoveVsCopy, AuthUserReplacementIP, IndexerFullMinutes, IndexerFullLimit, IndexerQuickLimit, LoadHeaderReadSize, LoadBodyReadSize, BlockedIPHoldSeconds, SMTPDMaxSizeDrop, BackupMessagesDBOnly, UseDNSCache, DNSServer, UseLanguage). Test hooks SimulateSpoolWriteFailure, SimulateDatabaseFailureFor (422-423) should not be documented for operators.
Added since 6.2.25 (line numbers in the current IniFileSettings.cpp): OutboundPipelining/OutboundChunking (732-733), DeliveryHardLinks (312), ArchiveDomains (317), SpamAssassinUser/SpamAssassinUserFromRecipient/SpamAssassinLearnOnMove (341-354), ScriptAllowedObjects (389), BackupVerifyRestore (480), LogonTarpitSeconds/SmtpTarpitCount/SmtpTarpitDelaySeconds (675-679), SmtpAuthenticatedSenderCheck (734), MetricsHistoryDays (735), PasswordHashIterations/PasswordHashMemoryKB/PasswordHashTimeCost (233-235), OAuth2JwksUrl/OAuth2JwksCacheSeconds (265-266), OAuth2Introspection* x5 (271-275), [Database] AllowUnencryptedConnection/PostgreSQLSslMode/PostgreSQLSslRootCert/ConnectionStringOptions (162-165), [Security] AdministratorTotpSecret (112); and, new in 6.2.28: IMAPCompressionEnabled (497), HttpProxy (492 in the release tree) and the fourteen Update* keys (489-509).
- Schema version: README:110 "Database version 6027"; code
REQUIRED_DB_VERSION 6031(Constants.h:173). (README:35's "still missing" claim about LIST-STATUS, non-synchronising literals and BINARY, listed here before, has been corrected in the README.) - REST OpenAPI document spelled
/api/v1/api-keyswhile the router served/api/v1/apikeys— resolved: the served document now says/api/v1/apikeys(grep -c api-keys RestApiServer.cpp= 0 at 40ae9491d; router at :1554) — README:472 matches the router. - README:363 comment says the REST API uses "HTTP Basic auth, administrator password"; code prefers Bearer API keys and still accepts Basic.
- Password hashing default is PBKDF2 (
PreferredHashAlgorithm=4), while Pop3LoginDelay.h:9 says Argon2id is the default — code-internal; README:70 is silent on the default.
(README's [Settings] block is a declared shortlist since 6.2.25, so the configuration-key items below are gaps in feature prose, not in the reference.) SMTP BINARYMIME; 19 additional Sieve extensions; ManageSieve RENAMESCRIPT/UNAUTHENTICATE; 19 additional IMAP capabilities; REST /srv, /quarantine, /aliases, /openapi.json; /email.mobileconfig; per-port client-certificate policy; quarantine; message trace; external filter hook; password policy; account lockout; POP3 login delay; per-domain metrics; outbound XOAUTH2; AV-failure policy; Authentication-Results/Received-SPF headers; DMARC aggregate reporting sender; 15 named event scripts (README lists five categories); the bounded-wait timeout keys.
- MariaDB Connector/C auth plugin DLLs shipped alongside libmysql.dll (README:101).
- SNI on inbound/outbound (README:42) — symbol hits only.
- DNSSEC applied to SPF/DKIM/DMARC lookups (README:48) — only the DANE/MX path was read.
- Argon2id "transparent upgrade on login" (README:70).
- "Only the five system flags can be stored" via Sieve imap4flags (README:468).
- Installer: admin password not settable from the command line; silent upgrade needs it (README:158-159).
- LDAP "verified end to end against Windows Server 2025 DC" (README:69).
- ".NET 10 still supports Server 2012" and the 12 January 2027 floor plan (README:130-132) — external/policy statements.
- The IMAP4rev2 per-command deltas beyond RECENT/UNSEEN/ESEARCH were not enumerated.
hMailServer 6.3.3 · AGPL-3.0-or-later · Repository · Report a documentation error
Hmail Server — full index
Start here
1. Install and run
- Before You Install
- Installing hMailServer
- Installing on Linux
- Running in a Container
- The Control Panel
- Your First Domain and Mailbox
- Connecting a Mail Client
- DNS for Your Domain
2. Secure it
3. Operate it
- Monitoring and Health
- Backup and Restore
- Troubleshooting
- Diagnosing Stalled Mail
- Relocating an Installation
- Upgrading hMailServer
- Upgrading Guide
- Migrating the Database Backend
- High Availability Runbook
- Warm Standby
- Runbooks Digest
4. Extend it
- Rules and Sieve
- Aliases Lists and Public Folders
- Routes and Relays
- The COM API and Scripting
- The REST API
- 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