Repository navigation
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. The figures and the passages on what came after 6.3.3 were read again against master 540347500 on 24 September 2026 (schema 6096), and against 92b8f9f63 (schema 6103) on 26 September 2026; the release is the tag v6.3.4 (51e2dbda0, schema 6104), which adds a last batch to that tree, and the schema, the IMAP capabilities and the 6.3.4 figures below that it changes are read from the tag. What this page marks 6.3.4 is in that release, and 6.3.3 did not have it. Brought up on 5 October 2026 to the tag v6.3.6 (e3c448aa6; 6 October 2026, schema 6108), for what 6.3.6 changes on this page: the version and schema stamps, the libraries, QuickJS, the live update's trust and states, HttpProxy's callers and the auto-ban's never-ban names. 6.3.7, the latest release (17 October 2026, schema 6108), is not yet reflected here, but for the version stamp, the published releases and the schema each release requires (section 1), and the releases the release jobs have made (the quality gates).
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). Since 6.3.4 (schema 6042), that overlay is the store: the [Settings] keys (241 in the baseline) are read from and written to hm_inisettings, the section in the file is a copy the server keeps current where it may write the file, a key with no stored row is adopted from the file, an edit to a stored key is reported as HM5804 and the stored value used, the line put back where the service may write the file (under NT SERVICE\hMailServer, to which the file is read-only, the edit stays in the file and the copy is not refreshed - The Service Account), a [SettingsOverride] section is applied over the store at every start and reported as HM5807, PasswordPepper stays in the file only and out of the backup, and values are stored trimmed (source: Server/Common/Application/IniSettingStore.h, IniSettingStore.cpp Synchronize, IniFileSettings.cpp:942,1015). build/check-ini-coverage.py refuses a new key under [Settings] against build/ini-settings-baseline.txt (263 keys across five sections when the rule was made, 238 of them in [Settings]; 270 in 6.3.4, 241 in [Settings], all with an editor, and 29 in the bootstrap sections, among them [Database] TlsMode, TlsCaFile and [Security] StoredSecretsKey) - a new setting belongs in hm_settings.
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.3.7, build 46 (source at the tag v6.3.7,
31d656c0c: Server/Common/Application/Version.h:6-8HMAILSERVER_VERSION "6.3.7",HMAILSERVER_VERSION_NUMERIC 6,3,7,46,HMAILSERVER_BUILD 46); 6.3.6 was build 45 (read at the tag v6.3.6,e3c448aa6), 6.3.5 build 44 (read atv6.3.5), 6.3.4 build 43 (HMAILSERVER_VERSION_NUMERIC 6,3,4,43at92b8f9f63; hmailserver/installation/section_setup_64.iss:2,6,10OutputBaseFilename=hMailServer-6.3.4-x64,AppVersion=6.3.4,VersionInfoVersion=6.3.4.0) and 6.3.3 build 42. 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), v6.3.0 (10 Sep 2026, the first with Linux packages), v6.3.1 (11 Sep 2026), v6.3.2 (13 Sep 2026), v6.3.3 (15 Sep 2026), v6.3.4 (27 Sep 2026), v6.3.5 (29 Sep 2026), v6.3.6 (6 Oct 2026), v6.3.7 (17 Oct 2026, latest). 6.3.4 is Authenticode-signed and carries no Sigstore bundle, so it is installed by hand; its Linux packages are x86-64 only (hmailserver_6.3.4_amd64.deb,hmailserver-6.3.4-1.x86_64.rpm), with no AArch64 package and no AppImage. 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, and work first in 6.3.4 is marked 6.3.4. - Required database schema version was 6031 when this page was first written, at 6.2.27; 6.3.3 requires 6040, 6.3.4 6104, 6.3.5 6105, and 6.3.6 and 6.3.7 6108 (next bullet but one) (source at 6.2.27: 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. -
6.3.6 requires schema 6108 (source at the tag v6.3.6,
e3c448aa6: Server/Common/Application/Constants.h:1276#define REQUIRED_DB_VERSION 6108; the three steps after 6.3.5's 6105 areUpgrade6105to6106*,Upgrade6106to6107*andUpgrade6107to6108*- the external accounts' days to keep translated, the greylisting key finished,hm_neverbannamesandhm_tcpipports.portsmtpauth); 6.3.5 required 6105 (hm_quarantine.quarantinehelo) and 6.3.4 6104 (source at the tag v6.3.4,51e2dbda0: Constants.h:1224#define REQUIRED_DB_VERSION 6104; the threeCreateTables*.sqlscripts insert 6104; DBUpdater's stepnew UpgradeScript(6103, 6104), Tools/DBUpdater/formMain.cs:314). 6.3.3 is at 6040, and sixty-four steps follow it to 6104; Changes-Since-6210 section 2 has each. The last eight, after master540347500's 6096: 6097 the four eDiscovery tables, 6098hm_storeblobsand the two compression settings, 6099hm_accountkeysandhm_domainrecovery, 6100hm_inboundhooksandhm_inboundhookqueue, 6101hm_accounts.accountkind, the four booking tables and two columns onhm_messages, 6102 every MySQL and MariaDB table converted to utf8mb4 (the other backends move only the version), 6103accounttotpsecretwidened to 1024, 6104hm_fetchaccounts.faorigin- who set a fetch account's server and user name, so that one a person made is fetched only from a public address (unlessReaderFetchPrivateHostsis on) and one made without the server's authority is never given the server's own OAuth2 token. The schema was 6045 at3233b0cc4, and the first five steps after 6.3.3 were: 6041hm_calendarsandhm_calendarobjectsfor CalDAV; 6042 no DDL - the version itself is the fence that keeps an older server off a database whose settings store is the database; 6043hm_audit,hm_alertrulesandhm_alerteventswith the six shipped rules, and ninehm_settingsrows each inserted only when no row of that name exists; 6044 sevenhm_domainscolumns for the external-sender tag, the first-contact note and the disclaimer, andhm_knownsenders; 6045hm_remotedomainpolicies(source: DBScripts/Upgrade6040to6041MSSQL.sql through Upgrade6044to6045MSSQL.sql, with MSSQLCE, MySQL and PGSQL variants).build/check-upgrade-inserts.py(run bystyle.yml) refuses a setting row inserted with plainVALUESin any upgrade step from 6030 on. - 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)
In 6.3.4 the SMTP and POP3 sets below are 6.3.3's, unchanged, and IMAP's names the same capabilities with two conditions added (checked at master 540347500, at 92b8f9f63 and at the tag v6.3.4): SendEHLOKeywords_ emits the same keywords and POP3's ProtocolCAPA_ answers the same list; IMAPCommandCapability.cpp now offers STARTTLS only before TLS is active and before the client has signed in, and COMPRESS=DEFLATE only once it has signed in, since those are when the commands are taken (the audit before the release; CHANGELOG 6.3.4). What changed is how IMAP answers, where Dovecot's imaptest found it did not follow the RFCs: a command pipelined behind an APPEND with a non-synchronising literal is answered (mbsync copied one message and stopped); APPEND stores the literal byte for byte, so RFC822.SIZE of a newly appended message is two octets smaller than before; a mailbox that does not exist, or a right the account lacks, is NO rather than BAD, and a failed SELECT or EXAMINE leaves no mailbox selected; an unknown LIST selection or return option is BAD; every BODY[<section>] without .PEEK sets \Seen; SORT uses every criterion and THREAD builds the RFC's trees; and the first session told of new messages counts them \Recent (commit 90df29ac5; Changes-Since-6210 section 5). A mailbox encrypted at rest is signed in by IMAP and POP3 only with its own password, and a message only an unreachable object store holds is answered IMAP NO [UNAVAILABLE] and POP3 -ERR [SYS/TEMP]. What 6.3.4 added to the SMTP conversation is refusals rather than capabilities - the SMTP policy's null-sender and SPF TempError answers, the domain black list at MAIL FROM, greylisting's abuse lock, restricted delivery, a mail flow rule's reject at the end of DATA, a spam trap's unknown-user answer - and they are in Stopping Spam and Changes-Since-6210 section 4e.
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) -
Remote domain policies (6.3.4; schema 6045): per remote domain or wildcard pattern, the most specific active record governs. Outbound TLS
none/encrypted/verified/dane, combined with MTA-STS and DANE as the strongest of the three and applied to routed deliveries too; a delivery it cannot satisfy is deferred with 4.7.0 and a sentence naming the policy, never sent in the clear. Inbound:530 5.7.0atMAIL FROMon an unencrypted, unauthenticated session from the domain, incoming relays exempt. Limits: a message over the size bounced at once with 5.3.4; connections and messages a minute deferred with 4.4.5. Switches for automatic replies and for forwarding (Sieveredirectincluded). Backup-MX recipient callout atRCPT TO: null-sender probe, only a 5xx refuses (550 5.1.1), cached 60 min (1 min for no verdict), 10 s timeout (at most 60), 10 per minute per remote, at most two hosts, never for a hosted domain. Edited in the Control Panel (Mail flow & delivery > Remote domains), the Deck,/api/v1/remote-domainsand COMSettings.RemoteDomainPolicies; carried by the backup with the settings (source: Server/Common/BO/RemoteDomainPolicy.h/.cpp; Server/SMTP/ExternalDelivery.cpp; SMTPConnection.cpp; RecipientCallout.cpp; RemoteDomainThrottle.cpp; Common/Util/RestApiRemoteDomains.cpp; docs/RemoteDomains.md). See Routes and Relays §14.2a. -
The connection to the database uses TLS where it is offered (6.3.4): MySQL and MariaDB through MariaDB Connector/C had never used TLS, whatever the documents said, and now do whenever the database server offers it; SQL Server through MSOLEDBSQL 18 and SQL Native Client encrypts the whole session; PostgreSQL is unchanged.
[Database] TlsMode(empty forprefer,disable,require,verify-full) andTlsCaFilechoose otherwise, anything else refused (HM7530); the application log says at every start what the connection got, and--check-databasesays the same (source: IniFileSettings.cppReadIniSettingString_("Database", "TlsMode", "");hmailserver/docs/DatabaseTls.md). -
A certificate for an IP address is accepted on Linux (6.3.4): an HTTPS endpoint named by its address - an object store, a webhook, an identity provider, the update feed - was refused when its certificate named the address in an
iPAddressentry, where RFC 5280 puts it; and no server name is sent for an address (hmailserver/docs/DatabaseTls.md). -
A new installation is secure before anyone configures it (6.3.4): a database the installation created - never an upgraded or attached one - gets a self-signed certificate (RSA 2048, 825 days), STARTTLS optional on 25, 587, 110 and 143, and TLS required for authentication on the Internet range, through
--apply-new-install-defaults, run once by the Windows installer and by the Linux unit's first start, and from the release after 6.3.7 by the Compose file's and the Helm chart's first start (the charts of 6.3.6 and 6.3.7 do not run it); the AppImage and any other container run it by hand. The setup checklist's Passwords only over TLS item judges every server (hmailserver/docs/SecureDefaults.md; Ports Reference 3.1). - 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, GitHub issue 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:820 at master540347500) — not in README. -
SPF, since 6.3.4: RMSPF (RFC 4408, its own
DnsQuery_Alookups, a refusal shim on Linux that made every check TempError, an unconditional pass for127.0.0.0/8and::1) is replaced by an RFC 7208 implementation ported from upstream (Server/SMTP/SPF/SPFEvaluator.cpp,SPFRecord.cpp,SPFMacroExpander.cpp,SPFSyntax.cpp,SPFAddress.cpp), which asks DNS through the server's own resolver (SPFDnsResolveroverDNSResolver::GetRecordsOfType, with the DNSSEC gate TXT lookups already had) and so works on Linux and honoursDNSServer. Loopback clients get no pass. All seven results are written toAuthentication-Resultsunder their own RFC 8601 keywords; scoring is unchanged (a fail scores, a pass is recorded). The openspf.org RFC 7208 suite, 203 cases, runs inSPFTesterfromClassTester::DoTestsand on Linux as thespf-testsbinary (source: SPF.cppSPFTester::Test; SPF/Conformance/README.md; CMakeLists.txtadd_executable(spf-tests). -
Domain transforms (6.3.4; schema 6044), per domain and off by default:
external_tag_subject(the tag text,[EXTERNAL]when empty, at the front of the Subject - breaks a sender's DKIM signature over it),external_tag_header(X-hMailServer-External: YES),first_contact_tip(X-hMailServer-First-Contact: YES, answered fromhm_knownsenders), applied at local delivery to the recipient's copy; anddisclaimer_enabledwith plain and HTML texts, appended to the text parts of outgoing mail on the SMTP accept path only - not to mail sent from the webmail or by send-later, nor to a signed or encrypted message, an automatic reply, a bounce or a list posting. Outside means neither authenticated (read from this server's own topmostReceived), nor handed over by an incoming relay, nor with both envelope sender andFromat hosted domains (source: Common/Util/MessageOrigin.cpp, ExternalSenderTagger.cpp, DisclaimerAdder.cpp; SMTP/LocalDelivery.cpp:430; SMTP/SMTPConnection.cpp:2316; docs/DomainTransforms.md). See Stopping Spam §10.7a. - 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. Since 6.3.6 a never-ban list by reverse name (
hm_neverbannames, schema 6108;Settings.AutoBanNeverBanNames,/api/v1/never-ban-names, the Auto-ban page, the Deck's Lists view) spares an address from all three auto-bans when one of its reverse names is covered by a pattern (mail.example.net, or.example.netfor it and every name beneath) and resolves back to it, andAutoBanWidenByReverseName(off) widens a ban to the contiguous run of addresses around it inside its IPv4/24whose reverse names share its registrable domain, forward-confirmed, never across a never-ban entry or an administrator's IP range (AutoBan.md; work items #55 and #56). 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). -
6.3.4, every one off until it is switched on unless it says otherwise, and every setting a row of
hm_settings(Settings Reference section 17): sender allow and block lists with URL entries and quarantine digests (6046); DKIM key rotation (6048); bounces and a suppression list (6049); country blocking from a MaxMind database (6052); complaints from the feedback loops (6055); the reputation watch (6057; on in a new installation); spam traps (6058); mail flow rules for a domain, with reject, require-TLS, quarantine and disclaimer actions (6063) and an outbound content policy of card numbers, IBANs, account numbers and attachment types (no step); compromised-account detection, inwatchby default (6071); greylisting keyed by a digest, with a sliding wait, an abuse lock and a bypass list that needs SPF, DKIM or DMARC (6072); SPF SoftFail, PermError and TempError scores, a TempError deferral, a multi-recipient null sender refused or banned, and a domain white and black list by envelope domain or HELO (6073); restricted delivery (6084); impersonation protection (6087); throttling per destination and per tenant, with a back-off that is on at 2 minutes (no step); IP warm-up (6093); and a sandbox for attachments (6091). Changes-Since-6210 section 4e has each.
- 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; since 6.3.6 the outbound/fetch XOAUTH2 token request does too, a port inOutboundOAuth2TokenUrlis used, and the request is given up after 20 seconds whole. -
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.
-
6.3.4: administrator roles -
serveradmin,domainadmin,helpdesk,auditorand custom ones over 27 permissions (24 before eDiscovery's two and the inbound webhooks' one), decided in one place for COM and REST (6047); the password breach check by k-anonymity (off); sessions and devices with revocation, and session policies per server, domain and account (6053); a recovery address, ten one-time recovery codes and a self-service password reset (6076); passkeys (WebAuthn, 6077) and single sign-on into the webmail and the Deck by OpenID Connect or SAML 2.0 - not into IMAP, POP3 or SMTP, which still take the password; and SCIM 2.0 provisioning at/scim/v2for an identity provider (6096).hmailserver/docs/Roles.md,PasskeysAndSingleSignOn.md,AccountRecovery.md,Scim.md. -
6.3.4, secrets at rest (6103): an account's TOTP secret and ten
[Settings]secrets (SRSSecret,BATVSecret,OAuth2HmacSecret,OAuth2IntrospectionClientSecret,OutboundOAuth2ClientSecret,OutboundOAuth2FixedToken,MetricsServerAuthToken,MetricsServerAuthPassword,ServiceAccountPassword,GssapiServicePassword) are sealed with AES-256-GCM (HMK1:) under the installation's stored-secrets key, kept in each server's[Security] StoredSecretsKeyunder DPAPI or the Linux key file and never in the database or a backup; the database records its id (StoredSecretsKeyId). Off after an upgrade until--create-stored-secrets-key; a new Windows installation's installer makes it. The administrator password is PBKDF2 from the installer, and an MD5 or SHA-256 value is hashed again at the next administrator sign-in (HM7494 when the file cannot be written) (hmailserver/docs/SecretsAtRest.md). -
6.3.4, mailbox encryption at rest (6099): per account, off on every account, and only after the domain's recovery key has been made: every message stored from then on is encrypted to an X25519 key of the account's own (AES-256-GCM), which the account's password opens at sign-in and which closes
MailboxKeyIdleMinutes(15) after its last use; app passwords are refused for such a mailbox, and it cannot be combined with zero access or a TOTP second factor. REST and the Control Deck only (hmailserver/docs/EncryptionAtRest.md). -
6.3.4, a low-privilege service account on Windows: a new installation runs the service as
NT SERVICE\hMailServer, an upgrade keeps its account;hMailServer.exe /GrantServiceAccessgrants the program folder's Data, Logs, Temp and Database folders and the folders an administrator approved;hMailServer.INIis read-only to the service; the live update's apply, the auto-ban's firewall rules andUtilities.MakeDependentare refused under it with a sentence;/Registertakes the account from--service-accountalone (hmailserver/docs/ServiceAccount.md).
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) -
Event scripts on Linux (6.3.4): JavaScript, in the QuickJS engine (release 2025-04-26, vendored as source under
Server/quickjs), for the same fifteen events under the same function names with the same objects;ScriptLanguagetakes a third word,JavaScript. Windows keeps VBScript and JScript and refuses JavaScript at load; Linux refuses the other two. A JavaScript handler has no file, process or network access,ScriptTimeoutand a 64 MB ceiling bound it, andGET /api/v1/statusanswersbuild.script_languages(hmailserver/docs/EventScripts.md; The COM API and Scripting 23.6). -
The JScript escaping fix (6.3.4, commit
5a3de9001; CVE-2026-100741, issue #43): up to 6.3.3 a JScriptOnClientValidatePasswordhandler could be made to run code in the server by a password ending its string with a backslash, before authentication; the same shape was open throughOnExternalAccountDownload's UID andOnDeliveryFailed's error text and recipient. Every JScript value is now escaped in one helper, and a rule's Run script function name must be a plain identifier (HM5018 otherwise). VBScript servers were not open through those values. - 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_store_bytes and hmailserver_store_messages (6.3.4 only; recomputed at most hourly by
Reports::CachedStoreTotals), 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). -
Backup completeness (6.3.4): the archive carries every per-account store 6.3.3 and earlier left out and a restore therefore deleted -
hm_contacts,hm_accountprefs,hm_scheduled(by folder path and UID, not ids),hm_files,hm_smimekeys,hm_passwordhistory,hm_calendarswithhm_calendarobjects, andhm_knownsenders- plus each message's keywords; the verified restore counts those rows back per table and discards an archive that does not hold them.build/check-account-stores.pyfails when a table that reacheshm_accountsis neither carried nor declared an exception with its reason; in 6.3.4 the declaration names 62 per-account tables, 24 carried and 38 declared exceptions (9, 16 and 23 at3233b0cc4, where the check was made and passed 84 checks, in GitHub'sstyle.yml) (source: Common/Application/AccountStores.cpp; BackupExecuter.cppVerifyRestore_; Common/BO/Message.cppKeywords; test/RegressionTests/Infrastructure/BackupAccountStores.cs, 10 tests). The audit trail and the alert rules and events are not in the archive. See Backup and Restore §15.1a. -
Syslog export (6.3.4): a copy of every log entry as RFC 5424 over UDP (1,024-byte limit), TCP or TLS (octet-counted, certificate verified), or on Linux under systemd with no host to the journal with
HMAILSERVER_*fields; sevenhm_settingsrows (SyslogEnabled,SyslogHost,SyslogPort514,SyslogTransport0,SyslogFacility2,SyslogMinimumSeverity6,SyslogLogTypes86), twelve COM properties onSettings.Loggingand twelve keys of/api/v1/settings/logging; read at start; errors always sent; a 4,096-entry queue that drops the oldest (source: Common/Util/SyslogFormatter.cpp, SyslogSink.cpp; Configuration.cppEnsureSettingDefaults_; docs/Syslog.md). See Monitoring and Health §16.2a. -
Alerts (6.3.4; schema 6043): six conditions as rows of
hm_alertrulesat3233b0cc4-backup.failedandcertificate.expiringon,disk.low,queue.stalled,autoban.stormandminidump.writtenoff - and eleven at master540347500, the later steps addingdkim.rotation.stalled,complaints.rate.high,reputation.listed,account.compromisedandaccount.password-reset, each seeded on, so seven are on; sent once a minute byAlertTaskby mail (nothing withoutAlertSenderAddressand a recipient) and/or an HMAC-SHA256-signed webhook retried and then dead-lettered (HM6543); a daily digest atAlertDigestHourUTC; REST only for the rules and events, administrator password only (source: Common/Util/AlertManager.cpp; RestApiAudit.cpp). See Monitoring and Health §16.7a. -
Audit trail (6.3.4; schema 6043):
hm_audit, one hash-chained row per administrative change taken atDatabaseConnectionManager::ExecuteandPropertySet::Set*with a thread-local actor fromRestApiServer::ProcessRequest_andCOMAuthentication;GET /api/v1/auditand/verify, administrator password only; the Deck's Audit trail view. Incomplete at3233b0cc4: five watched table names do not match the real tables (domain aliases, incoming relays, white list, greylisting white addresses, blocked senders),hm_acl,hm_remotedomainpoliciesandhm_inisettingsare not watched, the twelve syslog COM setters install no actor, andbuild/check-audit-choke-point.py, which reports 13 COM gaps, is run by no workflow (source: Common/Util/AuditTrail.cppAuditedTables; DBScripts/CreateTablesMySQL.sql). See Monitoring and Health §16.7b. -
Reports (6.3.4):
GET /api/v1/reportsand/api/v1/reports/{traffic|failures|senders|recipients|mailboxes|volume|storage|summary}withfrom,to(at most 366 days),domain,top(1-200) andformat=csv; the first four need the message trace (MessageTraceEnabled=0by default);volumeandstorageare server-wide and refused to a domain-restricted key; spam, virus and storage growth per domain are stated as unanswerable; the metric history gainsstore_bytesandstore_messages; a Deck Reports view andhmctl report(source: Common/Util/RestApiReports.cpp, Reports.cpp; MetricsHistoryTask.cpp). See Monitoring and Health §16.6a. -
Storage compression and attachment de-duplication (6.3.4, schema 6098): with
MessageStoreCompressionon (off as shipped), a message delivered or appended is stored deflated and each MIME part of at leastMessageStoreDedupMinKB(256) is kept once for every message carrying the same bytes; every reader sees the message as it arrived, and a stored file is checked by its SHA-256 before it is served; the queue is never compressed.--measure-store,--compress-storeand--compress-store --decompress;GET /api/v1/store; COMSettings.MessageStoreStatus(hmailserver/docs/MessageStore.md). -
Object storage for the message store (6.3.4, no schema step): messages on an S3-compatible store (AWS S3, MinIO, Wasabi, Backblaze B2, Ceph RGW) or Azure Blob Storage with the database as the index, written to the local disk first and moved by a background committer; TLS to the store always verified. Off until an administrator names a store:
MessageStoreBackendisfileandMessageStoreObjectServiceempty as shipped.--copy-store-to-object-storage,--copy-store-from-object-storage(hmailserver/docs/ObjectStorage.md). -
SNMP (6.3.4, no schema step): a read-only SNMPv3 agent (authPriv only: HMAC-SHA-256 and AES-128; SNMPv2c optional and off) answering the mail MIB (RFC 2789) from the counters
/metricsserves. Off, and answering nobody untilSnmpAllowedAddressesnames a manager; UDP 161 by default; its settings on REST and applied at a restart, with no COM property and no Control Panel page; its enterprise arc is RFC 5612's documentation number (hmailserver/docs/Snmp.md; Ports Reference 4.6). -
eDiscovery (6.3.4, schema 6097): cases, searches across every mailbox, the deleted items and the archive, legal holds that outrank deletion, retention, a purge, a move to another domain and a full restore, and exports as EML and mbox with a SHA-256 manifest; permissions
view_discovery(theauditorrole) andmanage_discovery; every search, hold, export and download on the audit trail; routes under/api/v1/discovery/cases, a Deck view and a read-only Control Panel page (hmailserver/docs/Discovery.md). -
PST import (6.3.4, no schema step): a migration of kind
pst, one job per Outlook data file into one account, read by a reader of this project's own in a process of its own (hMailServer.exe --pst-read,hmailserver --pst-read), killed on a stop, a pause or a cancel, so a file that crashes it costs one message, not the service; ANSI files and an OST that is a PST in all but its name are read, the 4K-page layout, Windows Information Protection and an OST caching an Exchange mailbox refused. No PST job runs untilPstImportDirectorynames a directory, empty as shipped (hmailserver/docs/Migration.md). -
Mail in, to a webhook (6.3.4, schema 6100): an address or a whole hosted domain routed to a URL, the mail posted there parsed, in SendGrid's inbound-parse field names or as one JSON document, signed with the outbound webhooks' HMAC-SHA256 recipe and retried on their schedule; routes over REST and the Deck under
manage_inbound_webhooks, which onlyserveradminholds; no COM member and no Control Panel page; nothing on until a route is made (hmailserver/docs/InboundWebhooks.md).
-
The feed and the signers, as of 6.3.4. Up to 6.3.3 the default feed is the Releases API of the suspended account's repository on GitHub, which has answered 404 since 18 September 2026, and the one signer compiled in is this repository's GitHub workflow
sign-release.yml; such a server reads the new feed only withUpdateFeedUrl=https://updates.progressiverobot.com/releases.json. 6.3.4 readshttps://updates.progressiverobot.comby default, which GitLab Pages serves from the project's releases (UpdateChecker.cppSTABLE_FEED,PRERELEASE_FEED), and trusts two signers: the GitHub workflow for a certificate issued before the suspension and a release no later than 6.3.3, and the project's GitLab CI pipeline for av*tag, which is to sign the releases after 6.3.4 and has signed none yet, at the project's present path and at the path of the grouphmailserverit is to move to, where the certificate must also carry the project's id,86730559(SigstoreVerifier.cppDefaultTrust). A file signed for one tag is refused as any other release, and on Windows the installer's version resource must name the release it is installed as. 6.3.4 itself carries no Sigstore bundle, so every server installs it by hand: a live update offered it answersRelease 6.3.4 carries no Sigstore bundle for its installer, so it cannot be verified.and downloads nothing (UpdateDownloader.cpp;hmailserver/docs/UpdateFeed.md). On Windows theUpdatesfolder admits changes from SYSTEM, the Administrators and the service alone, and under the virtual service account the apply is refused: an administrator downloads the installer from the release page, checks its Authenticode signature and runs it. - 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) - inhMailServer.inionly, since 6.3.6: the five trust keys are read from the file's[Settings]section alone, and a change to one over COM or REST is refused (409). - 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; since 6.3.6 also 6 while the service prepares an administrator's install and 7 when it is ready),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 asThe proxy <proxy> refused CONNECT to <host:port>: <its status line>. The setting belongs toHttpsClient, so the OAuth2 JWKS fetch and token introspection take the same route; up to 6.3.5 those were all of it - ACME, the outbound XOAUTH2 token request and the MTA-STS policy fetch built their own connections and went direct whatever this was set to - and since 6.3.6 the ACME client, the outbound OAuth2 token request, the MTA-STS policy fetch, brand logos, Web Key Directory lookups, Web Push, one-click unsubscribe and inbound webhook routes held to public addresses go through it too, a request held to public addresses asking the proxy for a tunnel to the address the server checked, never the name. 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.3, Boost 1.92.0, PostgreSQL 18.6 libpq referenced from the project/build files at the tag v6.3.6 (source: build/Get-LibraryVersions.ps1:23-25
openssl=4.0.3,boost=1.92.0,postgresql=18.6;hMailServer.vcxprojnamesopenssl-4.0.3andpostgresql-18.6); 6.3.4 and 6.3.5 carried OpenSSL 4.0.2 and PostgreSQL 18.3, both bumped in 6.2.25, and 6.3.6 takes 4.0.3 (CVE-2026-84783 applies to the Windows server on a port that asks for client certificates) and libpq 18.6, built against it, into the x64 and ARM64 installers; 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. They are GitHub Actions workflows: none has run for the project since GitHub suspended its accountProgressiveroboton 18 September 2026, butsbom.ymlandsign-release.yml, which, in the trees of the old tags, ran once on 18 October 2026 on the copies of the releases up to 6.2.22-pre6 in the project's repository on GitHub, GitLab's mirror since that day, and have been disabled there since (Assurance Case); and since 26 September 2026 none of them is in the tree -.github/went from it that day, itsallowed_signers,hmailserver.openvex.jsonandupstream-syncare in.gitlab/, and the workflows are in the git history atv6.3.3. From batch49.github/holds files GitHub reads for its copy, the pull-request template and a newworkflows/scorecard.yml, and from batch51CODEOWNERS, which names the code owner a pull request there asks for review, and a newworkflows/codeql.yml, CodeQL over that copy in the place of GitHub's default setup: the two workflows of the project's that GitHub Actions runs, neither of them the workflow of that name listed above (Contributing). In 6.3.4 the project's own CI is defined in.gitlab-ci.yml: the platform-neutral checkers and the Deck's and the webmail's script harnesses on GitLab's shared runners, and, for a push tomaster, abatch*branch or av*tag only, the Linux build, the Linux suite, the packages, the container image and the upgrade gates on the project's Linux runner and the Windows build and suite on its Windows runner; the release jobs for 6.3.5 onwards (release,release-authenticode,release-sign,release-verify,release-publish), written and not yet run when 6.3.4 was made, which have made every release since - 6.3.5 on 29 September 2026, 6.3.6 on 6 October 2026 and 6.3.7 on 17 October 2026; security scanning (cppcheck, clang-tidy, Semgrep, Trivy, OSV) not yet blocking; Renovate inert until its token and schedule exist. When 6.3.4 was made none of the jobs on the project's own runners had yet run, and the batches' Linux suites were run by hand on that runner's machine (hmailserver/docs/ContinuousIntegration.md). - Vendored and bundled in 6.3.4: QuickJS 2025-04-26 (source, Linux event scripts; 2026-06-04 since 6.3.6, which fixes CVE-2025-62494, -62493 and -62492, reachable by a script, and adds iterator helpers, the Set methods, RegExp
v, Float16Array,Promise.tryandError.isError- ES2025), zlib 1.3.2, and on Windows 7-Zip 26.03 from 7-zip.org in place of 19.00; the .NET Desktop Runtime the installer bundles is pinned to 10.0.12 by version and SHA-512, and the SBOM names seventeen components, checked bybuild/check-sbom-inputs.py. Windows on ARM is written and not built: aRelease|ARM64configuration and a second installer script,hMailServerARM64.iss, exist, nothing of it has been compiled (the build machine lacks ATL for ARM64), and 6.3.4 has no ARM64 installer (hmailserver/docs/WindowsOnArm.md).
- 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; checkers that fail on an unmarked or untranslated caption (in CI on GitHub until 18 September 2026, by hand since); 4,142 texts in
Strings.resxat master540347500, with the seventeen catalogues held complete bycheck-localisation.py(source: hmailserver/source/Tools/ControlPanel/Resources/Strings.*.resx, 17 catalogues beside Strings.resx; Services/LanguageChoice.cs; .github/workflows/style.yml:72-87check-mnemonics.py,check-localisation.py,check-catalogues.py). - Every
[Settings]key the server reads has a Control Panel editor, checked bybuild/check-ini-coverage.py, instyle.ymlon GitHub until 18 September 2026, by hand after, and in thecheckersjob of the project's.gitlab-ci.ymlin 6.3.4 (source: .github/workflows/style.yml:95;.gitlab-ci.ymlcheckers) — new in 6.2.28. Since 6.3.4 the same check also refuses any new[Settings]key againstbuild/ini-settings-baseline.txt(241 keys at master540347500, the three work factors now counted; 238 when the rule was made), and the Control Panel writes those settings over COM only, into the database store, even when it runs on the server itself (source: Tools/ControlPanel/Services/IniFeatureStore.cs). -
REST surface, counted from the OpenAPI path literals in
RestApi*.cpp: 137 paths and 202 path-and-method operations in 6.3.3; 158 paths and 229 operations at3233b0cc4, 15 September 2026 (the additions since 6.3.3:/api/v1/groupswith its members, message copy,/api/v1/rules/match,/api/v1/calendar/expand, the remote domain policies, the reports, the audit trail, the alerts, and/deck-lang/{code}.json). The Control Deck had nineteen views there (fifteen in 6.3.3), andhmailserver/docs/DeckParity.mdcounts 365 properties the desktop program writes, all 365 writable over REST, 359 reachable from a view. At master540347500: 404 paths and 563 path-and-method operations from the literals (376 and 527 under/api/, the rest SCIM, the sign-in and reading pages and the list manager's pages). The literals do not show the settings groups, whose pathsRestApiSettings.cppwrites from its group tables - nine paths and 17 operations in 6.3.3, thirteen and 25 at540347500- so the document itself names 146 paths and 219 operations in 6.3.3 and 417 and 588 at540347500; 38 Deck views, and a census of 464 properties the desktop program writes, 456 writable over REST and 456 reachable from a view; the Control Panel had 64 pages to 6.3.3's 55, and its Ctrl+K index 447 entries to 6.3.3's 393. In 6.3.4 (92b8f9f63): 428 paths and 596 operations from the literals (400 and 560 under/api/), the same thirteen settings groups, and so 441 paths and 621 operations in the document (413 and 585 under/api/) - the twenty-four new paths are eDiscovery's nine, the inbound webhooks' seven, mailbox encryption at rest's four, a room's booking policy, the store's two and the webmail's bulk action; 40 Deck views (Discovery and Inbound webhooks added); a census of 466 properties the desktop program writes, 458 writable over REST and 458 reachable from a view (DeckParity.md); the Control Panel has 65 pages in eight groups (Discovery added, read-only), and its Ctrl+K index 448 entries (SettingsSearchIndex.g.cs). See APIs Reference §4-5. -
Configuration as a document (6.3.4):
hmctl config export|diff|applyand the module'sExport-/Compare-/Import-HmConfigurationover REST - deterministic, a plan unless--force, deletions only with--allow-delete. At3233b0cc4itsinisection was empty against a real server (the route listed names only); since the third batchGET /api/v1/settings/inianswers values with secrets masked, so the document is whole. An API key still stops the export with a 401 (source: Tools/Cli/hmctlread_configuration; RestApiSettings.cppIniList). See Command Line. -
Tutorial tours (6.3.4): one never-modal stepper in the Control Panel (seven stops, progress under
HKCU\Software\hMailServer\ControlPanelvalueTourProgress), the Deck (the same walk,localStoragehmsTours) and the webmail (four stops, kept in the account's preferences) (source: Tools/ControlPanel/Services/Tour.cs, TourProgress.cs, MainWindow.Tour.cs; installation/WebAdmin/index.htmlTOURS; Common/Util/Portal.jsTOURS). See The Control Panel §5.2a. - 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 (6.3.4) 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. At master540347500an account has several calendars with colours and several address books, and the ones shared with it - calendars by an account or a group, address books by an account or a whole domain - appear in its home, beside the organisation's directory as a read-only collection (schema 6064, 6078, 6079, 6086). 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; at3233b0cc4it did not do iMIP, free-busy, a per-calendar time zone, partialcalendar-data, or a time-range filter on an alarm; the tree since has iMIP (invitations sent, answered and filed on delivery; batch 12) and free-busy (GET /api/v1/me/freebusyand thefree-busy-queryreport; batch 20), and the other three are still not done (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. Since 6.3.4/Registerno longer reads either: it takes the account from--service-account virtual|localsystem|keep|DOMAIN\name$alone, and a new installation runs asNT SERVICE\hMailServer(hmailserver/docs/ServiceAccount.md). - 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(described then as MySQL's fall-back from a required TLS; the test lab found on 25 September 2026 that MariaDB Connector/C never asked for TLS, and since 6.3.4 the key is read and changes nothing,TlsModeandTlsCaFiletaking its place),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. -
[Security] StoredSecretsKey(6.3.4): the installation's stored-secrets key under this machine's DPAPI or the Linux key file, written only by an elevated step (the installer,--create-stored-secrets-key,--import-stored-secrets-key), never by the service; empty after an upgrade (source: IniFileSettings.cpp, StoredSecrets.cpp;hmailserver/docs/SecretsAtRest.md). -
[Database] TlsModeandTlsCaFile(6.3.4): see section 3 (source: IniFileSettings.cppReadIniSettingString_("Database", ...)).
-
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.7, the latest release (17 October 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.7 is the latest release (17 October 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