Skip to content
  • Categories
  • Recent
  • Popular
  • Tags
  • Users
  • Groups
  • Documentation
  • Downloads
  • Report a bug
  • GitHub mirror
  • Paid support
Skins
  • Light
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Default (No Skin)
  • No Skin
Collapse
hMailServer Community Forum
hMailServer 6.3.8 is the current release, signed 2 November 2026. Windows and Linux, and an in-place upgrade from 5.3 or later, but for the original hMailServer's 5.7.Download 6.3.8 Documentation FAQ
  1. Support and discussion for hMailServer
  2. Automation & integration
  3. REST API & integrations
  4. hmctl and the PowerShell module: shell administration and accounts from a CSV file

hmctl and the PowerShell module: shell administration and accounts from a CSV file

Scheduled Pinned Locked Moved REST API & integrations
rest-apiadministrationlinuxwindowsapi
1 Posts 1 Posters 114 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • ProgressiverobotP
    ProgressiverobotP
    Progressiverobot
    hMailServer Team
    wrote on last edited by chris
    #1

    Written for 6.3.4. hMailServer 6.3.6 (6 October 2026) carries the PowerShell module, HmailServer.psd1 and HmailServer.psm1, Authenticode-signed, as files of the release, and Linux packages for x86-64 and AArch64; hmctl as a PyPI package and the module on the PowerShell Gallery are built but not yet published. See the 6.3.6 announcement. hMailServer 6.3.7 (17 October 2026) carries the module the same way, and since 18 October 2026 the repository and its releases are also mirrored on GitHub, issues and merge requests staying on GitLab. 6.3.7's REST API refuses a state-changing request that carries a credential when a browser reports it as cross-site, which hmctl and the module, sending no such header, never meet, and it refuses a key restricted to named domains a domain's rename, a domain alias and a custom role's creation, change or delete. See the 6.3.7 announcement. Since 21 October 2026 hmctl is on PyPI as hmailserver-cli and the module on the PowerShell Gallery as HmailServer, each at 6.3.7. hMailServer 6.3.8 (2 November 2026) carries the module as files of the release, as before, and both are sent 6.3.8 by a job started by hand once the release is published. 6.3.8's server answers 400 to a body that is not one JSON object or names a member twice, so a body given to hmctl api or --from-json must be one JSON object that names each member once, and 6.3.8's hmctl refuses a body holding NaN or Infinity before sending it. See the 6.3.8 announcement.

    hMailServer 6.3.4 has two command-line clients: hmctl for any shell, and the HmailServer module for PowerShell. They share one vocabulary and one API, so both administer a Windows server, a Linux server or a server on another machine, and neither needs COM or the Control Panel. This guide covers where each comes from, the credentials they take, the verbs, the import of accounts from a CSV file with its dry run, the configuration as one document, and what neither client does. The one thing to know first: both speak the REST API, so the REST listener has to be on before either can do anything, and neither client can turn it on.

    Neither is a replacement for the administration programs. The Windows Control Panel is the place to configure a server on Windows, and the browser Control Deck is that place on Linux. A command line is for what a command line is better at: making fifty accounts from a spreadsheet, reading one setting in a script, watching the delivery queue, or doing the same thing to twenty servers.

    Where the clients come from

    File What it is
    hmailserver/source/Tools/Cli/hmctl One Python 3 file with no dependencies beyond the standard library.
    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 in CI and asserts what each one sent, so the two cannot drift apart.

    On Linux, the packages install hmctl as /usr/bin/hmctl. The 6.3.4 packages are x86-64 only, and the changelog records Python 3 as one of the .deb's two Recommends, the other being 7-Zip. On Windows, the wiki says the installer carries neither client: take them from the repository directory hmailserver/source/Tools/Cli/. Both clients are in the repository since 6.3.4.

    build/hmconfig.ps1 is a different tool and stays. It exports the whole configuration as one JSON document and applies it to another machine over COM, on Windows. The two clients here are per-object verbs over HTTP.

    Before anything else: the listener

    On Linux, store the port on the server itself and restart the service. Since 6.3.4 the database is the settings store, so editing the [Settings] section of hMailServer.ini does not turn the listener on once the server has stored the key.

    sudo hmailserver --set-setting RestApiPort=8080
    sudo systemctl restart hmailserver
    

    On Windows, the Control Panel does this over COM.

    On either platform, TLS is required unless the listener is bound to the loopback, so give it a certificate the client trusts if it is to be reached from another machine. The REST API guide has the listener's settings, the reinitialise they wait for and the three ways it refuses to start, and the hmailserver command line on Linux has the checks --set-setting makes and how it takes a secret.

    Credentials

    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 list
    
    Connect-HmServer -Url https://mail.example.com:8045 -ApiKey (Get-Content .\api.key)
    Get-HmDomain
    

    hmctl reads HMAIL_URL, HMAIL_API_KEY, HMAIL_PASSWORD and HMAIL_PASSWORD_FILE, and takes --url, --api-key, --password and --password-file 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.

    --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 what the refusal prevents, and the remedy is a certificate the client trusts.

    Some commands take the administrator password and never an API key, because no key reaches the routes behind them: hmctl apikey, hmctl ini, hmctl config changed, hmctl webhook and hmctl webhooks. The command synopsis names no option for a one-time code. Since 6.3.4 a one-time code is taken once, and the changelog's advice to a script that sent the administrator's password and a code with every request is a session or an API key; the REST API guide has the detail.

    hmctl

    hmctl [--url URL] [--api-key KEY | --password PASSWORD | --password-file FILE]
          [--insecure] [--json] <resource> <verb> [arguments]
    

    The resources, grouped. CommandLine.md has a row for each.

    Area Resources Notes
    Domains and what is in them domain, account, alias, list, group, fetchaccount, app-password, device, mailbox An account is listed and created under --domain, and read and written at its address, which is how the API is shaped. mailbox renames or moves one.
    Server-wide objects rule, route, port, iprange, certificate, journalrule list, create, update, delete
    Mail in motion queue, quarantine queue has list, retry, delete; quarantine has list, release, delete
    Settings settings, ini, config A settings group whole or one setting; the keys that were the [Settings] section; the configuration as one document
    Deliverability dkim, dmarc, suppression, bounce, bounces, complaint, complaints, spamtrap, traphits, reputation, openpgp reputation exits 1 when an address is listed and when a list could not be asked
    Integrations apikey, webhook, webhooks All three with the administrator password only, never an API key
    Operations log, backup, report, sample-domain, match match says what a rule criterion would decide, without a rule
    Bulk accounts import and export, a CSV file, with a dry run
    Anything else api A method and a path: the escape hatch, for a route newer than the client

    A few whole commands:

    hmctl domain create --set name=example.com --set active=true
    hmctl account create --domain example.com --set [email protected] --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 device [email protected] list
    hmctl queue list --json | jq '.[] | select(.retry_count > 3)'
    hmctl match contains viagra "cheap viagra here"
    hmctl report traffic --from 2026-09-01 --to 2026-09-15 --domain example.com
    hmctl api POST /api/v1/server/reinitialize
    

    How values are read. --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 and exit codes. Output is a table by default and the server's own JSON with --json.

    Exit code Meaning
    0 Success
    1 An error
    2 A usage mistake
    3 The server refused the credential
    4 What was named does not exist

    A script can therefore tell "no such account" from "wrong password" without reading the text.

    The PowerShell module

    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 [email protected] -Password (Read-Host -AsSecureString)
    Set-HmAccount -Address [email protected] -Property @{ max_size_mb = 500 }
    Set-HmSetting -Group antispam -Property @{ use_spf = $true }
    Get-HmQueue | Where-Object retry_count -gt 3 | Start-HmQueueDelivery
    Get-HmChangedSetting | Format-Table store, name, value, default
    

    Every function answers objects, so Where-Object, Sort-Object and Export-Csv work on them, and 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 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.

    Accounts from a CSV file

    hmctl accounts import new-people.csv --dry-run
    hmctl accounts import new-people.csv
    hmctl accounts export example.com accounts.csv
    
    Import-HmAccount -Path .\new-people.csv -WhatIf
    Import-HmAccount -Path .\new-people.csv
    Export-HmAccount -Domain example.com -Path .\accounts.csv
    

    The file is UTF-8 (in Excel, CSV UTF-8) and its header row names the columns. These are the columns, and there are no others.

    Column Required Notes
    address yes Created under the address's own domain.
    password yes Sent as text, so digits alone are still a password. Never trimmed: white space at either end is refused. Judged by the server's password policy and, when it is switched on, the breach check.
    first_name At most 60 characters, counted as UTF-16 code units.
    last_name As first_name.
    active true, yes, on or 1; false, no, off or 0. Default true.
    max_size_mb A whole number of megabytes; 0 is no limit, and is the default.
    admin_level user, domain or server. Default user.
    address,password,first_name,last_name,active,max_size_mb,admin_level
    [email protected],S0me-Long-Passphrase,Ann,Adams,true,500,user
    [email protected],An0ther-Long-Passphrase,Bob,"Brown, Jr",true,,domain
    

    The columns may come in any order and their names in any case. Every cell but the password is trimmed, and an empty cell is not sent, so the server's default applies. A value with a comma in it goes in quotes. A row with more cells than the header has columns is refused rather than shifted along.

    What is refused before anything is sent. Each row becomes one POST /api/v1/domains/<domain>/accounts, and that route refuses a field it does not know by name. So a column that is not in the table is refused before anything is sent, a name column included. The clients also refuse, in the words the server would use:

    • a row with no password;
    • an active that is not one of the eight words;
    • a max_size_mb that is not a whole number;
    • an admin_level that is not one of the three;
    • a name past its 60 characters;
    • a password with a space or a tab at its start or end, which is the one row the clients refuse and the server would take.

    What a dry run cannot foresee. The password policy, the domain's account limit and an address the server does not accept are judged by the server alone. They are refused per row when the import runs, and listed with the address and the server's reason; hmctl adds the line number.

    An address that already exists is left alone and counted, never overwritten. An import that reset a password without saying so would lock people out with one command. --dry-run makes no request that changes anything and prints what would happen, line by line. The exit code is 1 when any row was refused.

    Who may make an administrator. admin_level is decided by who is asking. The administrator password, or a key issued for every domain, may make a server administrator. A key restricted to named domains may make users and domain administrators, and a row asking it for server is refused with a 403.

    The export writes the same columns less password, because the server does not have the passwords to give. Add a password column and change the addresses, and the exported file imports into another domain or another server as the same accounts: names, sizes, levels and whether each is active.

    The configuration as one document

    hmctl config changed                     # every setting that is not the shipped default
    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, applied
    

    The document holds the settings groups, the hMailServer.ini keys the server exposes, the domains with 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, so two exports of an unchanged server are the same bytes.

    apply is a plan unless --force is added, 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.

    Not carried Why
    Passwords The server does not give them out. An account in the document that does not exist on the server is refused rather than created with a password nobody chose; add a password field for the accounts to be created.
    The secrets among the ini keys Listed as (set) or empty, and left out of the document. hmctl ini get <name> reads one by name.
    Certificates and the directories They are files and paths on one machine's disk.
    The journaling rules An address chosen for one server is almost never the right one for another. hmctl journalrule edits them.
    What lives under an account Application passwords, fetch accounts and folder permissions are the account holder's.
    The selector and key files of a domain whose DKIM key rotation is on The rotation moves them on its own schedule.
    Settings the environment manages In a container the value is the deployment's. The server refuses a change to such a setting with 409.
    Remote domain policies and alert rules The wiki's own addition: they have routes of their own that config does not read.

    What this server has changed from stock. hmctl config changed, or Get-HmChangedSetting in PowerShell, lists every setting whose value is not the one this version ships with, with the shipped value beside it. Settings: what is not stock has the route behind it, the other places to read the same list, what "default" means and who may see it.

    $ hmctl config changed
    STORE     NAME                       VALUE           DEFAULT
    ini       LogLevel                   5               9
    ini       SMTPDMaxTimeout            600             1800
    ini       OAuth2HmacSecret           (set)
    ini       DnsServer                  10.0.0.1 (forced from [SettingsOverride])
    database  smtpnoofretries            6               4
    
    5 of 391 settings are not what this version ships with.
    

    A secret is named and never shown. A value that the file's [SettingsOverride] section is forcing says (forced from [SettingsOverride]), a value the environment sets says (set by the environment), and a name the server holds a value for and ships no default for says (none: this server does not read it). A difference is not an exit code here, unlike config diff, because every real server has changed something.

    Reports and country blocking from a shell

    hmctl report sections
    hmctl report summary --from 2026-09-01 --to 2026-09-15
    hmctl report senders --domain example.com --top 25
    hmctl report mailboxes --csv mailboxes.csv
    hmctl report traffic --csv -
    

    --csv fetches the server's own CSV with format=csv instead of building one from the JSON. Per-domain reports has the sections, their sources and parameters, and who may read them.

    Run hmctl report sections first. It says which sources are recording and what this server cannot answer. The four sections counted from the message trace are empty when the trace is switched off, which is the default, and that is not the same statement as "nothing happened". Without --csv, the section's own note is printed above the table.

    CountryBlocking.md shows the country policy, which the topic on allow and block lists and country blocking explains, set from both clients:

    hmctl settings get settings geoip_database_status
    hmctl settings set --set geoip_database_file=/var/lib/GeoIP/GeoLite2-Country.mmdb
    hmctl settings set --set country_policy_smtp=block:CN,RU
    hmctl domain update example.com --set active=true --set country_policy=allow:GB,IE
    
    Set-HmSetting -Property @{ geoip_database_file = '/var/lib/GeoIP/GeoLite2-Country.mmdb'
                               country_policy_smtp = 'block:CN,RU' }
    (Get-HmSetting).geoip_database_status
    Set-HmDomain -Name example.com -Property @{ active = $true; country_policy = 'allow:GB,IE' }
    

    geoip_database_status is read-only. A settings change is all or nothing, so a policy the grammar refuses leaves every other key in the same request unapplied.

    What neither client does

    • Neither reads or writes the message store. Mail is IMAP's business.
    • Neither is a backup tool of its own. hmctl backup start, hmctl backup restore and hmctl backup passphrase ask the server; the archive is written, read and restored by the server.
    • Neither sets the administrator password. hmailserver --set-admin-password on the server does that, on Windows hMailServer.exe --set-admin-password, with the password in HM_ADMIN_PASSWORD or on standard input and never as an argument. On Windows the Control Panel does it too, but only where the service may write hMailServer.ini, as under LocalSystem: under NT SERVICE\hMailServer, a new 6.3.4 installation's account, its Administrator password card is refused with a sentence.
    • Neither makes or carries the stored-secrets key. That is done on the server itself, never over the API.
    • Neither can turn the REST listener on, since both need it.
    • Neither sets what the environment sets in a container. No container image of 6.3.4 is published.
    • Neither invents a field. A field that was not given is one the server decides.
    • hmctl has no pending verb. SettingsApply.md notes that GET /api/v1/server/settings/pending is one request; hmctl api takes a method and a path.

    Where to read more

    • The operator document: CommandLine.md, with a row for every resource and sections on DKIM rotation, bounces, complaints, DMARC reports, OpenPGP keys, the mailbox move and setting profiles.
    • The wiki page: Command Line.
    • For the examples above: ChangedFromStock.md, Reports.md and CountryBlocking.md.
    • The public documentation: chapter 24, The REST API, which both clients speak, and 35.10, Administering a Linux server.
    • On this forum: the Linux guide, The Control Deck, the 6.3.4 announcement, the REST API guide and, for what hmctl webhook manages, the send API and webhooks.
    1 Reply Last reply
    0

    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
    Reply
    • Reply as topic
    Log in to reply
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes


    hMailServer Community

    The community support forum for hMailServer — the free, open-source mail server for Windows and Linux — maintained by Progressive Robot Ltd. The software is AGPL-3.0 and stays that way; this is where it is discussed, debugged and documented.

    The software

    • Documentation
    • Downloads
    • hMailServer 6.3
    • Source on GitLab

    Getting help

    • Forum rules
    • About this forum
    • Support plans
    • Pay monthly support
    • Talk to Progressive Robot

    Legal

    • Privacy policy
    • Terms and conditions
    • Cookie policy

    Progressive Robot Ltd · 220 Chester Business Park, Heronsway, Chester, Cheshire CH4 9GB, United Kingdom · +44 (0)1244 911212 · [email protected] · VAT 506152326

    Powered by NodeBB

    • Login

    • Don't have an account? Register

    • Login or register to search.
    • First post
      Last post
    0
    • Categories
    • Recent
    • Popular
    • Tags
    • Users
    • Groups
    • Documentation
    • Downloads
    • Report a bug
    • GitHub mirror
    • Paid support