Alerts, the audit trail, syslog and the journal, and per-domain reports
-
Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) moves the Control Panel's Audit trail page, with journaling and eDiscovery, into a new Compliance group, and the alerts' settings, the outbound webhooks and the SNMP agent onto its Monitoring & alerts page (API & monitoring until 6.3.6; 6.3.5 and 6.3.6 had them on Logging); Ctrl+K takes an old title or route to its page. It also fixes the SMTP log, whose lines the syslog sink copies, which wrote some credentials in the clear with SMTP logging on, in every version from 6.0.0 to 6.3.6 (work item #95). See the 6.3.7 announcement.
This topic covers four things 6.3.4 adds for being told what the server is doing and who changed it: the alerts, the audit trail, the syslog sink (or the systemd journal on Linux) and the per-domain reports, with a paragraph on following one connection live. The one thing to know first: after the upgrade to 6.3.4 the audit trail and the alerts are on, with seven of the eleven conditions enabled, but nothing is mailed until
AlertRecipientandAlertSenderAddressare set.What 6.3.4 switches on without a switch
From the CHANGELOG's list of what changes on upgrade: the audit trail is on (schema 6043), and keeps every row until a retention is set. Compromised-account detection starts in watch mode (schema 6071): it raises
account.compromisedbut holds nothing untilcompromise_detection_modeis set toenforce. The reputation watch, which raisesreputation.listed, is on in a new installation and off after an upgrade.Alerts
A condition is a row in
hm_alertrules(on or off, threshold, mail or webhook or both, cool-down, at once or in the digest); an event is a row inhm_alertevents. Delivery isAlertTask, once a minute, and nothing else sends, so an alert about the delivery queue is never itself submitted into the stuck queue.Condition On as shipped Raised by Threshold backup.failedyes A backup that failed, and a scheduled backup that could not run. Sent at once — certificate.expiringyes The notAfter of every configured certificate, read once a minute 7 days disk.lowno The free-space warning band, and again below the floor; cleared on recovery — queue.stalledno A work queue whose every thread has been in the same task past AsyncQueueStallThreshold— autoban.stormno Auto-bans in the last hour 25 minidump.writtenno A crash dump newer than the last one recorded, in the log directory — complaints.rate.highyes A hosted domain's complaint rate over the feedback loops' seven-day window; inert while complaint processing is off, and a domain that sent fewer than 100 messages is not judged 1,000 per million dkim.rotation.stalledyes A rotation whose next key's record has not been seen for the threshold, or a running rotation whose last move failed 7 days account.compromisedyes Compromised-account detection at submission, one event per account per hour; in watchmode the detail says what enforcing would have done— account.password-resetyes A password reset completed through an account's confirmed recovery address; no cool-down and no digest — reputation.listedyes The reputation watch's stored rows; sent at once, and a check that was refused or timed out does not clear it — The four that are off are noisy on a healthy server.
The settings. All nine are rows of
hm_settings, none anhMailServer.inikey. They are in the server group ofGET/PUT /api/v1/settings, on the Deck's Settings page, and on Windows over COM throughSettings. The CHANGELOG and AlertsAndAuditTrail.md spell some in theirhm_settingsform (AlertRecipient,AlertSenderAddressand so on); the REST keys are these.REST key Default Meaning alerts_enabledon The master switch alert_recipientempty Where alerts and the digest go; a rule can name an addressof its own insteadalert_sender_addressempty What they are sent from; a rule with no sender sends nothing alert_digest_enabledon Whether rules marked for the digest wait for it alert_digest_hour7 The hour, UTC, the digest goes out; a digest with nothing in it is not sent alert_max_per_hour20 Past this many single alerts in an hour, everything goes into the digest alert_webhook_max_attempts5 Webhook attempts before an event is dead-lettered (1 to 20) audit_trail_enabledon Whether administrative changes are recorded audit_retention_days0 Days of trail to keep; 0 keeps everything Nothing may flap. A state condition already raised is not raised again; an occurrence inside the rule's cool-down is recorded but held for the digest; and the digest is one message listing what fired.
Webhooks. A rule whose
actionsis 2 (webhook) or 3 (mail and webhook) POSTs one JSON object per event (condition,state,severity,time,time_utc,summary,detail,server) with three headers:X-hMailServer-Timestamp,X-hMailServer-Signature: sha256=<hex>andX-hMailServer-Event. It is signed under the rule's shared secret, as every webhook the server sends is; the topic on integrating applications shows how to verify it.A rule that calls a webhook is refused without an address and without a secret;
webhook_secretis write-only and the listing says onlywebhook_secret_set. Onlyhttps, or plainhttpto a loopback address, is used. An attempt that does not get a 2xx is retried on the next pass, then after 1, 5 and 15 minutes, which is the default five attempts, and 60 minutes apart after that if the maximum is raised. After the last attempt the event is dead-lettered: kept, no longer retried, and reported once in the error log asHM6543.Route Does Permission GET /api/v1/alerts/rulesEvery condition and its rule view_configurationPUT /api/v1/alerts/rules/{condition}Change one, or create one for a condition nothing ships; a field left out keeps its value manage_settingsGET /api/v1/alerts/eventsWhat has fired, newest first, with the webhook's state view_configurationPOST /api/v1/alerts/testRaise a condition through the path a real one takes: {"condition":"disk.low","summary":"...","occurrence":false}manage_settingsPOST /api/v1/alerts/runRun one pass now manage_serverAll five are refused to every API key. The administrator password reaches them, and so does an account whose role holds the permission and reaches every domain. Neither the Control Panel nor the Deck has a page for the rules, and
hmctland the PowerShell module have noalertsorauditverbs;hmctl apireaches the routes.The audit trail
One row per administrative change made over COM, REST or the Deck, in
hm_audit: when (audittime, UTC); who (auditactor:Administratorover COM,administratororkey:<id>over REST, and an account signed in with its own password is its address over COM andaccount:<address>over REST and the Deck); the interface and, except over COM, the peer address; the object's type and name; the action (created,updatedordeleted, alsounlocked,cleared,movedandmove failed, the last for a mailbox move that failed and could not be put back); the detail (the columns the change carried, or for a settingName: old -> new, ending[role: <name>]under a role); and the two hashes of the chain.Both kinds of setting are recorded: the
hm_settingsvalues and the[Settings]keys (inhm_inisettingssince schema 6042), with the value they had. A key set for the first time readsSmtpTarpitCount: (default) -> 5; returned to its default it readsSmtpTarpitCount: 5 -> (default), with the actiondeleted. A write of the value a setting already has records nothing.What is never recorded as a value. A password, key, token, secret, hash, salt or passphrase is recorded as
(changed), decided by the name of the column or setting, matched as a substring, soMetricsServerAuthTokenandPasswordPepperare among them. Fifteen keys whose names only look like secrets, the sevenPasswordPolicy*keys among them, are recorded with their values. The server's own writes (delivery, retention, greylisting, the index) are never recorded.Verifying the chain. Each row carries the SHA-256 of the row before it and of itself.
GET /api/v1/audit/verifyanswers{"intact","rows_checked","first_broken_id","reason"}, and the reason tells a row that was edited from one that was removed. This makes tampering detectable, not impossible: whoever can write the table can recompute the whole chain. Rows hashed the older way, which only a build from before the 6.3.4 release can have written, did not cover a character above U+00FF; they still verify, with that weakness.Reading it.
GET /api/v1/audit?actor=&object_type=&action=&since=&until=&limit=&offset=answers newest first;actoris a substring, sokey:finds every change made with an API key, andlimitis 1 to 1,000, default 100. It needs the administrator password, or an account whose role holdsview_audit(theauditorrole) and is not confined to named domains. Every API key is refused, with 401 rather than 403, so that it learns nothing from asking.Putting a change back.
POST /api/v1/audit/{id}/revertwrites a setting's old value back through the same setter, so the put-back is itself a row in the trail. It needsmanage_settingsand is refused with 409 for a row that is not a setting's change, a secret's row, a value the trail cut at 120 characters, and a setting this version no longer has.Where to see it. The Deck's Audit trail view has the filters, paging, Verify the chain and Put back. On Windows the Control Panel has the same page, over COM, at Monitoring & troubleshooting → Audit trail (Compliance → Audit trail since 6.3.7; Ctrl+K takes the old route to it). Switching recording off is itself recorded. The trail, the rules and the events are not in the built-in backup.
What it holds for compliance. A download of a discovery export is recorded on the trail alone, so with the trail off it is recorded nowhere. An erasure is recorded with the address it erased wherever it deletes from recorded tables (aliases, distribution-list recipients and, with the archive included, the archive index), and
audit_retention_daysis how those rows age out. The compliance topic covers eDiscovery and the erasure.What it does not record. Reads; what a restore replaces (the request is one row,
restore started); creating or revoking a REST API key; releasing or deleting a quarantined message; creating, renaming or deleting a public folder; the[Security],[Directories]and[Database]sections ofhMailServer.ini; and a change nobody made through an interface. A change to the twelve syslog settings over COM, from the Control Panel's Logging page for one, is recorded in 6.3.4: the settings property setters carry an audit scope of their own. The wiki's syslog section, 16.2a, still describes the earlier state.Syslog and the journal (since 6.3.4)
Every entry the server logs can also be sent as RFC 5424 to a syslog collector over UDP, TCP or TLS. It is a copy: the files under
LogFolderare written as before. On Windows the settings are the syslog (RFC 5424) card on the Control Panel's Logging page; on Linux they are keys ofPUT /api/v1/settings/loggingand the Control Deck, andhmctl settings get loggingprints them. They take effect when the sink starts, at the next service start or Reinitialize, not when they are saved.hm_settingsnameREST key Default Meaning SyslogEnabledsyslog_enabledoff The whole feature SyslogHostsyslog_hostempty Empty is not "off": on Linux under systemd it selects the journal; on Windows nothing is sent, which the server says once in the application log SyslogPortsyslog_port514514 for UDP and plain TCP; 6514 is the registered port for TLS SyslogTransportsyslog_transport0(UDP)0UDP,1TCP,2TCP over TLS; the REST key takesudp,tcportlsSyslogFacilitysyslog_facility2(mail)0 to 23; 16 to 23 are local0tolocal7SyslogMinimumSeveritysyslog_minimum_severity6(informational)Entries less severe than this are not sent SyslogLogTypessyslog_log_smtp,syslog_log_pop3,syslog_log_imap,syslog_log_application,syslog_log_tcpip,syslog_log_debug86One bit mask: SMTP, POP3, application and IMAP on; TCP/IP and debug off Errors are sent whatever the mask says. A message looks like this:
<22>1 2026-09-15T14:21:09.284+01:00 mail.example.com hMailServer 4812 SMTPD [hmailserver@32473 thread="7412" session="41" client="203.0.113.9"] <BOM>RECEIVED: MAIL FROM:<[email protected]>The message id is the log category (
SMTPDand so on) or, on an error line carrying an hMailServer error number, that number, somsgid == HM5165is a usable filter at the collector. The structured data underhmailserver@32473carriesthread, andsessionandclienton a conversation line. UDP truncates each message to 1024 bytes, cutting the text and never the header; TCP and TLS frame each message with its length and truncate nothing below 65000 bytes. TLS verifies the collector's certificate against the machine's trust store and the configured host name, so set the host to the name in the certificate, not an IP address; no client certificate is offered.A collector that is down costs log lines, not mail. Entries go to a background thread over a queue of 4096 that drops the oldest when full; a stream that cannot connect backs off from one second to a minute; and the application log gets one line when an outage starts and one when it ends, with the number dropped. Nothing is retried from disk. Each step of a stream transport (the connection, the TLS handshake, one entry written) is given ten seconds, so a collector that accepts the connection and then goes quiet is a failure like a refused one, and a reinitialise or a service stop waits for that step at most.
For rsyslog, in
/etc/rsyslog.d/30-hmailserver.conf(UDP for the default transport, TCP forSyslogTransport = 1; thestopkeeps the lines out of/var/log/mail.logtoo, so drop it if you want both):module(load="imudp") input(type="imudp" port="514") module(load="imtcp") input(type="imtcp" port="514") if ($app-name == "hMailServer") then { action(type="omfile" file="/var/log/hmailserver.log" template="RSYSLOG_SyslogProtocol23Format") stop }Without
RSYSLOG_SyslogProtocol23Formatrsyslog reformats to the older RFC 3164 shape and the structured data is lost. For TLS (SyslogTransport = 2
module(load="imtcp" StreamDriver.Name="gtls" StreamDriver.Mode="1" StreamDriver.AuthMode="anon") input(type="imtcp" port="6514") global(DefaultNetstreamDriverCAFile="/etc/ssl/certs/ca.pem" DefaultNetstreamDriverCertFile="/etc/rsyslog.d/collector-cert.pem" DefaultNetstreamDriverKeyFile="/etc/rsyslog.d/collector-key.pem")A self-signed collector certificate has to be installed as trusted on the hMailServer machine, or the handshake is refused and nothing is sent.
The journal, on Linux only, needs no collector. Where the server runs as the packaged
hmailserver.service, switch the sink on and leave the collector host empty:$ hmctl settings set logging --set syslog_enabled=true $ sudo systemctl restart hmailserver $ journalctl -u hmailserver -fBeside the standard fields the journal gets
HMAILSERVER_CATEGORY,HMAILSERVER_MSGID,HMAILSERVER_THREAD,HMAILSERVER_SESSIONandHMAILSERVER_CLIENT:$ journalctl -u hmailserver HMAILSERVER_SESSION=41 $ journalctl -u hmailserver HMAILSERVER_MSGID=HM5165If a collector host is configured the journal is not used. The start of the sink is itself logged, as
Syslog: sending RFC 5424 log entries to <host>:<port> over UDP, facility 2.orSyslog: writing log entries to the systemd journal.; a server that logs neither has the feature off. Not sent: the AWStats log, the backup log and the event-script log.Per-domain reports (since 6.3.4)
GET /api/v1/reportsis an index of the sections, the state of their three sources and what this server cannot answer. A report drawn while its source was off is empty, which is not the same statement as "nothing happened"; each section'snotesays which it is.GET /api/v1/reports/{section}answers one section, the Deck's Reports view draws the summary, andhmctl reportprints a section or saves it as CSV. There is no Control Panel page.Section Counted from Says trafficthe message trace Per day and per local domain: incoming,outgoing,failed_incoming,failed_outgoingfailuresthe message trace Per local domain and SMTP status code senders,recipientsthe message trace The local addresses that sent or received the most, with their failures mailboxeshm_messagesThe largest mailboxes as they are now: a size, not a history volumethe metric history, and the daily per-domain sample Processed, delivered, deferred, bounced, spam and viruses per day, with by_domainrows of spam and virusesstoragethe same The store's size per day and growth_bytes, withby_domainrows of each domain's sizesummaryall three sources Every section the credential may see, with an omittedlist and the reasonquotathe store Every mailbox with a limit and at least one message, nearest its limit first, as a share of the limit: a share now, not a history countriesthe metric history and the country counters What a country policy refused, per day; the split by country is since the server last started, not per day The last two are described in QuotaPolicy.md and CountryBlocking.md, not in Reports.md.
Parameters.
fromandtoasYYYY-MM-DD, both inclusive, defaulting to the last 30 days and never more than 366;domain, one hosted domain (a domain not hosted here is a 404);top, 1 to 200, default 10;format=csv, refused forsummary. In CSV a cell starting with=,+,-or@is prefixed with an apostrophe.Three things decide whether a report means anything. The message trace is off by default (
MessageTraceEnabled=0), and while it is off the four sections counted from it are empty. Processed, delivered, deferred and bounced cannot be given per domain, because those counters carry no domain; spam, viruses and storage can, since schema 6062 added the daily sample inhm_domainsamples, which starts the day it was first taken. And only hosted domains are counted.Authorisation. The administrator password reads everything; every other credential needs both
view_configurationandview_message_trace.domainadminandauditorhold both;helpdeskis refused. A key restricted to named domains sees only those and is refused 403 for another. OnvolumeandstorageReports.md contradicts itself: its Authorisation section refuses them to such a key outright, while its section What cannot be answered, and what it would take (and, forvolume, its table of sections under The route) gives a credential restricted to domains the per-domain rows, as the wiki's section 16.6a does. Test both with your own restricted key before a script relies on either.The cost. One report makes at most three grouped queries; the trace read is capped at 100 000 grouped rows and the answer says
truncatedwhen it reaches the cap.hmctl report sections hmctl report traffic --from 2026-09-01 --to 2026-09-15 --domain example.comFollowing one connection live
Since 6.3.4 the Deck's Logs page lists the sessions open now and those recently ended, and Follow shows one session's lines as they are written, from an in-memory ring of the last 4,096 log entries that no restart keeps; it needs
view_logs. Reading the logs covers following a connection in full, and the log files themselves.Where to read more
- Documents at v6.3.4: AlertsAndAuditTrail.md, Syslog.md, Reports.md and FollowConnection.md
- Wiki: Monitoring and Health, sections 16.2a, 16.6a, 16.7a and 16.7b
- The public documentation: 16.2 Logs on disk
- On this forum: Monitoring and health for the metrics, the probes, the dashboard and the SNMP agent; Administrator roles for the permissions named here. See also The Control Deck and hMailServer on Linux: what you get and what you do not
Hello! It looks like you're interested in this conversation, but you don't have an account yet.
Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.
With your input, this post could be even better 💗
Register Login