Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) is a security release that closes gaps in what this guide describes. Over COM an account given serveradmin with a list of domains was the server's administrator, its list aside, and one given domainadmin with a list that left out its own domain administered that domain's ordinary accounts; both are now confined as over REST, so before upgrading give serveradmin with no list to any account that is to administer the whole server over COM, a script's or a Control Panel's account among them. A credential confined to named domains is refused a domain's rename, a domain alias, a custom role's creation, change or deletion, a rule action that reaches the server's own addresses, a route's stored credentials or the event script, a relay host that is not public, and the server-wide metric history, over REST and COM alike; and a role is given or taken away only by a credential that takes in what the account holds now and afterwards, so manage_roles in named domains can no longer take a role from an account whose admin level is the server administrator's. A state-changing REST request that a browser reports as cross-site is refused. In the Control Panel, API & monitoring is Monitoring & alerts and REST API keys is under Sign-in & access; Ctrl+K takes every old title to its page. See the 6.3.7 announcement.
This guide puts administrator roles in one place. Roles are new since 6.3.4 (schema 6047). The one thing to know first: an upgrade changes nobody's authority. The upgrade writes no assignment, and an account with no assignment holds exactly what its admin level has always given it. Nothing narrows until you give somebody a role.
Permissions, roles and assignments
A permission is one thing a credential may do, a role is a named set of permissions, and an assignment gives one account one role in named domains. An account holds at most one role.
Role
For
Reaches when no domain is named
The same as
serveradmin
Everything, including the secrets no other role reads
Every domain
The administrator password, admin level Server, an API key with Scope=full and no role
domainadmin
The accounts, aliases, lists and mailboxes of a domain
The account's own domain
Admin level Domain
helpdesk
Finding an account, resetting its password, unlocking it, and reading where its mail went, and nothing else
The account's own domain
Nothing before 6.3.4
auditor
Reading everything the administration exposes, the audit trail included, and changing nothing
Every domain
An API key with Scope=readonly and no role
The built-in roles cannot be edited or deleted. Their permissions are in the server's code, not the hm_roles table, so a change to the table cannot hand anybody a permission.
The 27 permissions and who holds them
A permission marked server-wide is reached only by a credential with no list of domains. The queue, the quarantine, the archive, the logs and the settings carry every domain's mail, so a role confined to named domains is refused them whatever it holds.
Permission
What it reaches
serveradmin
domainadmin
helpdesk
auditor
administer_server
Everything, secrets such as password hashes and private keys included; COM's "is this the server administrator?"
yes
administer_domain
COM's "is this a domain administrator?"
yes
yes
view_configuration
Status, settings, listeners, certificates, rules, routes, anti-spam lists, the roles and who holds them, alert rules and events, bounce and complaint readings; never a secret
yes
yes
yes
view_accounts
Domains, accounts, aliases, lists, groups (server-wide), fetch accounts
yes
yes
yes
yes
view_mailbox
Another account's folders and messages
yes
yes
yes
view_queue
The delivery queue (server-wide)
yes
yes
view_quarantine
The quarantine (server-wide)
yes
yes
yes
view_archive
The archive (server-wide)
yes
yes
view_logs
The logs (server-wide)
yes
yes
view_message_trace
The message trace; with view_configuration, the reports and a domain's suppression list
yes
yes
yes
yes
view_audit
The audit trail (server-wide)
yes
yes
manage_accounts
Creating, changing and deleting accounts, aliases, lists, groups, fetch accounts and app passwords; a domain's spam traps, DKIM rotation and suppression list; moving a mailbox (needs both domains)
yes
yes
reset_password
POST /api/v1/accounts/{address}/password, Application.ResetPassword
yes
yes
yes
unlock_account
POST /api/v1/accounts/{address}/unlock, Application.UnlockAccount, Account.UnlockMailbox; clearing the logon-failure list (server-wide)
yes
yes
yes
manage_mailbox
Another account's folders, messages and folder rights, a support session, a PST migration job (with manage_accounts)
yes
yes
manage_queue
Retrying or deleting a queued message (server-wide)
yes
manage_quarantine
Releasing or deleting held mail (server-wide)
yes
yes
manage_archive
Holding and releasing archived messages (server-wide)
yes
manage_settings
Every setting, listener, certificate, route, rule and anti-spam list, the alert rules (server-wide); the webmail's branding and file-link policy, a confined credential only its own domains'
yes
manage_domains
Which domains exist (server-wide)
yes
manage_server
Reinitialise, back up, reload scripts, clear caches, install an update, run a pass now (server-wide)
yes
manage_roles
Roles and who holds them (server-wide)
yes
send_through_api
Sending through POST /api/v1/send; only an API key reaches it
yes
provision
SCIM 2.0 at /scim/v2, reads and writes; only an API key reaches it
yes
view_discovery
eDiscovery's cases, searches, holds and exports
yes
yes
manage_discovery
Opening and closing a case, searching, legal holds, exports
yes
manage_inbound_webhooks
Routing an address or a domain to a URL, as much as reading every message the route takes
yes
Four rules sit beside the table:
The auditor reads mail. It holds view_mailbox; the help desk does not.
The reports need two permissions. GET /api/v1/reports needs view_configuration and view_message_trace, because it says who mails whom. domainadmin and auditor read them; helpdesk is refused, and the Deck leaves the Reports view out for any role without both.
The trace follows the role's reach. A confined help desk traces one whole address in a domain it reaches and sees only events naming that address exactly:
[email protected] does not also show
[email protected].
Clearing the logon-failure list is server-wide. hm_logon_failures records only the address a failure came from, with no account or domain, so a confined help desk is refused POST /api/v1/settings/logon-failures/clear, and Settings.ClearLogonFailureList answers the server administrator alone. The help desk unlocks one account with POST /api/v1/accounts/{address}/unlock instead; a password reset clears the lockout too. A ban already placed is an IP range under /api/v1/ipranges, which needs manage_settings.
What only serveradmin has
Reading a secret. No view_ permission covers a password hash or a private key: an auditor that could read one would be an offline attack away from being every role.
Handing out authority. No custom role may hold administer_server, administer_domain or manage_roles, and a credential may never give a role holding a permission, or reaching a domain, that it does not hold itself.
Taking over a wider account. A credential may not reset the password of, or change, an account whose whole authority it does not take in. A help desk is refused a domain administrator's or an auditor's password, even in its own domain, because otherwise it could sign in as them; a domain administrator is refused an auditor's. Where the server administrator's own mailbox is in a domain somebody else administers, that domain's administrator is refused all of these on it, over REST and COM. Over COM this is asked where the write is made, not where the object was reached: Account.Save, Account.Delete, Account.MoveTo, Application.ResetPassword, the app passwords and every write to what is in an account (FetchAccount.Save, Rule.Save, IMAPFolder.Save, Message.Save and the rest) ask that the credential reach the account and take in its authority. An event script's objects carry no credential and are left as they were. Roles.md lists every route and COM member that asks this.
Which domains a role reaches
Credential
No domains named
*
A list
An assignment of serveradmin or auditor
Every domain
Every domain
Only those
An assignment of any other role, custom ones included
The account's own domain
Every domain
Only those
An API key's Domains= line, whatever its role
Every domain
Matches no domain
Only those
A custom role holding a server-wide permission needs * before an account can use it at all. The listings answer every_domain over REST, and a fourth field over COM. A credential confined to named domains sees only the assignments of accounts in those domains.
Giving a role, surface by surface
Control Panel (Windows only): Accounts & domains → Administrator roles lists the roles and who holds them. Type an address, choose a role, optionally name the domains, and press Give the role; select a row and press Take the role away. It applies at once.
Control Deck (Windows and Linux): the Roles view does the same.
REST: giving or taking away a role needs manage_roles, which only serveradmin holds and which is server-wide: the administrator password, or a serveradmin credential reaching every domain.
PUT /api/v1/role-assignments/
[email protected] {"role":"helpdesk","domains":"example.test,example.org"}
DELETE /api/v1/role-assignments/
[email protected]
GET /api/v1/role-assignments
GET /api/v1/roles
COM (Windows only): an empty role name takes the role away, and Account.RoleName, RoleDomains and RolePermissions read it back:
$app = New-Object -ComObject 'hMailServer.Application'
$account = $app.Authenticate('Administrator', 'your-admin-password')
if (-not $account) { throw 'Authentication failed.' }
$app.AssignRole('
[email protected]', 'helpdesk', 'example.test')
$helen = $app.Domains.ItemByName('example.test').Accounts.ItemByAddress('
[email protected]')
$helen.RoleName; $helen.RoleDomains; $helen.RolePermissions
With a second factor on the administrator, Authenticate refuses and the script uses AuthenticateWithCode. Application.CurrentRole and CurrentPermissions say what a session holds.
An API key: POST /api/v1/apikeys with "role":"helpdesk" writes a Role= line to hMailServerApiKeys.ini. The key store answers to the administrator password alone. The Deck's API keys view, signed in with that password, also makes a key with a role and its domains, showing the token once.
curl -u Administrator:your-admin-password \
-H "Content-Type: application/json" \
-d '{"label":"help desk tool","role":"helpdesk","domains":"example.test"}' \
https://<host>:8045/api/v1/apikeys
Use the address and port your listener answers on; bound to loopback with no certificate, it is http://127.0.0.1:8045 (https:// once a certificate, or an ACME one, is in place). A key's role decides what it reaches. The Control Panel's REST API keys page, as its reference describes it, names no role; give a key its role over REST or in the Deck. No API key signs in to the Deck.
Custom roles are made over REST from a name, a description and a comma-separated permission list; both editors list them and give them out. A role that releases held mail holds server-wide permissions, so it is given with * and reaches every domain's held mail:
POST /api/v1/roles {"name":"quarantine-desk","description":"releases held mail","permissions":"view_quarantine,manage_quarantine"}
PUT /api/v1/role-assignments/
[email protected] {"role":"quarantine-desk","domains":"*"}
A permission the server does not know is dropped, not refused, so a custom role ends with less authority than it asked for, never more. A custom role somebody holds cannot be deleted: DELETE /api/v1/roles/{name} answers 409 saying how many accounts hold it, and the Deck's Delete shows that sentence. Give each holder another role or take this one away first; assignments are never removed for you, because an account with none falls back to its admin level. A built-in role answers 400, and COM cannot delete a role. An assignment decides even when its role holds nothing: holders of a custom role whose permissions name nothing this version knows are ordinary accounts, not their admin level.
Where each role works
Role
Control Deck
REST API
Control Panel (COM)
serveradmin
Every view
Every route
Every page
domainadmin
Dashboard, DANE / TLSA, domains, fetch accounts, lists, roles and reports for its domains
Its domains' accounts, aliases, lists and mailboxes, the status, who holds a role there
Domains and Administrator roles
helpdesk
Domains, fetch accounts and lists, with Reset password and Unlock on every account
The reset, the unlock, the account listings, the trace of an address in its domains
Welcome and About only; Application.ResetPassword, UnlockAccount and GlobalObjects.MessageTrace answer a script
auditor
Every view that reads, the audit trail and reports included
Every read, the audit trail and alerts included; every write refused
Welcome, About and Administrator roles
The help desk and the auditor work in the Deck. COM's settings objects answer only the server administrator, so an auditor reads the configuration through the Deck or REST. A domain administrator is not shown the settings, rules, routes, remote domains, certificates, ports, IP ranges, groups, backup, server messages, queue, logs or audit trail, which are server-wide. The quarantine is server-wide as well, so a domain administrator confined to its domain is refused it.
Hiding a page is a courtesy, not the boundary. The server refuses the request whatever a page draws, and a Control Panel connected to a server that predates roles shows every page.
A role holder signs in with its own address and password, and its second factor where it has one. An app password never carries the role: over REST it is an ordinary account's request, and COM's Application.Authenticate and AuthenticateWithCode treat it as a wrong password. A support session into a mailbox does not carry the person's role either. A role holder's own mail and settings under /api/v1/me stay its own.
Setting up a help desk
Turn the REST listener on if it is off. It needs RestApiPort set, RestApiBindAddress set to an address staff can reach (the default, 127.0.0.1, serves this machine alone), a certificate unless it is bound to loopback, and a name staff can reach. On Windows set them on the Control Panel's API & monitoring page (Monitoring & alerts since 6.3.7) and press Apply now, since they wait for a reinitialise. On Linux run sudo hmailserver --set-setting RestApiPort=8045 and the same for RestApiBindAddress and, for any address but loopback, RestApiCertificateFile and RestApiPrivateKeyFile (or let an ACME certificate stand in), then sudo systemctl restart hmailserver. The Control Deck topic has the details.
Give each person a mailbox of their own; the role goes to that account.
Decide the domains. With none named, helpdesk reaches only the account's own domain; name several, comma separated, for a desk serving more.
Give the role on any surface above and check it with GET /api/v1/role-assignments or the Roles view.
The person signs in to the Deck at https://<host>:8045/ as themselves, then uses Reset password or Unlock on an account.
Tell them the limits: no mail reading, no reports, no domain administrator's or auditor's password, no removing a ban, and, when confined, no clearing of the logon-failure list.
Each change the help desk makes is on the audit trail as its own, over COM and REST, with actor type account and a detail ending [role: helpdesk].
What a refusal says
A permission the role lacks: 403 naming both, for example the role 'helpdesk' does not hold the permission 'manage_settings'.
A domain it does not reach: the role 'helpdesk' does not reach that domain, whether or not the account exists, so nobody can list another domain's mailboxes one request at a time. Over COM, Application.ResetPassword and UnlockAccount answer No such account. for both.
An account wider than the credential: that account holds authority this credential does not.
Over COM, since 6.3.4 a refusal carries the server's sentence rather than a bare Exception from HRESULT: 0x800403E9.
An API key on the key store, the [Settings] routes, the audit trail or the alerts: 401 with {"error":"authentication failed"}, the same as "not a key".
The two stores only the administrator password opens
The key store (/api/v1/apikeys) and the [Settings] section of hMailServer.ini (/api/v1/settings/ini) answer to the administrator password and nothing else: not an account holding serveradmin, not a key of any role or scope. A key that could mint keys could issue itself an unrestricted replacement, and the file holds the server's secrets. An account is refused 403 with the reason, a key 401.
The audit trail (/api/v1/audit) and the alerts (/api/v1/alerts/...) are refused to every API key with the same 401, so a stolen key cannot read what it did or silence the alerting. An account whose role reaches every domain reaches them through view_audit, view_configuration, manage_settings or manage_server; a confined role is refused them.
Upgrades, the audit trail and backups
The upgrade. Schema step 6047 adds hm_roles, seeded with the four built-in rows, and hm_roleassignments, and writes no assignment. Roles are data, not settings: nothing goes into hm_settings or hMailServer.ini.
The audit trail. Every role created, changed or deleted, and every role given or taken away, is a row naming the credential that did it; an assignment's row reads like role: helpdesk -> auditor; domains: the account's own domain -> every domain. The actor type follows the role: the administrator password and holders of serveradmin or domainadmin are administrator, every other account is account.
Backup and restore. An account's assignment travels with it, by role name, and the custom roles travel whenever the domains or the settings do, restored first. A role the restoring server already has is kept as it is, with any difference named in the backup log. The admin level waits until the assignment is written, so a restore that stops part-way leaves no account wider than its role. An assignment naming a role neither side has is dropped, the level stays withheld, and HM6129 names the account. No archive carries the API key store.
What it does not do
No mapping from an identity provider. There is no claims mapping; an identity provider saying somebody is an administrator is not obeyed. The server's roles and oidc_administrators or saml_administrators decide.
No role on an app password or a support session.
No Control Panel or COM on Linux: roles are given there in the Deck and over REST.
Where to read more
Roles.md, the authority on all of this.
Sign-in and Identity §7, The Control Deck, The REST API §2 and The COM API and Scripting §23.2.
AlertsAndAuditTrail.md, Backup.md and Reports.md.
On this forum: The Control Deck: browser administration at /, The REST API: turning it on, credentials and roles, The COM API on Windows, Hardening a public mail server, Alerts and the audit trail and IP ranges, auto-ban and lockout.