-
Notifications
You must be signed in to change notification settings - Fork 3
Command Line
In the tree after 6.3.3, not yet released: both clients are in the repository and the Linux packages install
hmctl, but no published release carries them yet. Thereportandconfigverbs below, and the PowerShell module's configuration cmdlets, arrived after the rest and were checked against the code at commit3233b0cc4.
Source of truth: hmailserver/docs/CommandLine.md, kept in the repository and copied here.
Two clients, one vocabulary, one API: hmctl for any shell, and the
HmailServer PowerShell module for PowerShell. Both speak the REST API, so both
work against a Windows server, a Linux server, or a server on another machine,
and neither needs COM or the Control Panel.
This is not a replacement for either administration program. The Windows Control Panel is the place to configure a server, and the browser Control Deck is that place on Linux. A command line is for the things a command line is better at: making fifty accounts from a spreadsheet, reading one setting in a script, watching the delivery queue, asking what a rule criterion would decide, or doing the same thing to twenty servers.
hmailserver/source/Tools/Cli/hmctl |
One Python 3 file, no dependencies beyond the standard library. The Linux packages install it as /usr/bin/hmctl. |
hmailserver/source/Tools/Cli/HmailServer.psm1 |
The PowerShell module: Get-HmDomain, New-HmAccount, Set-HmSetting and the rest, answering objects rather than text. |
build/check-cli.py |
Runs both against a recorded server and asserts what each one sent, so the two cannot drift apart in what they ask for. In CI. |
build/hmconfig.ps1 is a different tool and stays: it exports the whole
configuration as one reviewable JSON document and applies it to another machine,
over COM, on Windows. These two are per-object verbs over HTTP.
Either the administrator password, sent as HTTP Basic with the user name
Administrator, or an API key, sent as a bearer token. An API key is the better
answer for anything unattended: it can be read-only, it can be restricted to one
domain, and it can be revoked without changing the administrator password.
export HMAIL_URL=https://mail.example.com:8045
export HMAIL_API_KEY="$(cat /etc/hmailserver/api.key)"
hmctl domain listConnect-HmServer -Url https://mail.example.com:8045 -ApiKey (Get-Content .\api.key)
Get-HmDomainhmctl reads HMAIL_URL, HMAIL_API_KEY, HMAIL_PASSWORD and
HMAIL_PASSWORD_FILE, and takes --url, --api-key, --password and
--password-file wherever you type them - before the verb or after it. With no
credential at all it asks for one, but only when there is a console to answer:
in a scheduled task or a pipeline it says what is missing and stops, rather than
waiting at a prompt nobody can see.
--insecure is refused for anything but the loopback. A self-signed
certificate on localhost is the ordinary case and nothing is on the wire; an
administrator password sent to a remote host over a connection nobody verified
is not a convenience, and this will not make it one. Give the server a
certificate the client trusts.
hmctl [--url URL] [--api-key KEY | --password PASSWORD | --password-file FILE]
[--insecure] [--json] <resource> <verb> [arguments]
| Resource | Verbs | Notes |
|---|---|---|
domain |
list, get, create, update, delete | |
account |
list, get, create, update, delete | Listed and created under --domain, read and written at the address: which is how the API is shaped. |
alias |
list, create, delete | Under --domain. |
list |
list, create, update, delete | Distribution lists, under --domain. |
group |
list, get, create, update, delete | Account groups; member handles the accounts in one. |
rule, route, port, iprange, certificate
|
list, create, update, delete | Server-wide. |
fetchaccount |
list, get, create, update, delete | Under --address. |
apikey |
list, create, delete | Administrator password only, never an API key. |
queue |
list, retry, delete | |
quarantine |
list, release, delete | |
settings |
get, set | A group whole, or one setting: settings, antispam, antivirus, logging, backup, cache, indexing, scripting, directories. |
ini |
list, get, set, delete | The hMailServer.ini keys the server exposes. |
log |
list, tail | |
backup |
status, start | The server's own backup. |
app-password |
list, create, delete | Under an account's address. |
match |
(a criterion and a value) | What a rule criterion would decide, without a rule. |
accounts |
import, export | A CSV file, with a dry run. |
report |
(a section) | What happened on this server, per domain and per day. sections first: it says what can be reported and what cannot. |
config |
export, diff, apply | The whole configuration as one document, below. |
api |
(method and path) | The escape hatch, for a route newer than this file. |
A few whole commands:
hmctl domain create --set name=example.com --set active=true
hmctl account create --domain example.com --set address=ann@example.com --set password=...
hmctl account list --domain example.com
hmctl settings get antispam use_spf
hmctl settings set antispam --set use_spf=true --set spam_mark_threshold=5
hmctl queue list --json | jq '.[] | select(.retry_count > 3)'
hmctl match contains viagra "cheap viagra here"
hmctl report sections
hmctl report traffic --from 2026-09-01 --to 2026-09-15 --domain example.com
hmctl report mailboxes --top 25 --csv mailboxes.csv
hmctl api POST /api/v1/server/reinitialize--set name=value reads true, false, null, numbers and anything starting
with { or [ as JSON and everything else as a string; --set-string keeps a
value that looks like one of those a string. --from-json FILE takes a whole
object, and - reads it from standard input.
Output is a table by default and the server's own JSON with --json. The exit
code is 0 for success, 1 for an error, 2 for a usage mistake, 3 when the server
refused the credential and 4 when what you named does not exist - so a script
can tell "no such account" from "wrong password" without reading the text.
hmctl report sections
hmctl report summary --from 2026-09-01 --to 2026-09-15
hmctl report senders --domain example.com --top 25
hmctl report traffic --csv traffic.csvThe sections are traffic, failures, senders, recipients, mailboxes,
volume, storage and summary. The window is --from and --to as
YYYY-MM-DD, both inclusive, defaulting to the last thirty days ending today on
the server; --domain narrows it to one domain the server hosts; --top is how
many rows the sender, recipient and mailbox sections return (1 to 200, default 10).
--csv FILE writes the table, and --csv - writes it to standard output. It is
the server's CSV, fetched with format=csv, rather than one built here out of
the JSON: two writers of the same table drift, and the file that ends up in a
spreadsheet should be the one the route promises. summary is not one table and
has no CSV.
Without --csv, the section's own note is printed above the table. Read it. The
four sections counted from the message trace are empty when the trace is switched
off - which is the default - and "the message trace is switched off" is not the
same statement as "nothing happened". hmctl report sections says which sources
are recording, how long each is kept, and the two questions this server cannot
answer at all: spam and virus counts per domain, and storage growth per domain.
Monitoring and Health §16.6a is why. The PowerShell module has no report
cmdlet; Invoke-HmApi -Method GET -Path /api/v1/reports/summary reaches the same route.
hmctl accounts import new-people.csv --dry-run
hmctl accounts import new-people.csv
hmctl accounts export example.com accounts.csvThe header row names the fields and address is the only one required; anything
the account resource takes may be a column, so the file does not need changing
when the account gains a field.
address,password,name,active,max_size_mb
ann@example.com,Sup3rSecret!,Ann Adams,true,500
bob@example.com,An0therOne!,Bob Brown,true,An empty cell is not sent, so the server's own default applies. An address
that already exists is left alone and counted, never overwritten: an import
that silently reset a password would lock a hundred people out with one command.
--dry-run makes no request that changes anything and prints what would happen,
line by line.
The export writes no password column, because the server does not have the passwords to give. A file exported and re-imported makes no accounts; it is for an inventory, not a round trip.
hmctl config export server.json # the whole configuration, as a document
hmctl config diff server.json # what would change; exit 1 if anything would
hmctl config apply server.json # the plan, and nothing else
hmctl config apply server.json --force # the plan, appliedThe document holds the settings groups, the hMailServer.ini keys the server
exposes, the domains and their accounts, aliases and distribution lists, the
groups, the rules, the routes, the listeners and the IP ranges. It is sorted
throughout and carries nothing the server allocated - no identifier, no count,
no time - so two exports of an unchanged server are the same bytes. That is
what makes it usable in a repository: a diff means a change, and nothing churns.
export with no file name writes to standard output.
apply is a plan unless you add --force, and a deletion needs --allow-delete
on top of that. Without it, an entry the server has and the document does not
is left alone and counted, because the common case is a document written from
one server and applied to another that has things of its own. A step the server
refuses is printed with its reason, the rest still apply, and the exit code is 1.
What it does not carry, and why:
-
Passwords. The server does not give them out, so they are not in the
document. An account in the document that does not exist on the server is
refused with that reason rather than created with a password nobody chose;
add a
passwordfield for the accounts you mean to create. - Certificates and the directories. A certificate is files on the server's own disk, and the directories are that machine's paths; copying either between machines would describe a server that does not exist.
- What lives under an account - application passwords, fetch accounts, folder permissions - which are the account holder's rather than the configuration's.
-
Remote domain policies and alert rules, which the tree after 6.3.3 added, are not
sections of the document: they have routes of their own (APIs Reference §4) that
configdoes not read. A domain's external-sender tag, first-contact note and disclaimer settings are carried, because the domain listing thedomainssection is read from includes them.
Checked against the code at
3233b0cc4: theinisection does not work against a real server yet.configreads it fromGET /api/v1/settings/ini, which answers only{"names":[...]}- the key names, without values - so an export made with the administrator password writes an emptyinisection, and a diff never finds a difference there. And that route is the administrator password's alone: presented with an API key it answers 401, whichhmctl config export,diffandapplyreport as a refused credential and stop on (exit 3): no document is written and no plan is made.build/check-cli.pydoes not catch either, because its recorded server answers that route with names and values. Until that is fixed, runconfigwith the administrator password, and carry[Settings]keys separately withhmctl ini get/hmctl ini set.
There is no daemon; the loop is two commands and whatever runs them.
# On a pull request: does the repository still match the server?
- run: hmctl config diff server.json # exit 1 fails the job
# On merge: make the server match the repository.
- run: hmctl config apply server.json --forceKeep the credential in the runner's secret store, and let the diff run on a schedule as
well: a server that has drifted from its repository is worth knowing about before somebody
needs the repository to be true. (The repository's copy of this page recommends an API key
for the job; with the defect above, an API key stops the export at the ini section.)
The PowerShell module speaks the same document: Export-HmConfiguration answers it as an
object or writes it with -Path, Compare-HmConfiguration -Path answers one object per
step (Section, Verb, Key, Detail), and Import-HmConfiguration -Path keeps the same
rules - a plan unless -Force, deletions only with -AllowDelete as well, and -WhatIf and
-Confirm on the whole thing. A document one client writes is one the other reads, which
build/check-cli.py checks. The module swallows a refused route rather than stopping, so
with either credential its ini section comes out empty.
Import-Module .\hmailserver\source\Tools\Cli\HmailServer.psm1
Connect-HmServer -Url https://localhost:8045 -Insecure
Get-HmDomain | Where-Object active -eq $false
Get-HmAccount -Domain example.com | Sort-Object max_size_mb -Descending | Select-Object -First 10
New-HmAccount -Domain example.com -Address ann@example.com -Password (Read-Host -AsSecureString)
Set-HmAccount -Address ann@example.com -Property @{ max_size_mb = 500 }
Set-HmSetting -Group antispam -Property @{ use_spf = $true }
Get-HmQueue | Where-Object retry_count -gt 3 | Start-HmQueueDelivery
Import-HmAccount -Path .\new-people.csv -WhatIfEvery function answers objects, so Where-Object, Sort-Object, Export-Csv
and the rest work on them; nothing in the module formats anything. Everything
that changes the server supports -WhatIf and -Confirm, and deleting a domain
or an account asks before it acts unless told not to. A domain name or an
address completes from the server itself once Connect-HmServer has run.
Invoke-HmApi -Method GET -Path /api/v1/... is the escape hatch, and is what
every other function is built on.
- It does not read or write the message store. Mail is IMAP's business.
-
It is not a backup tool.
hmctl backup startasks the server to run its own backup, and that is all. -
It sets no administrator password.
hmailserver --set-admin-passwordon the server does that, and the Control Panel does it on Windows. -
Neither invents a field. A field you did not give is one the server
decides; the two clients were made to send the same request for the same
command, and
build/check-cli.pyfails if they stop doing so.
hMailServer 6.3.3, the latest release (schema 6040) · pages also describe the tree after 6.3.3 (schema 6045), not yet released, and say so where they do · AGPL-3.0-or-later · Repository · Report a documentation error
Hmail Server — full index
Start here
1. Install and run
- Before You Install
- Installing hMailServer
- Installing on Linux
- Running in a Container
- The Control Panel
- Your First Domain and Mailbox
- Connecting a Mail Client
- DNS for Your Domain
2. Secure it
3. Operate it
- Monitoring and Health
- Backup and Restore
- Troubleshooting
- Diagnosing Stalled Mail
- Relocating an Installation
- Upgrading hMailServer
- Upgrading Guide
- Migrating the Database Backend
- High Availability Runbook
- Warm Standby
- Runbooks Digest
4. Extend it
- Rules and Sieve
- Aliases Lists and Public Folders
- Routes and Relays
- The COM API and Scripting
- The REST API
- Command Line
- APIs Reference
5. Contribute to it
- Project Handbook
- Architecture
- Contributing
- Release Process
- Governance
- Assurance Case
- Regression Test Environment
- Fuzzing
- Regulatory Scope
- Third-Party Binaries
Look it up — from any journey