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. Scripting & the COM API
  4. The COM API on Windows: object model, Save() since 6.3.4, roles and the Import Tool

The COM API on Windows: object model, Save() since 6.3.4, roles and the Import Tool

Scheduled Pinned Locked Moved Scripting & the COM API
windowsapiadministrationupgrademigration
1 Posts 1 Posters 138 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.7 (17 October 2026) confines an account given the serveradmin role with a list of domains to those domains over COM as over REST, so it is no longer the server's administrator: give the role with no list to an account that is to administer the whole server, a script's or a Control Panel's account among them. Over COM, one given domainadmin with a list that leaves out its own domain no longer administers that domain's ordinary accounts; 6.3.4 to 6.3.6 allowed both where REST refused them. A role is given or taken away only by a credential that holds manage_roles in the account's domain and takes in what the account holds now and afterwards, and a credential confined to named domains is refused over COM, as over REST, a domain's rename, a domain alias, a relay host changed to an address that is not public and an action on a domain's or an account's rule that reaches one of the server's own addresses, a configured route or a function of the event script. Rule.Save and RuleAction.Save refuse, on a domain's or an account's rule, a Set header value of From, Sender, Resent-From, Resent-Sender or DKIM-Signature and a Reply from an address the owner may not send as, and on a rule of every owner a Set header value of the loop count; one stored before the upgrade is taken without that action, and the application log names it. For a rule of every owner they refuse a Run function that is not a plain function, which one stored before still meets when the rule fires, as since 6.3.4. Utilities.CheckSieveSyntax refuses a Sieve script that adds or deletes From, Sender, Resent-From, Resent-Sender, DKIM-Signature, Auto-Submitted or the loop count. Once the database has ended a Database object's transaction, every later statement is refused and Database.CommitTransaction fails: call Database.RollbackTransaction and run the whole transaction again, or stop. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026) signs a name in over COM, as at every other sign-in, only when it is the account's stored address, compared in lower case after a domain alias and the default domain: a mailbox address spelt otherwise, in more than the case of the letters A to Z, or with white space at either end, may no longer sign in; it fails as a name that does not exist fails, and is counted as a failure. See the 6.3.8 announcement.

    This topic covers the COM API of hMailServer 6.3.4: what a script connects to and how it authenticates, the object model and the shape every collection shares, working PowerShell examples, the change 6.3.4 makes to when a configuration change takes effect, what 6.3.4 adds to the object model, administrator roles over COM and why API keys are not there, the changed-from-stock document, the COM surfaces of distribution lists and the directory, and the Import Tool. The one thing to know first is that the COM API is Windows-only, and that an object you add to a collection, or an item of one you change, is not stored until you call its Save(); a few writes need none, among them an account's SieveScript and Settings.SetIniSetting, and an address saved or deleted through Route.Addresses needs no Save() of the route. Since 6.3.4 the running server's routes, relays and other shared configuration objects also wait for that Save(); up to 6.3.3 they changed the moment a property was set.

    What you are connecting to

    hMailServer exposes a full COM API. Almost anything the Control Panel can do, a script can do, because the Control Panel does its work through COM. The exceptions are the pages that edit a file on the server itself: REST API keys (described under roles below), the two directory pages' [LDAP] section, the sending limits on Server limits & expert settings, the [Database] rows of the Performance page, and the event script; and Discovery, which reads the REST listener because eDiscovery has no COM interface. The COM API and the Control Panel are both Windows-only: a Linux server is administered over REST, hmctl and the Control Deck (hMailServer on Linux: what you get and what you do not).

    • One type library, hMailServer, with 94 coclasses in 6.3.3 and 100 in 6.3.4. The root is Application, ProgID hMailServer.Application.
    • Every interface is a dual IDispatch interface, so late binding works without registering the type library. CreateObject, New-Object -ComObject and C# dynamic all work on a machine where only the server is installed.
    • Early binding depends on vtable order, so new members are always appended, never inserted. An interop assembly built against 6.3.3 keeps working; recompile it to reach the new members, and on a hand-copied installation register the server again, elevated, so that the new interfaces are known to COM. Late-bound scripts need nothing.
    • The installer's admintools component exists only to register the type library, so that scripts on another machine can administer this one.
    # Local
    $app = New-Object -ComObject 'hMailServer.Application'
    
    # Remote, over DCOM, against a machine where admintools is registered
    $app = [Activator]::CreateInstance([Type]::GetTypeFromProgID('hMailServer.Application', 'mail01'))
    

    Authenticating

    $app = New-Object -ComObject 'hMailServer.Application'
    $account = $app.Authenticate('Administrator', 'your-admin-password')
    if (-not $account) { throw 'Authentication failed.' }
    

    Authenticate returns an Account object, or $null on failure.

    Sign in as Gets
    Administrator with [Security] AdministratorPassword from hMailServer.INI server administrator
    A mailbox address whose AdminLevel is 2 server administrator
    A mailbox address whose AdminLevel is 1 domain administrator: their own domain only
    A mailbox address that holds a role (since 6.3.4) what the role's permissions reach, in the domains it reaches

    An empty administrator password is an open door. If the administrator password is empty, and no administrator TOTP secret is enrolled, every COM session starts already authenticated as a server administrator. This is the historical behaviour.

    A second factor changes the call you must make. With Settings.EnrolAdministratorTOTP() used, Authenticate refuses the administrator outright and AuthenticateWithCode is the only way in:

    if ($app.AdministratorTOTPEnabled) {           # readable BEFORE authenticating
       $app.AuthenticateWithCode('Administrator', $password, $code) | Out-Null
    } else {
       $app.Authenticate('Administrator', $password) | Out-Null
    }
    

    Three things about that sign-in change in 6.3.4:

    Change, since 6.3.4 What a script sees
    A one-time code is taken once a TOTP code the server has accepted is refused if it comes again, for the administrator and for an account alike; the refusal is not counted as a wrong code
    A wrong code is counted AuthenticateWithCode checks the password first, and a wrong code after a right password counts against a budget of five in fifteen minutes, then no code is taken for fifteen
    An app password is not a COM logon Authenticate and AuthenticateWithCode answer an app password as they answer a wrong password; it still opens the mailbox over IMAP, POP3 and SMTP

    How a refusal reaches you. If you are not entitled to an object, every member returns an error HRESULT: a terminating exception in PowerShell, a trappable error in VBScript, a COMException in C#. Up to 6.3.3, seventy-two COM classes answered a refusal as a bare Exception from HRESULT: 0x800403E9. Since 6.3.4 a refusal arrives with the server's own sentence.

    The object model

    From Application What is there
    Domains Domain, and under each Accounts, Aliases, DistributionLists, DomainAliases and, since 6.3.4, Rules
    Account (under Accounts) Address, Password, Active, MaxSize, Size, AdminLevel; Messages, IMAPFolders, Rules, FetchAccounts, AppPasswords; SieveScript, TOTPEnabled
    Settings the server-wide properties and the sub-objects AntiSpam (which owns Quarantine), AntiVirus, Backup, Cache, Directories, Groups, IncomingRelays, Logging, MessageIndexing, PublicFolders, Routes, Scripting, SecurityRanges, ServerMessages, SSLCertificates, TCPIPPorts; since 6.3.4, RemoteDomainPolicies
    Rules the global rules
    Status UndeliveredMessages, StartTime, ProcessedMessages, RemovedViruses, RemovedSpamMessages, SessionCount
    Utilities helpers such as CheckSieveSyntax, CriteriaMatch, MakeDependent, PerformMaintenance, ImportMessageFromFileToIMAPFolder
    Database ExecuteSQL, ExecuteSQLWithReturn, RequiresUpgrade, IsConnected, DatabaseType
    BackupManager StartBackup, LoadBackup
    GlobalObjects DeliveryQueue, MessageTrace, Languages
    Links Domain(DBID), Account(DBID), Alias(DBID), DistributionList(DBID)
    Diagnostics DiagnosticResults, each a DiagnosticResult

    Every collection offers the same shape: Count, Item(index), ItemByDBID(id), Add(), Refresh(), Delete(index) and DeleteByDBID(id), with ItemByName or ItemByAddress where a name exists. Items have Save() and Delete().

    Add() does not persist. It returns a new, unsaved object. Set the properties, then call Save(). A script that forgets Save() looks as though it worked and changes nothing. In PowerShell, keep the object in a variable rather than chaining.

    Working examples

    $app = New-Object -ComObject 'hMailServer.Application'
    $app.Authenticate('Administrator', 'your-admin-password') | Out-Null
    
    # Add a domain
    $domain = $app.Domains.Add()
    $domain.Name   = 'example.com'
    $domain.Active = $true
    $domain.Save()
    
    # Add an account
    $account = $domain.Accounts.Add()
    $account.Address  = '[email protected]'
    $account.Password = 'a-strong-password'
    $account.Active   = $true
    $account.MaxSize  = 1000          # MB, 0 = unlimited
    $account.Save()
    
    # Report on every mailbox
    foreach ($d in $app.Domains) {
       foreach ($a in $d.Accounts) {
          '{0,-40} {1,8} MB used' -f $a.Address, [math]::Round($a.Size, 1)   # Size is already in MB
       }
    }
    

    Three units, three objects. Account.Size is megabytes. Dividing it by 1MB again, which older examples do, prints 0 for every mailbox. Message.Size is kilobytes, and the Message size rule criterion is bytes.

    Find one account without walking every domain:

    $domain  = $app.Domains.ItemByName('example.com')
    $account = $domain.Accounts.ItemByAddress('[email protected]')
    

    Send a message from a script. Message is a creatable coclass in its own right, and Save() on a new one queues it for delivery:

    $m = New-Object -ComObject 'hMailServer.Message'
    $m.FromAddress = '[email protected]'
    $m.From        = 'Postmaster <[email protected]>'
    $m.AddRecipient('Alice', '[email protected]')
    $m.Subject     = 'Scheduled maintenance tonight'
    $m.Body        = 'The server will restart at 22:00.'
    $m.Save()
    

    The queue, and a backup:

    $app.Status.UndeliveredMessages          # tab-separated text, one row per message
    $app.GlobalObjects.DeliveryQueue.ResetDeliveryTime()
    $app.GlobalObjects.DeliveryQueue.StartDelivery()
    
    $b = $app.Settings.Backup
    $b.BackupDomains = $true; $b.BackupSettings = $true; $b.BackupMessages = $true
    $b.Destination   = 'D:\backups'
    $app.BackupManager.StartBackup()
    

    The [Settings] keys are reachable generically:

    $app.Settings.IniSettingNames                       # every key the server knows
    $app.Settings.GetIniSetting('ManageSieveServerPort')
    $app.Settings.SetIniSetting('ManageSieveServerPort', '4190')
    

    Up to 6.3.3 a change this way usually needed a restart. Since 6.3.4 the keys are kept in the database, and Settings.IniSettingApplies(name) says whether a save applies a key at once, at a reinitialise or at a restart; Settings.PendingIniSettings lists the saved ones the server does not run on yet. Settings: when a save takes effect, and the three levels has the counts and what becomes of the file's copy.

    What changed in 6.3.4: a shared object takes effect at Save()

    Some collections are not loaded for your COM session alone: the running server reads the very same items while it works. They are the routes and their addresses, the incoming relays, the DNS black lists, the SURBL servers, the remote domain policies, the server messages, the blocked attachments, the SSL certificates and the groups.

    Up to 6.3.3, setting a property on one of those items wrote the live object every SMTP, IMAP and delivery thread was reading, which let a session read a string while it was being freed. Since 6.3.4:

    • The first property you set makes a copy, held by your COM object, and every later set writes the copy. Reading a property back from the same object reads what you set.
    • Nothing is in effect until Save(). Save() writes the copy to the database and the server reads the collection again.
    • A property set and never saved changes nothing at all. Up to 6.3.3 it changed the running server until the next restart while the database kept the old value. A script that relied on that must now call Save().
    • An object read before the save keeps what it read. After a Save(), read the item again from its collection (ItemByDBID, ItemByName) to see the saved values.
    • A route's addresses. A change to an existing address waits for that address's Save(), like every item here. An address saved or deleted through Route.Addresses reaches the running route at once, without the route's own Save(), as it always did. A RouteAddresses list read before the route's save is the old route's list, though an address saved or deleted through it still reaches the running route.
    • A save the database refuses leaves the running item as it was, and your object keeps the refused values, to be corrected and saved again. A remote domain policy is the exception: it is read back as the database holds it.
    • A save whose re-read fails still answers success, because the save succeeded, and is reported as error HM7390: the change is in the database and not yet in effect, until a reinitialise, a restart, or another save.

    The Control Panel reads each item fresh, sets, saves and reads again, so it is not affected. The other collections were never shared this way and behave as before: TCP/IP ports, security ranges, the white list, the greylisting lists, blocked senders, sender list entries, and everything loaded for the COM object itself (domains, accounts, aliases, lists, rules, fetch accounts, app passwords, folder permissions).

    What 6.3.4 adds to the object model

    Six new interfaces make 100 coclasses, and the IDL has 1,884 HRESULT declarations against 6.3.3's 1,375. The wiki page lists every member; this is the shape of it.

    Interface Added in 6.3.4
    Application Roles, CurrentRole, CurrentPermissions, RoleAssignments, AssignRole, ResetPassword, UnlockAccount
    Settings RemoteDomainPolicies; GetChangedFromDefault, GetSettingDefault; IniSettingApplies, IniSettingAppliesAll, PendingIniSettings; the profiles (GetProfiles, ApplyProfile, RevertProfile and the rest)
    Domain Rules; the quota policy; the domain transforms and disclaimer; SenderList; the DKIM rotation members; SpamTraps
    Account MoveTo; GetDevices, RevokeDevice; RoleName, RoleDomains, RolePermissions; the nine Directory properties; ExportArchive; Kind (0 a person, 1 a meeting room, 2 a piece of equipment)
    Utilities among others GetAuditTrail, VerifyAuditTrail, RevertAuditedChange, GetSetupChecklist, MoveMailbox, RunDkimRotation
    DistributionList Membership, and the list manager's members
    BackupManager, BackupSettings LoadEncryptedBackup, RestoreMailbox, RestoreMessage; EncryptionPassphrase (write-only), EncryptionPassphraseSet
    New RemoteDomainPolicies and RemoteDomainPolicy; SenderListEntries and SenderListEntry; GreyListingBypassEntries and GreyListingBypassEntry

    eServerState gains hStateDraining (5). The rule criteria gain eFTAttachmentName (9), eFTSensitiveContent (10), eFTAttachmentType (11), eFTExternalRecipient (12) and eFTContent (13), and the rule actions eRAAddDisclaimer (11), eRAReject (12), eRARequireTLS (13), eRAQuarantine (14) and eRAWarnSender (15).

    Roles over COM, and why API keys are not

    Since 6.3.4, authority is a role: serveradmin, domainadmin, helpdesk, auditor or a custom one, given to an account for named domains. Nothing changes on the day you upgrade: an account with no assignment holds exactly what its AdminLevel has always given it. An account with a role signs in with its own address and password, and COM asks the same question REST asks. Application.CurrentRole and CurrentPermissions say what the session holds. Administrator roles end to end has the permissions, the domains a role reaches and the Control Panel pages each role sees.

    Giving a role over COM is Application.AssignRole("[email protected]", "helpdesk", "example.test"), with an empty role name to take it away. Account.RoleName, RoleDomains and RolePermissions read it back. Custom roles are made over REST (POST /api/v1/roles), and COM has no way to delete a role at all.

    A help desk and an auditor over COM. A help desk's script has Application.ResetPassword, UnlockAccount and GlobalObjects.MessageTrace; COM's settings objects answer only the server administrator, so an auditor reads the configuration through the Deck or REST.

    Refusals that do not give anything away. Application.ResetPassword and UnlockAccount answer No such account. alike for an account that does not exist and for one the role does not reach. Settings.ClearLogonFailureList answers the server administrator alone.

    The question 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) each 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.

    API keys are not a COM object. The API key store has never been COM-visible. The key store (/api/v1/apikeys) answers to the administrator password and to nothing else, and a key is given a role with POST /api/v1/apikeys and "role":"helpdesk". The Control Panel's REST API keys page does not go through COM either: it edits hMailServerApiKeys.ini directly.

    What this server has changed from stock

    Since 6.3.4, Settings.GetChangedFromDefault answers every setting whose value is not the one this version ships with, with the shipped value beside it: the same document, byte for byte, as GET /api/v1/settings/changed. Only the administrator password may read it; an API key of any scope and an account holding an administrative role are both refused. The settings topic says what each entry carries.

    Distribution lists and the directory

    A list that defines itself. Since 6.3.4, DistributionList.Membership is a string property, read and written like the moderator and bounce addresses, holding a membership query whose accounts are added to the list's own recipients. A domain administrator over COM may set domain, a pattern: in a domain it holds, or nothing; the server administrator may set any form, and a query the server cannot answer is refused when it is saved. Aliases, distribution lists and membership queries has the five forms and how a query is answered.

    The directory. Since 6.3.4 an account has Account.DirectoryTitle, .DirectoryDepartment, .DirectoryPhone, .DirectoryMobile, .DirectoryOffice, .DirectoryOptOut, .DirectoryNote, .DirectoryPhoto (base64) and .DirectoryPhotoType (read-only). The row is read at the first get and written by Account.Save() with the rest. A change of the opt-out through Account.Save() is recorded on the audit trail as directory-opt-out-changed.

    The Import Tool

    The Import Tool is a Windows program (Start menu > hMailServer > Import Tool) that works through the COM API: it brings mbox files into an account, handing each message to the server through Utilities.ImportMessageFromFileToIMAPFolder, reads a Maildir with its flags, and creates accounts in bulk from a text file. Bringing mailboxes in covers each of its jobs.

    The breach check applies to a COM script. It is off by default. Where it has been switched on, it runs whenever a password is set through COM Account.Password, the Import Tool's included, and a bulk operation makes one request per account: run a large import against a mirror, or with the check off.

    What it does not do

    • The COM API and the Control Panel do not exist on Linux, and there is no Import Tool on Linux.
    • The Import Tool does not read PST files. The file goes into the server's import directory, and REST, hmctl or the Control Deck start the job.
    • COM has no API keys and cannot delete a role.
    • These features have no COM surface at all and are REST only: SCIM provisioning, SAML and OpenID Connect sign-in, passkeys, the S/MIME authority, the send API, migrations, IP warm-up, eDiscovery, mail in to a webhook and mailbox encryption at rest, among others the wiki lists.

    Where to read more

    • The COM API and Scripting, sections 23.1 to 23.5, and APIs Reference, sections 8 and 9.
    • Roles.md, ChangedFromStock.md, DistributionLists.md and Directory.md.
    • Migration.md for the Import Tool, and Upgrading.md for what an upgrader's scripts now see.
    • The public documentation's COM API chapter and its Import Tool section.
    • On this forum: Upgrading to 6.3.4, the 6.3.4 announcement, event scripts, and the REST API for what COM does not reach.
    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