6.2.22-pre2 (build 27) A second pre-release of 6.2.22, fifteen commits on from pre1. Still a pre-release: it is gated but not yet run in anger anywhere. Upgrading from pre1 requires DBUpdater — the database schema moves to 6015. Reporting: this server now sends the reports it has always asked others for DMARC aggregate reports (rua, RFC 7489). hMailServer has consumed DMARC policy for years and produced nothing for the domains whose mail it receives. It now aggregates every evaluation per UTC day and per policy domain — passes included, because a report carrying only failures tells a domain owner nothing about whether their legitimate mail aligns — and mails the Appendix C XML to the addresses a domain asks for with rua=. Inert until DmarcRptFromAddress is set. RFC 7489 §7.1's external-destination verification is enforced: a rua target outside the policy domain's own organizational domain is only used when ._report._dmarc. says it wants the reports. Without that check, anyone could publish a rua naming somebody else's mailbox and point every reporter on the internet at it. Forensic (ruf) reports are deliberately not implemented: a failure report carries the whole message, and the large receivers stopped sending them years ago for that reason. TLS-RPT reporting became verifiable. The machinery existed, but the task only mails days that are over, so an administrator who had just configured it had nothing to look at until tomorrow — and nothing could test it. Utilities.SendTlsRptReports(IncludeCurrentDay) sends what has been collected now. It refuses, with the statistics preserved, when TlsRptFromAddress is unset, because the send pass pops each day destructively and a diagnostic must not destroy the data it was asked to show. SendDmarcReports is its twin. Authentication Per-account lockout. The per-IP auto-ban cannot see a distributed attack: a botnet spending one guess per address per account never crosses any single address's threshold. AccountLockoutThreshold counts by the name being guessed at — the one thing such an attack cannot vary — and locks it. It is enforced on every path a password can be guessed through, SCRAM included, and every mechanism that authenticates clears the counters on success. Off by default. A locked name is refused with the ordinary invalid-credentials reply, and that refusal is deliberately kept out of the per-IP auto-ban: the person most likely to retry a locked name is its owner, with the correct password, and charging those attempts would ban an innocent office NAT for everyone behind it. POP3 AUTH-RESP-CODE (RFC 3206). Credential failures now carry [AUTH] and a failed inbox load carries [SYS/TEMP], so a client can tell "your password is wrong" (reprompt) from "the server is having trouble" (retry quietly). Without it, a transient database problem made scheduled clients nag their users for a password that was never wrong. POP3 PIPELINING and IMPLEMENTATION (RFC 2449). PIPELINING was advertised only after batched commands were proved to work across single TCP segments and after confirming the STLS injection defence still holds; IMPLEMENTATION answers the product name and deliberately no version. APOP and the CRAM-MD5 family are now documented, at the code, as decisions rather than a backlog: each needs a cleartext-equivalent stored secret that this server's Argon2id/SCRAM store rightly cannot produce. Anti-virus A scan that could not run is no longer the same as a clean message. Previously such a message was delivered with one line in the error log, which is indistinguishable from having been scanned and found clean. AVFailAction names that posture: 0 (the default) keeps today's behaviour exactly, 1 holds the message, re-attempts it, and returns it to the sender rather than ever delivering it unscanned. IMAP BINARY (RFC 3516) — FETCH BINARY[…], BINARY.PEEK[…] and BINARY.SIZE[…] decode a part's Content-Transfer-Encoding server-side, so a client saving an attachment stops downloading base64 it must immediately decode itself. Partial ranges apply to the decoded bytes. APPEND accepts the literal8 (~{n}) form. OBJECTID (RFC 8474) — EMAILID follows a message through copies, so a client can recognise mail it has already downloaded when it reappears in another folder; MAILBOXID survives RENAME. THREADID is answered NIL, which the RFC provides for, rather than inventing one. Schema 6015. Fixes An idle server now stops in about half a second rather than up to two: the work-queue join retried on a 250ms interval after a 1ms wait, so whether a shutdown cost nothing or two seconds turned on scheduler timing the server does not control. hMailServer.exe /Test no longer terminates on an unhandled exception when the machine has no network, or when a third party's DNS breaks: the SPF self-test evaluated a live policy for a domain that now answers SERVFAIL, and signalled failure with a bare throw; — which outside a catch block calls std::terminate. Control Panel Nine settings the server acts on had no field at all and were reachable only by hand-editing the ini: the virus-scanner failure policy, the per-name lockout, DMARC report settings, and the POP3 fetcher's OAuth2 host list. Each now sits on the page where the question is asked — the scanner policy beside the scanners, the lockout beside auto-ban — and carries the caveat, not just a label. Known limitations in this pre-release FETCH BINARY on a composite section returns an empty literal. The whole message (BINARY[]), a section naming a multipart part, and a section naming a message/rfc822 part all come back empty, because the decode path serves the entity's raw text and a multipart entity's text stops at the first boundary. Leaf parts — the attachment case BINARY exists for, and the one the tests cover — are correct. Found by the pre-release review; the fix belongs with the partial-fetch code rather than in a release cut. DMARC report data lives in memory, so a restart before the daily send loses that day's statistics, and a resolver outage during the hourly run discards the day it was about to report. Aggregate reporting is best-effort observability, not a ledger. What the pre-release review changed The diff since pre1 was reviewed adversarially before this was cut, and three of the four things it found were in code or text written in the previous two days: DMARC reports could be schema-invalid — p=None or a missing p= reached the XML verbatim, and RFC 7489 types those as a closed lowercase enumeration, so validating receivers discarded the whole report while the log said it was sent. Policy tags are now mapped onto the enumerations. Report ids collided: the id (and the Message-ID built from it) was day + second + submitter with no mention of the domain being reported on, so two domains reported in the same second shared them. Fixed in both reporters. The new Control Panel field for FetchOAuth2Hosts said "empty = none" and rendered blank while the server defaults it to outlook.office365.com — and saving that page would have written the blank back, silently stopping Microsoft 365 fetching. Both OAuth2 host fields now show the real default. Provenance Built from 6f01f5f, gated at 1650/1650 on the stamped binary, with 630/630 Control Panel tests. SBOMs and cosign signatures are attached by CI after publication; the installer is smoke-tested by CI against this tag. The installer is deliberately not run on the build machine.