Mail filtering — Sieve (RFC 5228) + ManageSieve (RFC 5804)
A complete, standards-based filtering subsystem alongside the existing proprietary rules engine.
- Sieve interpreter. RFC 5228 lexer + recursive-descent parser building a validated AST. Control flow (if/elsif/else/stop), the core tests (true/false/not/allof/anyof/header/address/exists/size with :is/:contains/:matches, address parts :all/:localpart/:domain, and the default + i;octet comparators), and the core actions (keep/fileinto/discard/redirect plus implicit keep).
- Live during local delivery. Each recipient account's active script is evaluated as the message is delivered: fileinto routes into the named IMAP folder, discard silently drops, redirect queues a copy to another address (reusing the forwarding copy/loop-guard/SRS path), and the implicit keep delivers to INBOX. A no-script account has zero overhead; an unparseable script never breaks delivery (it is logged and falls through to keep).
- Per-account script storage on disk under {DataDirectory}\Sieve\{domain}\{localpart}\, surfaced through the COM API as the Account.SieveScript property (and Utilities.CheckSieveSyntax / Utilities.EvaluateSieveScript).
- ManageSieve service (RFC 5804). An optional listener (enabled via [Settings] ManageSieveServerPort, disabled by default) for managing multiple named scripts over TCP: CAPABILITY, SASL PLAIN AUTHENTICATE (against the regular account database), PUTSCRIPT/CHECKSCRIPT (syntax-checked), LISTSCRIPTS, GETSCRIPT, SETACTIVE, DELETESCRIPT, HAVESPACE, NOOP, LOGOUT, including non-synchronizing literals. Activating a script over ManageSieve is immediately honoured by local delivery.
- Control Panel. The account dialog gained a Sieve editor tab, and the feature settings pages gained a ManageSieve card.
Operability & observability
Health & readiness probes (on the metrics listener)
Enabled via [Settings] MetricsServerPort, the unauthenticated listener now serves Kubernetes-style probes alongside /metrics:
- /livez — 200 once the listener is up.
- /readyz — 200 when the server is running and the DB pool is connected, else 503 (so load balancers stop routing during startup/shutdown).
- /healthz — JSON: status, server state, database up/down, per-protocol session counts, uptime.
Prometheus metrics on /metrics
- Database — hmailserver_database_up (1/0) and hmailserver_db_connections{state="busy|available"} pool gauges.
- Database query latency + slow-query log (new in 6.2.3). Every statement is timed at the single backend-agnostic chokepoint (so MySQL, PostgreSQL, MS SQL and SQL CE are covered uniformly): the summary hmailserver_db_query_seconds (_sum + _count) gives average query latency without per-query histograms, and hmailserver_db_slow_queries_total counts slow statements. A new [Settings] SlowQueryLogMilliseconds (default 0 = log disabled, metrics still run) logs any statement at/over the threshold with every SQL string literal redacted to '?' so inlined secrets never reach the log.
- TLS — hmailserver_tls_handshakes_total and hmailserver_tls_handshake_failures_total.
- Delivery queue — hmailserver_delivery_queue_messages (cached 10 s so frequent scrapes never issue a COUNT(*) per request).
- Authentication — hmailserver_auth_success_total / hmailserver_auth_failures_total, incremented from the central logon path (every protocol) for credential-stuffing / brute-force alerting.
- Delivery outcomes — hmailserver_messages_delivered_total, hmailserver_messages_deferred_total, hmailserver_messages_bounced_total.
- Command latency — the summary hmailserver_command_processing_seconds (_sum + _count) across every SMTP/IMAP/POP3 command line.
Reliability & durability
- Connection-pool condition variable — the DB pool no longer busy-polls with Sleep(10) while waiting for a free connection; it blocks on a condition variable signalled on release, removing scrape/latency jitter under pool exhaustion.
- Graceful shutdown drain — on shutdown the server moves to a stopping state (so /readyz returns 503) and, if [Settings] ShutdownDrainSeconds > 0, waits up to that window for active sessions to finish before tearing down listeners.
- Default 0 preserves immediate stop.
- Configurable message-store fsync — with [Settings] MessageStoreFsync = 1 a received message is forced to physical disk before the server acknowledges it to the sender. Default 0 keeps the previous OS-buffered behaviour.
- Message-store consistency check + recovery report — opt-in via [Settings] MessageStoreConsistencyCheck = 1: a scheduled, read-only task cross-checks every message row against its file on disk, publishes hmailserver_messagestore_missing_files, and writes a timestamped recovery report listing any affected messages. It never deletes or repairs anything.
- Log retention — [Settings] LogDeleteDays (default 0 = disabled) deletes hMailServer's own date-stamped log files older than the configured age. Only hmailserver_*.log / ERROR_hmailserver_*.log are ever touched.
Tracing
- Message-to-session correlation IDs — the Received header now carries an RFC 5321 id clause equal to the numeric TCP session id, and that same id prefixes every line of the session log, so a delivered message can be traced back to the exact session that received it with no external trace system.
High availability
- Active/passive runbook — a documented, validated topology (shared external database + shared message store + floating VIP) with readiness-gated, graceful- drain failover, in
hmailserver/docs/HighAvailabilityRunbook.md.
Deliverability & SMTP standards
- BATV (prvs) backscatter protection. Local outbound MAIL FROM is signed prvs=<K><DDD><6-hex HMAC>=local@domain (wire-only; the envelope domain is preserved so SPF/DKIM stay aligned). Inbound bounces to a prvs return-path are HMAC/day-window validated and stripped back to the original recipient, while forged null-sender bounces to invalid tags are rejected. Default-off via [Settings] BATVEnabled / BATVSecret.
- CHUNKING / BDAT (RFC 3030). EHLO advertises CHUNKING; message data may be submitted as one or more byte-counted BDAT chunk-size [LAST] commands instead of a dot-terminated DATA stream. Chunk payloads are read verbatim (byte-transparent: no dot-unstuffing, no <CRLF>.<CRLF> terminator) and concatenated into one message; DATA after BDAT is rejected 503 and a non-numeric chunk-size 501.
- Implemented additively so the existing DATA path is untouched.
Supply chain & quality gates
- SBOM — a workflow emits SPDX + CycloneDX inventories (Syft) on every push to master and attaches them to each published release.
- Dependabot — CVE / security-advisory alerts and grouped weekly update PRs for the NuGet packages and GitHub Actions.
- Dependency-review PR gate — fails any PR that introduces a high/critical-CVE dependency.
New hMailServer.INI [Settings] keys (all backward compatible)
(The metrics listener itself remains controlled by the existing MetricsServerPort / MetricsServerBindAddress keys.)
The Control Panel requires the .NET 8 Desktop Runtime (bundled by the installer and installed silently when missing).