Skip to content
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
  • 0 Votes
    1 Posts
    143 Views
    ProgressiverobotP
    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.
  • 0 Votes
    1 Posts
    126 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) bears on relocating a Windows installation to another drive. hMailServer.exe /ProtectFolders, which setup runs at every installation and upgrade, now hardens the program folder, Bin and ControlPanel: where hMailServer was installed in a folder that took its permissions from the root of a drive, any account that could sign in to the computer could replace the programs the service runs (CVE-2026-107571); the default folder under Program Files was never affected. The procedure below copies the tree and re-registers it without running setup, and a folder below a drive's root takes the permissions the root hands down, so an installation relocated there is hardened by the upgrade to 6.3.7, or on 6.3.7 by running hMailServer.exe /ProtectFolders as an administrator, not by the move (on 6.3.6 or earlier follow the icacls steps in ServiceAccount.md, since those versions' /ProtectFolders leaves the three program folders as they are); before upgrading an installation at a drive's root, follow the steps "Before upgrading an installation at a drive's root" in ServiceAccount.md. Where accounts that are not administrators could sign in to the computer while the installation was in such a folder, the release says to reinstall the programs into a new folder that only the administrators may change, not over the installation, with setup given the new folder by /DIR=: over an installation setup installs into the folder it finds, and a 32-bit installation's uninstall entry overrides /DIR= until it is taken away, which the "re-run the installer" repair below meets. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026) has setup make or harden the program folder, Bin and ControlPanel before it copies a file or runs a program of the installation. One of them that another account could change, as a folder below a drive's root can be, setup hardens and then refuses, with exit code 7, until Windows has restarted; and a folder or a link on the way to them, or a program, library, script or settings file in them, that another account than SYSTEM, the Administrators and TrustedInstaller controls, it refuses with exit code 7, naming the icacls that deals with it and a restart of Windows before setup runs again. On Windows 6.3.8 may also refuse, with HM7534 or HM7536, TlsCaFile, PostgreSQLSslRootCert, a TLS certificate or private key file, OAuth2PublicKeyFile or AcmeCertificateDirectory where its path holds characters outside ASCII, so give the new root, and the folders above it, an ASCII name. See the 6.3.8 announcement. This guide covers two different moves. The first is relocating a configured installation to another directory or drive on the same machine, for example from C:\Program Files (x86)\hMailServer to C:\Program Files\hMailServer. The second is renaming a mailbox or moving it to another domain, which is an operation of its own since 6.3.4. Moving to new hardware is a third thing, and Moving to new hardware, and why Windows to Linux is a rebuild covers it. The one thing to know first: relocating is not one change. The installation path is recorded in the registry, in the service, in the COM registration, in hMailServer.ini and in a handful of database columns, and since 6.3.4 a [Settings] path edited in the file is ignored in favour of the value stored in the database. The relocation runbook is written in Windows terms: the registry, the service and the COM registration. The documents used here give no separate procedure for a Linux installation. Installing somewhere new is a different, solved problem: /DIR="<path>" on the installer puts a fresh installation wherever you want. The good news first The mail store itself is already relocatable. Message rows hold a bare {guid}.eml with no directory, and the path is rebuilt at every read against the data directory. Moving the data directory and pointing DataFolder at its new home moves every message with it. The exception is a database that predates hMailServer 5.4, where the column can hold a full path. Before you touch anything Take a backup and verify it restores (Backup and restore). Everything below is reversible only while the old tree and a good backup both exist. Ask the server whether its message rows are relative. In the Control Panel, Monitoring & troubleshooting → Diagnostics → Test message file locations. If it reports ERROR: Full paths are stored in the database, the move is still possible, but converting the paths becomes mandatory rather than optional. Write down what you are about to strand. The same Diagnostics page has an Installation paths section that lists the registry root, every [Directories] key and every path-shaped setting the running server holds, with the ones that do not exist marked. It changes nothing. The inventory Where What Repaired by Bin\hMailServer.ini, [Directories] ProgramFolder, DataFolder, DatabaseFolder, LogFolder, TempFolder, EventFolder Editing the file Bin\hMailServer.ini, [Database] PostgreSQLSslRootCert and TlsCaFile Editing the file. Leave the connection keys alone: they name a server, not a path The settings store (hm_inisettings) ArchiveDir, AcmeCertificateDirectory, OAuth2PublicKeyFile, UpdateTrustRootsFile, UpdateLogPublicKeyFile, and the certificate and private key files of the REST API, metrics and web services listeners Storing the new value, not editing the file (since 6.3.4) Registry, HKLM\SOFTWARE\hMailServer in the 32-bit view InstallLocation By hand Registry: the service's ImagePath, LocalServer32 (79 keys), InprocServer32 (15), the TypeLib values hMailServer.exe /Register Registry, HKLM\...\Uninstall\hMailServer_is1 InstallLocation, UninstallString, DisplayIcon Nothing; re-run the installer The database Certificate and key files, DKIM key files, the client-certificate CA bundle, backupdestination, the ClamWin and custom scanner paths, rule actions Each on its own page What breaks when a key is left behind. With a stale DataFolder the store appears empty. With a stale LogFolder logs are written to the old tree, silently. With a stale EventFolder event scripts are not found and events stop running. The 32-bit view is not optional. The server reads InstallLocation through KEY_WOW64_32KEY, and the installer writes it there. Writing the 64-bit view changes nothing and looks like it worked. The procedure Stop the service, and confirm it is stopped. Nothing here may run against a live server. Copy the tree. Copy, do not move: the old tree is the rollback. Include Bin\hMailServerApiKeys.ini if it exists, since the REST API key store lives there and no installer manifest mentions it. Grant the service account access to the new tree as 6.3.8's RelocatingAnInstallation.md does: a copy takes the permissions of the folder it lands in (under Program Files every user's Read on the new Data, Logs, Temp, Database, Events and hMailServer.INI) and not the old tree's entries for NT SERVICE\hMailServer, so the document makes the new root with the permissions setup gives a program folder before the copy, runs hMailServer.exe /ProtectFolders from the new Bin after step 5's /Register, and grants a service that runs as anything but LocalSystem its folders again with /GrantServiceAccess. A server that cannot write its log directory starts and then says nothing. Rewrite the file keys in <new root>\Bin\hMailServer.ini whose value was under the old root: the [Directories] folders, and [Database] PostgreSQLSslRootCert and TlsCaFile, the second named beside the first by 6.3.8's document, since with TlsMode=verify-full a CA file left in the old tree stops the server reaching its database once that tree is deleted (HM7531). Nothing else, and not a folder that was outside the old tree and has not moved: a DataFolder pointed at an empty directory makes the store appear empty. Repoint the registry root, from an elevated prompt: reg add "HKLM\SOFTWARE\hMailServer" /v InstallLocation /t REG_SZ /d "<new root>" /reg:32 /f. /reg:32 is the whole of the point. Re-register the server, from an elevated prompt, from the new Bin: hMailServer.exe /Register. It registers the path of the executable that runs it. Start it, and check what it read. Read the application log, and since 6.3.4 the ERROR log too. Confirm that the Diagnostics page's Installation paths shows the new paths (6.3.8's document names it; the Control Panel has no Settings → Directories page), that your domains are listed, and that the log file you are reading is under the new log directory. Convert full message paths, only if the diagnostic found any: run the Data Directory Synchronizer, in its import mode, on the old installation before step 2 copies anything, as step 0 of 6.3.8's RelocatingAnInstallation.md has it, not against the new data directory after the move: it converts only a row whose full path is where the file is found, so after the move it would import every such message a second time, and its other mode would delete the copies. A hand-written UPDATE hm_messages SET ... is not a supported converter. Store the [Settings] paths and fix the database-borne paths (both below). Verify before deleting anything: send and receive a message; complete a TLS handshake on every port that has a certificate; run a backup to completion; run an event script, if you use them; check the log directory is being written. Keep the old tree, unreachable by the service, for at least one full backup cycle. Do not make it unreachable until the [Settings] paths are stored and a reinitialise has picked up the new certificate and ACME paths. The failure this prevents. A server whose anchors are stale reports Running and serves an empty configuration. That failure is silent. The [Settings] paths: 6.3.3 and earlier, and since 6.3.4 On 6.3.3 and earlier these are [Settings] keys of hMailServer.ini like any other, and editing the file moves them. Since 6.3.4 it does not: the stored value is used, the edit is named in the error log as HM5804, and, where the service can write the file, the line is put back to the old path. RelocatingAnInstallation.md and the wiki page between them give three ways to store the new values. The first is the document's own, and the one to use: When How Where it is described After the moved server is running In the Control Panel under Settings, with Settings.SetIniSetting over COM, PUT /api/v1/settings/ini/{name}, or hmctl ini set <name> <value> RelocatingAnInstallation.md, step 9, and the wiki Before stopping the old server The same four ways, as the last thing before the service is stopped. A save applies ArchiveDir, OAuth2PublicKeyFile, UpdateTrustRootsFile and UpdateLogPublicKeyFile at once, not at the next start as the wiki page has it, so the running old server turns to a tree not yet copied: archiving stops, and token validation and update verification refuse. The database then holds the new paths, so going back to the old tree means storing the old values again The wiki, corrected by SettingsApply.md At the first start, if the service is already stopped A [SettingsOverride] section in the new hMailServer.ini, named in the error log as HM5807; then store the same values and remove the section before the next restart The wiki A save applies every one of these paths at once except the listeners' certificate paths and the ACME certificate directory, which are bound when the servers start and wait for a reinitialise (Apply now, or POST /api/v1/server/reinitialize). Until then the server runs on the old paths, which still resolve because the tree was copied rather than moved. PasswordPepper is the exception to all of this: it lives in the file only, is not a path, and comes across with the copied file. The database-borne paths SSL certificates. Since 6.3.7 the Control Panel's SSL certificates page has Edit…, which changes a certificate's name and files and keeps its id, so every port that presents it keeps it and reads the new files at the next reinitialise; deleting a certificate and adding it again, as the document had it until 6.3.8, leaves each port that presented it with none, and such a port does not start (HM5113). It can also be set over COM. DKIM keys. Domain → DKIM, the primary and, if used, the secondary. A domain whose key rotation manages its keys refuses that edit. Client-certificate CA bundle, per port. A port set to require a client certificate refuses every client when its bundle cannot be loaded. Archive index. hm_archiveindex.archivepath is matched on the exact string by retention, legal hold and the address eraser. Do not bulk-rewrite it. If the archive directory moved, move the archive back, or accept that the rows written before the move point at the old location; there is no supported converter for this column. What stays broken, and why Add/Remove Programs still points at the old unins000.exe. Do not edit unins000.dat. The supported repair is to re-run the installer at the new root. Start-menu shortcuts hold absolute targets and nothing repairs them. Message content. The old path appears inside messages the server itself wrote, such as a delivery report. Never rewrite message content. Renaming a mailbox, or moving it to another domain New in 6.3.4 (schema 6056, whose table hm_mailboxmoves is the move's journal). new_address carries the domain, so renaming and moving are the same operation: [email protected] to [email protected] is a rename, [email protected] to [email protected] is a move. A move keeps the account's row, so everything keyed on the account id follows without being touched: rules, folders with their subscriptions and access-control entries, application passwords, the second factor, contacts, calendars and the role assignment. Copying the mail into a second account loses all of it. Interface How Control Deck The Move or rename card in the account editor hmctl hmctl mailbox status [email protected], then hmctl mailbox move [email protected] [email protected] REST POST /api/v1/accounts/ann%40example.com/move with {"new_address": "[email protected]", "keep_alias": true}; GET on the same path says whether anything holds the mailbox PowerShell Move-HmMailbox -Address [email protected] -NewAddress [email protected] Control Panel (Windows) Move or rename… on the account's General tab COM (Windows) Account.MoveTo(NewAddress, KeepOldAddressAsAlias) or Utilities.MoveMailbox(Address, NewAddress, KeepOldAddressAsAlias) The old address is kept as an alias by default, pointing at the new address. Turn it off (keep_alias false, --drop-alias) only when the address is to stop working: mail to it is then refused at RCPT TO. What moves: the message store directory <data>/<domain>/<local part>; the Sieve tree; aliases in any domain that point at the account; distribution list memberships; recipients of mail still in the delivery queue; and quarantined mail, up to a ceiling of 200,000 rows. What does not move, on purpose. The archive and the message trace stay under the old address, as a record of the past. Erasure works by address, so erasing the new address will not reach the archive written under the old one: erase both (Compliance: personal data and erasure). The domains a role reaches are not rewritten either, so check a role after a cross-domain move (Administrator roles end to end). What is refused, before the first byte is moved: a domain this server does not hold, or one that is not active; a destination domain at its maximum number of accounts, or that would exceed its maximum total size; an account, an alias or a distribution list already at the new address; a mailbox encrypted at rest moving to another domain (409); a rename inside the domain is not refused; an installation whose message file names are still full paths; a credential that may not administer accounts in both domains; a mailbox in use. In use means a POP3 session, or an IMAP session with one of the account's folders selected. What is not counted: an IMAP session that has selected nothing, a webmail request in flight, a ManageSieve session and an SMTP delivery to the mailbox in flight. A message that arrives during the copy is written under the old directory, and the old directory is swept once afterwards; anything that could not be carried is HM6948. That is why a move of a large busy mailbox belongs out of hours. When a move fails or is interrupted There is no transaction across a filesystem and four database backends, so the order is chosen to make each step undoable: the store, then the account row, then the references, then the alias. A journal row is opened before the first step and closed after the last, and an interrupted move is said once per start as HM6947 in the application log, naming both addresses and the stage it stopped at. Stage What it means started Nothing has been changed moving the store The copy was under way; look at both directories. The database has not been touched store moved The directories are at the new address. While the account row says the old one, the account authenticates and its mail is unreadable account written The store and the account row agree; the references have not all been rewritten references rewritten Everything but the alias complete The move finished rolled back A step failed and everything before it was put back rollback failed Something could not be put back; HM6940 is in the application log To finish a move interrupted at moving the store or store moved, ask for the move again, naming the address the directories now have. Read the journal with hmctl mailbox status or GET /api/v1/accounts/<address>/move. The journal is not in the backup. Code Meaning HM6941 The move succeeded but the alias for the old address could not be created HM6942 Aliases pointing at the account could not be re-pointed HM6943 Distribution list memberships could not be moved HM6944 Messages in the delivery queue still name the old address HM6945 Quarantined messages still name the old address HM6946 The move could not be journalled. It still happened, and is on the audit trail Who may. manage_accounts, in both domains. The server administrator and a domain administrator of both domains have it; the help-desk role and the auditor do not. view_accounts is enough to read the journal. A move is one entry on the audit trail: object type account, action moved. What it does not do The relocation runbook does not repair the uninstall entry or the shortcuts, and it has no converter for the archive index. A mailbox move does not wait for a delivery in flight; nothing in the server records one. Setting Account.Address and saving goes through the same implementation, but leaves no alias, does not refuse while the mailbox is in use, and cannot cross domains. A domain rename (Domain.Name and Save()) is a different thing again, and keeps the path it has always taken. Where to read more Wiki: Relocating an Installation. Documents: RelocatingAnInstallation.md and MailboxMove.md. The public documentation: 33.1, the installation on disk. On this forum: Moving to new hardware, and why Windows to Linux is a rebuild, Upgrading to 6.3.4, and how the settings store works and when a save takes effect in Settings: what is not stock, when a save takes effect, and the three levels.
  • 0 Votes
    1 Posts
    143 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) has the migrations as a card on the Control Panel's Backup, restore & migration page, where this topic says there is no Control Panel page; and a message collected from an external account is sent to SpamAssassin with the Return-Path the other server recorded, where it was the guess from the From field, so its SPF and envelope rules may score such mail differently. See the 6.3.7 announcement. The Windows installer of hMailServer 6.3.8 (2 November 2026) refuses an installation of the original hMailServer's 5.7 (5.7.0, its pre-release builds 2788 and 2843, 5.7.1 and 5.7.2), which this fork's upgrade chain has no step from, before it changes anything, and the installers of 6.3.7 and earlier must never be run over one: such a server's mail comes in by a migration of kind imap, as Migration.md's From the original hMailServer says. See the 6.3.8 announcement. This topic covers every route 6.3.4 offers for bringing existing mail into this server: the webmail's Other accounts page for one person, an external account with the mirror on for a few mailboxes that must stay in step, migration jobs for hundreds of mailboxes from an IMAP server, Google Workspace or Microsoft 365, the Import Tool for mbox files and a Maildir, and the PST import for Outlook data files. It ends with the mailbox move, for a mailbox that is already here and needs a new address or a new domain. The one thing to know first: every route needs the local account to exist. Create the accounts with the Import Tool's text-file import, POST /api/v1/domains/{domain}/accounts, a spreadsheet over REST and hmctl, or SCIM, and only then bring the mail. Moving an installation is a different subject: Upgrading to 6.3.4 for an upgrade and Moving to new hardware for a new machine. Which route You are moving Use Keeps Does not keep One person's own mailbox, by that person The webmail's Other accounts (since 6.3.4) Folders as copies, then new mail collected Rules, signatures A few mailboxes that must stay in step until the cut-over An external account with Mirror every folder on (6.3.3 and later) Folders, every message byte for byte, the five standard flags, the internal date Subscriptions, custom keywords, ACLs Many mailboxes from an IMAP server, Google Workspace or Microsoft 365 A migration, a job per mailbox (since 6.3.4, schema 6090) The above, with keywords, special-use folders, contacts and calendars Rules, signatures, shared mailboxes, ACLs, Gmail's labels as labels mbox files The Import Tool, one folder per file (Windows only) Every message, the date from the headers Flags A Maildir The Import Tool's Select Maildir (Windows only) The Maildir++ hierarchy, the flags in the file names Custom keywords Outlook PST files PST import, a migration of kind pst (since 6.3.4) Folders, messages rebuilt as Internet mail, contacts, calendar items, tasks Attachments by reference, OLE objects, distribution lists What PST import refuses Outlook itself, over IMAP Whatever Outlook uploads Anything Outlook does not put on the wire One person, from the webmail: Other accounts Since 6.3.4. Settings, Other accounts lets a person do the IMAP route for their own mailbox without an administrator. They pick the old provider (Gmail, Outlook.com, Yahoo Mail and iCloud Mail fill in the server, port and encryption), give the user name and password, choose whether to leave the mail there, and tick Bring the folders over too. It is an external account made through /api/v1/me/fetch-accounts, with these differences from one an administrator makes: The spam and virus filters are on for new mail; folders brought over arrive as copies, without the filters. The host must resolve to public addresses only, on port 110 or 995 (POP3) or 143 or 993 (IMAP), unless the administrator turns on reader_fetch_accounts_private_addresses. Schema 6104 marks such an account reader_created, and the host is judged again at every collection: a name that answers a private address later is not collected, and the error log says why (HM5909). The interval is at least 15 minutes, and a person has at most reader_fetch_accounts_max of them: 3 by default, and 0 takes the page away. A host on FetchOAuth2Hosts (outlook.office365.com by default) is refused from the page; only a server administrator adds one. Signing in rather than giving a password. Outlook.com no longer accepts a password from another mail server, and Gmail takes only an app password. Where an administrator has registered the server with Google and Microsoft, the page offers Sign in to Gmail and Sign in to Outlook.com: the person grants IMAP access there and the account logs in with XOAUTH2. No password is stored, and the permission can be taken back at the provider. The first collection then lists the folders to tick, with Gmail's All Mail, Starred and Important unticked so that nothing arrives twice. The registration is set on PUT /api/v1/settings: import_oauth_redirect_base_url (the redirect URI to register is that plus /portal/oauth/callback), import_oauth_google_client_id and import_oauth_google_client_secret, import_oauth_microsoft_client_id and import_oauth_microsoft_client_secret. The Google client needs the Gmail API and the https://mail.google.com/ scope; the Microsoft Entra registration needs the delegated IMAP.AccessAsUser.All and offline_access permissions. Until Google verifies the app, only the test users named on its consent screen can sign in. A few mailboxes that must stay in step: the mirror With Mirror every folder on, the server's own IMAP client does what imapsync does. The switch is on the account dialog's External tab in the Control Panel (Windows) and on the Deck's fetch-accounts view; over COM (Windows) it is FetchAccount.ServerType = 1 and FetchAccount.MirrorFolders = true. Create the local account. Add an external account on it: server type IMAP, the remote host, port and security (implicit TLS on 993, STARTTLS on 143), the remote credentials, and the mirror on. Decide what happens on the far side. Days to keep messages at 0 deletes each message from the remote folder once it is filed here; any other value leaves it there, so the two servers stay in step for as long as the account polls. A migration usually leaves the messages in place until the cut-over, then one last poll, and the account is disabled. Poll. Download now starts a collection at once. A large mailbox is collected over several polls, and a message is recorded as collected only after it has been filed. A mirrored message is stored byte for byte: no X-hMailServer-ExternalAccount header, no delivery, no rules, no anti-spam or anti-virus, because it is a copy of mail the other server had already accepted. The remote flags and internal date become the local ones, and Archive/2025 becomes Archive.2025. Use a new external account for the mirror. One that has been collecting the INBOX without the mirror collects it once more when the mirror is switched on. A \Noselect mailbox is skipped with a line in the application log, OnExternalAccountDownload does not fire for a mirrored message, and subscriptions, ACLs, quotas, Sieve and vacation settings on the far side are not read. Many mailboxes: migration jobs Since 6.3.4, schema 6090. A migration is a batch the server runs itself: a job per mailbox, throttled, resumed after a failure or a restart, with a report per mailbox and the contacts and calendars beside the mail. It is administered under /api/v1/migrations and in the Control Deck's Migrations view; there is no COM interface, and in 6.3.4 no Control Panel page (in 6.3.7's Control Panel the migrations are a card on Backup, restore & migration). A migration names its source: kind imap (a password per mailbox), google or microsoft365 (each mailbox signed in to by its owner), or pst; concurrency (1 to 32, 4 by default); and messages_per_minute (0 for no limit). A Google or Microsoft 365 migration reads imap.gmail.com or outlook.office365.com on port 993 over TLS, and any other host is refused. A job is queued, running, paused, done, failed or cancelled, through the phases mail, contacts, calendars and report. POST /api/v1/migrations {"name": "Old server", "host": "imap.old.example"} POST /api/v1/migrations/{id}/jobs the CSV below, or one job as JSON POST /api/v1/migrations/{id}/start GET /api/v1/migrations/{id}/jobs/{job}/report GET /api/v1/migrations/{id}/summary.csv sourceuser,secret,target [email protected],Anna's old password,[email protected] [email protected],,[email protected] One request takes up to 5,000 lines. Every line is checked before any job is made, and a bad line refuses the whole request and names itself, so nothing is half added. A job into an account another migration is copying into now is refused (409). Google Workspace and Microsoft 365. A job added with an empty secret waits for its owner: the answer carries signin_url, a one-use link for that person to open, sign in as that mailbox and grant access. A sign-in as anybody else attaches nothing, and POST .../jobs/{job}/signin makes a new link. The scopes asked for are Google's https://mail.google.com/, https://www.googleapis.com/auth/carddav and https://www.googleapis.com/auth/calendar, and Microsoft's https://outlook.office.com/IMAP.AccessAsUser.All and offline_access with Microsoft Graph Contacts.Read and Calendars.Read. A work or school tenant may need an administrator's consent. What a job does. It maps the INBOX to the INBOX, a special-use folder (\Sent, \Drafts, \Junk, \Trash, \Archive) to the account's own folder of that use whatever it is called, and everything else to the same path. It opens each folder with EXAMINE, so nothing on the source is marked read, and stores each message byte for byte with its flags, keywords and internal date. A message too large is skipped as too_large; an account over its quota pauses the job. No rule, filter or scan runs over a copied message. Contacts and calendars follow: from an IMAP server by CardDAV and CalDAV found by RFC 6764 discovery (or dav_url), from Google by its CardDAV and CalDAV, from Microsoft 365 by Microsoft Graph. A phase that cannot run is skipped with its reason and never fails a job whose mail is copied. A job is one copy, not a synchronisation: move the MX records first and migrate after, or keep a mirror for the mailboxes that must stay in step. It survives being interrupted. After every 50 messages and at the end of each folder the job writes its resume marks, and a job that has run before reads back what it wrote, so each message arrives once. A failure time may mend is retried after 1, 5 and 15 minutes and then hourly; after 24 attempts in a row without a message copied the job is failed. Wrong credentials fail it at once, and POST .../jobs/{job}/retry with {"secret": "..."} takes a mended password. Throttling is threefold: concurrency per migration; MigrationConnectionsPerHost (4 by default, 1 to 64) across every migration from one source host, because a provider counts sessions per client address; and at most 64 jobs on the server at once. Who may. Reading needs view_accounts and writing manage_accounts in the target's domain. The server administrator creates a migration, and a domain administrator then sees and adds to it. The audit trail records migrations and jobs, never a secret's value. Jobs are not carried by a backup. HM7240 (a resume mark could not be written) and HM7241 (a message could not be filed) are the two codes that reach the error log for a job from an IMAP server, Google or Microsoft 365. A PST job says the same two as HM7370 and HM7371, and its contacts and calendars phases add HM7410 and HM7411. The report says, folder by folder, how many messages the source held, how many were copied and why any were skipped: duplicate, another_folder, too_large, refused, gone, empty. mbox files, a Maildir and accounts from a text file: the Import Tool The Import Tool is a Windows program (Start menu > hMailServer > Import Tool, or Addons\ImportTool\ImportTool.exe) that works through COM, each message going to the server through Utilities.ImportMessageFromFileToIMAPFolder; there is no Import Tool on Linux. It imports a folder of mbox files into one account, one IMAP folder per file; Inbox in any case is the INBOX, and a subfolder's files are a second run. Each message is dated from its Received or Date header. mbox carries no flags the tool could read, so every imported message is unread. Select Maildir points at the directory holding cur, new and tmp, and lists the INBOX and every Maildir++ folder beside it. The flags after :2, in a file name are set on the copy: S seen, F flagged, R answered, D draft, T deleted. A colon is not allowed in a Windows file name, so the tool also reads ;2, and the same marker written with an exclamation mark in place of the colon; a copy whose names lost the info part imports unread. Accounts from a text file. The tool's other button creates or updates the accounts of one domain from a comma-separated text file, three fields a line: the account's name without the domain, its password and its size limit in MB (0 for none). Where the breach check is on, it runs for each password the tool sets through COM's Account.Password, one request per account: run a large import against your own mirror of the endpoint, or with the check off. Outlook data files: PST import Since 6.3.4. The server reads a PST itself, with a reader of this project's own written from Microsoft's published specifications and no third-party PST code. It is a migration of kind pst: one job per file, each into one account. Off until a directory is named. A job names a file, never a path, and the file is looked for only in the subdirectory named for the target account's domain inside the directory pst_import_directory names (PstImportDirectory in hm_settings, set with PUT /api/v1/settings or on the Deck's Settings page). It is empty as shipped, and while it is empty every PST job is refused with No PST import directory is set on this server. D:\PstImport\ <- pst_import_directory D:\PstImport\example.com\archive.pst <- a job for [email protected] names archive.pst Copy the file into the domain's subdirectory, where the service's account can read it. A share on another machine is refused, and so is a name that climbs out of the domain's directory. Make the migration, POST /api/v1/migrations with {"name": "Archives", "kind": "pst"}, then jobs as CSV lines of file,password,target over REST or in the Deck, or one job as JSON with hmctl api POST /api/v1/migrations/{id}/jobs. There is no Import Tool button for it and no COM route. Start it. It needs manage_accounts and manage_mailbox in the target's domain. What comes in. The folders a person sees in Outlook, with Sent Items, Deleted Items, Drafts and Junk E-mail found by what the store says they are, never by their names. Every message is rebuilt as Internet mail, deterministically, so a resumed job knows what it filed. Read, unsent, flagged and replied state become \Seen, \Draft, \Flagged and \Answered, forwarded the keyword $Forwarded, categories keywords. Contacts go into the address book, calendar items and tasks into the calendars, notes and journal entries into mail folders as messages. ANSI files (Outlook 97 to 2002) are read. What it refuses, with a sentence saying what to do: the 4K-page layout Outlook 2013 and later write for an OST, an OST caching an Exchange mailbox, a file under Windows Information Protection, and a missing or wrong password. A file Outlook still has open pauses the job. A message over 128 MB of text and data together is left out and named. Migration.md is candid about its evidence: no password-protected PST and no OST from Outlook were at hand. The reader runs in a process of its own (hMailServer.exe --pst-read on Windows, hmailserver --pst-read on Linux, not a command for an administrator), killed on a pause, a cancel or a stop, or when it has not answered in ten minutes. A file that crashes it costs one message, not the service. This is crash containment, not a sandbox: the reader runs as the service's own account, bounded in memory (2 GB committed on Windows, 4 GB of address space on Linux). The fixture API.PstImport runs on Windows; nothing runs it on Linux yet. What is not migrated Rules and filters, signatures, out-of-office replies and forwarding: make them again here. Shared mailboxes and permissions; a Microsoft 365 shared mailbox, which nobody signs in as, cannot be a job. Subscriptions: every folder is created subscribed. Gmail's labels as labels. A google migration copies each message once, and the star arrives as \Flagged. Contact groups; from Microsoft 365, the default contacts folder and calendar only. A mailbox already here: renaming it, or moving it to another domain Do not copy it into a second account: that loses everything hanging off the account id, which is its rules, folders, application passwords, second factor, contacts, calendars and role. Move or rename (since 6.3.4) keeps the account's row, and leaves the old address as an alias unless you say otherwise. Relocating an installation, and moving or renaming a mailbox has where it is done, what moves and what is refused. Where to read more The wiki page: Migration The document, with every field, limit and refusal: Migration.md Renaming and moving a mailbox: MailboxMove.md The documentation page: Importing accounts and mail, the Import Tool On this forum: The Control Deck, where the Migrations view is, and The webmail portal at /portal, where Other accounts is.
  • 0 Votes
    1 Posts
    157 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) adds no schema step (6108, as 6.3.6). Its x64 installer's MySQL and MariaDB client is MariaDB Connector/C 3.4.11, built from MariaDB's source, in place of the 3.4.9 that 6.2.3 to 6.3.6 carried (CVE-2026-107819), with seven authentication plugins where 6.3.6's had nine: a database account that signs in with MySQL Enterprise's Windows authentication can no longer sign in, and the server then cannot open its database; the ARM64 installer carries no MySQL or MariaDB client. On MySQL, MariaDB, SQL Server and SQL Server Compact an insert that forgives a duplicate key now forgives that alone and any other failure is reported (HM5032), and on MySQL and MariaDB a lost connection is reconnected and the statement run again, so an error log that was quiet may now name failures that were hidden. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026), a security release, adds no schema step either. Through MariaDB Connector/C, the client the Windows installer bundles and the Linux server loads, TlsMode=verify-full without TlsCaFile to a MySQL or MariaDB server on this machine (by a loopback address, localhost, a Unix socket or a named pipe, or through a Server= list that holds one) is refused with HM7535, since in 6.3.4 to 6.3.7, with Connector/C 3.4.1 or later, neither the certificate's issuer nor its name was checked there: before upgrading, name in TlsCaFile a CA that issues nothing but the database's certificates, the one that issued its certificate, or set TlsMode=require. A TlsCaFile whose name holds a character outside ASCII, which in 6.3.4 to 6.3.7 could reach the client as no CA file at all, is now handed to it as named; on Windows a TlsCaFile or PostgreSQLSslRootCert name the client cannot be handed as named is refused with HM7534, so give the file, and the folders above it, an ASCII name; and on Linux, where the distribution's client is older than Connector/C 3.4.11 and not patched, name the CA in TlsCaFile. See the 6.3.8 announcement. This topic covers the four databases hMailServer 6.3.4 can keep its data in, what each needs before the schema can be made, how the connection to the database server is encrypted since 6.3.4, and how to move an installation off the built-in engine. The one thing to know first: on MySQL and MariaDB, 6.3.4 needs MySQL 5.7.9 or MariaDB 10.2.2 or later, because since schema 6102 every table is utf8mb4. On an older server the create script stops at its first table, and an upgrade from 6.3.3 stops at its first step, 6040 to 6041, with error 1071 and nothing changed. What the database holds, and what it does not hMailServer stores accounts, domains, message metadata and settings in the database. The message bodies are files on disk, one per message, at <DataFolder>\<domain>\<local part>\<xx>\{guid}.eml, so the database stays small relative to Data\. Since 6.3.4 the database is also the settings store: the [Settings] keys of hMailServer.ini are rows from schema 6042, the file's section is the server's copy, and a line edited there for a key the database holds is named in the error log as HM5804 and does not take effect. Where the service may write the file the line is put back; where it may not (a new Windows installation under NT SERVICE\hMailServer, a Linux package installation) the copy is not refreshed, the edited line stays, and the database's value is used. The [Database] section itself stays in the file, because it says how the store is reached. The four choices Database [Database] Type What is bundled What you must have first Built-in (SQL Server Compact 4.0 SP1) MSSQLCE SSCERuntime_x64-ENU.msi, installed by setup Nothing. This is the default MySQL / MariaDB MYSQL MariaDB Connector/C 3.4.9 as Bin\libmysql.dll (3.4.11 since 6.3.7, with seven authentication plugins where 6.3.6's had nine) A reachable server, MySQL 5.7.9 or MariaDB 10.2.2 or later, an empty database, and a user that may create tables Microsoft SQL Server MSSQL Nothing; the OS's own data stack The same, including Express edition PostgreSQL PostgreSQL libpq from PostgreSQL 18.3 The same On Linux there is no built-in database: a Linux server keeps its data in PostgreSQL or MySQL/MariaDB, and hmailserver --create-database refuses SQL Server and SQL Server Compact by name. For MySQL or MariaDB install the client library yourself, because the server opens libmariadb.so.3 at run time: libmariadb3 on Debian and Ubuntu, mariadb-connector-c on Fedora and RHEL. Databases on Linux has the steps for each backend, and First install on Linux the order of the whole first install. Two things about the built-in engine. It runs on a single database connection: NumberOfConnections is forced to 1 for MSSQLCE. And Microsoft has withdrawn the SQL Server Compact download; the copy in installation/SQLCE/ is the archive of record, and it still installs. MigratingDatabaseBackend.md calls it "the one real dependency liability in this tree". For anything up to a few dozen mailboxes it is adequate, and the last section of this topic is the way off it. How the four behave differently MS SQL Server PostgreSQL MySQL / MariaDB SQL Server Compact DDL in a transaction Mostly Yes, the only one of the four No, commits as it goes No, commits as it goes A failed schema step leaves Normally nothing applied Nothing applied A partly built database A partly built database Isolation the server uses READ UNCOMMITTED its default its default its default Transactions used at all yes yes only if every table reports InnoDB at connect time asked for, but a no-op: SQLCEConnection::BeginTransaction returns true without starting one (Roadmap.md) Choose it when You already run SQL Server You want a failed schema step to roll back cleanly You already run it As a new installation's default, up to a few dozen mailboxes; as the target of a migration, only for a test rig MySQL and MariaDB since 6.3.4 MySQL 5.7.9 or MariaDB 10.2.2, or later. Since schema 6102 every table is utf8mb4 under utf8mb4_general_ci, InnoDB, with ROW_FORMAT=DYNAMIC, and a key over a varchar(255) is then 1020 bytes: more than the 767 bytes an InnoDB key part may be in the older COMPACT rows, and within the 3072 bytes these releases, as they ship, take. Why. MySQL's utf8 is three bytes a character, so an emoji in a contact, a folder or a signature was refused with error 1366 under strict mode and made a question mark without it. utf8mb4_general_ci is the twin of utf8_general_ci, so an address or a folder compares as it did, but it does not tell characters outside the Basic Multilingual Plane apart: two address books called Family 👪 and Family 🎉 are one name to its unique index. Creating the database yourself: CREATE DATABASE hmailserver CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; GRANT ALL PRIVILEGES ON hmailserver.* TO 'hmailserver'@'%'; The 6102 step also changes the database's own default character set, which needs the ALTER privilege on the database, not only on its tables. Every table must end up InnoDB: the server issues transactions only if all of them report it at connect time, and silently does not otherwise. A database the server creates itself is made utf8mb4 already. The 6.3.4 upgrade on these two rebuilds every table, and on a server older than 5.7.9 or 10.2.2 it stops at 6041 with error 1071, having changed nothing. Databases on Linux covers that step in full, its time, disk and duplicates (error 1062) included, and Upgrading to 6.3.4 the rest of the upgrade. Creating the database on Windows The wizard asks only "built-in or external". For the built-in engine, DBSetupQuick.exe creates the database at the current schema version, 6104. For an external engine, DBSetup.exe runs after the file copy, asks for the connection, and either creates the database or attaches one that exists; the secure defaults of a new 6.3.4 installation are applied only to one it created. Since 6.3.4 the administrator password reaches both tools in the environment variable HM_ADMIN_PASSWORD, never on a command line. The tools write [Database] in Bin\hMailServer.INI: Key Meaning Type MSSQL, MYSQL, PostgreSQL or MSSQLCE, matched case-insensitively Server, Database, Username, Password The connection. Server is empty for the internal engine PasswordEncryption Written by the server; says how Password is protected Port 0 uses the backend's default Internal 1 only for the embedded engine NumberOfConnections Pool size; default 5 The password in this file may be machine-bound. With ProtectStoredSecretsWithDPAPI=1, the default, the server writes Password through machine-scoped DPAPI (PasswordEncryption=6), and it cannot be decrypted on another machine. If you are also moving hosts, set the password on the new machine through DBSetup.exe rather than copying the encrypted one across. Under NT SERVICE\hMailServer, the account a new 6.3.4 installation runs as, hMailServer.INI is read-only to the service, and the COM methods the two tools call to write [Database] are refused before anything is made. The installer therefore runs its database step while the service is still LocalSystem. To change the database later, move the service to LocalSystem for the change and back (Installing on Windows has the commands), or edit [Database] by hand and restart. Encrypting the connection (since 6.3.4) The test lab measured on 25 September 2026 that the connection to MySQL and MariaDB through MariaDB Connector/C, the client bundled on Windows and the one Ubuntu packages, was never encrypted: every account was reached in plaintext, password exchange, queries and rows alike, and one made with REQUIRE SSL refused the server. Since 6.3.4 two keys under [Database] choose the behaviour: [Database] TlsMode=verify-full TlsCaFile=C:\hMailServer\certs\database-ca.pem TlsMode What the connection does (empty, the default) prefer: TLS whenever the server offers it, and the connection without it when the server offers none, or when the TLS it offers cannot be agreed on (one attempt with TLS and one without, for that failure only). The certificate is not checked prefer As the default, said explicitly disable No TLS. SQL Server's providers still encrypt the login itself require TLS or no connection; the certificate is not checked verify-full TLS or no connection, the certificate checked: its chain to a CA in TlsCaFile (or the system's trust store when that is empty; since 6.3.8 an empty one is refused, HM7535, on MySQL and MariaDB through MariaDB Connector/C to a database server on this machine), and its name or address against Server= TlsCaFile is a PEM file of CA certificates, read by verify-full and by nothing else, so that a CA file left beside require cannot quietly make it stronger or weaker than it says. A TlsCaFile that verify-full would read and that is not there refuses the connection (HM7531) rather than falling back to the system's store. Any other word in TlsMode is refused without an attempt (HM7530); verify-ca is refused by name, and PostgreSQL's own PostgreSQLSslMode=verify-ca remains for PostgreSQL. The keys are read at the service's start. Backend Before 6.3.4 Since 6.3.4, with nothing set MySQL / MariaDB, Windows and Linux Plaintext, always TLS whenever offered, unchecked; plaintext when not PostgreSQL, both platforms libpq's default, prefer Unchanged: libpq's prefer, or a PGSSLMODE in the service's environment, stands. TlsMode and TlsCaFile become sslmode and sslrootcert unless PostgreSQLSslMode and PostgreSQLSslRootCert are set, which win. verify-full with no TlsCaFile is sslrootcert=system on Linux (libpq 16 and later), and libpq's own default file on Windows, %APPDATA%\postgresql\root.crt of the service's account SQL Server, Windows The login encrypted and nothing after it, unless the server forced encryption The whole session encrypted through MSOLEDBSQL, when version 18 or later is installed, and through SQLNCLI for a failover partner; through SQLOLEDB only the login, unless TlsMode is require or verify-full SQL Server, Linux Refused: ADO is Windows (HM6390) Unchanged SQL Server Compact A file on this machine Unchanged On MySQL and MariaDB the old AllowUnencryptedConnection is still read and changes nothing: what it described, TLS preferred and plaintext allowed, is the default now. On SQL Server verify-full checks against the Windows certificate store; TlsCaFile is not read (HM7532), and ConnectionStringOptions naming encryption beside a TlsMode is refused (HM7533). Seeing it. The application log says how the connection is protected, once the service has started and again whenever it changes, for example Database connection: PostgreSQL: NOT encrypted: the server offered no TLS (TlsMode prefer, the default); the diagnostics run on the Diagnostics pages carries the same line under Server details. At the service's start a connection refused for TLS is the connect failure it always was, HM4354, with the database client's own reason and a sentence naming the setting to change. --check-database makes one connection with the TLS settings in the configuration, says what it got, and exits 0 connected, 1 not, 2 not configured. It changes nothing, so it runs beside a running service: hmailserver --config /etc/hmailserver/hMailServer.ini --check-database hMailServer.exe --check-database --config C:\hMailServer\try.ini What the server does not do for you. It cannot switch TLS on at the database server or give it a certificate. Under verify-full a MariaDB 11.4 server's own self-signed certificate is accepted without a CA file when the account's password proves it (MariaDB's zero-configuration TLS, for mysql_native_password, ed25519 and parsec accounts), and not once a CA file is named. A database that must refuse a plaintext connection should say so itself: REQUIRE SSL on the MySQL account, hostssl only in pg_hba.conf, Force Encryption on SQL Server. What is thinner than it could be. There is no editor for these keys in the Control Panel or the Deck. require on the MariaDB client is checked after the login, and an unencrypted connection is closed before a statement crosses it: by then a challenge and a response have crossed in the clear for mysql_native_password, caching_sha2_password and ed25519 accounts, and the password itself for an account that signs in through PAM or the clear-text plugin. verify-full refuses before the login. require protects against someone who can listen and not against someone who can alter the traffic, and prefer connects without TLS to a server that seems to offer none; only verify-full protects against someone on the path. And only a login with VIEW SERVER STATE lets SQL Server say whether its session is encrypted. Moving off the built-in database There is no migration tool. Upgrading.md records that as a gap; MigratingDatabaseBackend.md says it is not an omission, because backup and restore are already backend-agnostic: back up, repoint, restore. It works because the archive's XML carries no identity values, and the message store is addressed by domain name, local part and the file's own guid, never by id. Your mail files never move. Before you start. Write down how many domains, accounts, aliases and distribution lists there are, to verify against afterwards. Take a filesystem-level backup of the whole installation. Backup refuses to run if any message row points outside DataFolder ("All messages are not located in the data folder"). The restore refuses an archive taken by a newer hMailServer, so migrate on one version, then upgrade. Keep a copy of hMailServer.ini; the administrator password lives in it, so it carries over. And any mail delivered between the backup and the cutover is lost to the new database: hold inbound traffic, or accept the gap knowingly. Stop the service (Stop-Service hMailServer). Restart it for the backup itself, which runs through the running server, but stop accepting new mail first if you can. Take the backup on Maintenance → Backup & restore with all three of Back up domains, Back up messages and Back up settings. The archive lands as HMBackup <local time>.7z. All three are required: the restore refuses a combination that would delete something and put nothing back, and says so before it changes anything. Set BackupMessagesDBOnly to 1 before both the backup and the restore. Since 6.3.4 it is a stored setting that applies when it is saved, with no restart: set it in the Control Panel, over REST (PUT /api/v1/settings/ini/BackupMessagesDBOnly with 1) or with hmctl ini set BackupMessagesDBOnly 1. On 6.3.3 it was a [Settings] line in the file, read at start, so restart there. Without it, a normal restore empties the live data directory and copies the staged message store into it; with it that step is skipped and your .eml files are never touched. Remove it afterwards. Stop the service again (Stop-Service hMailServer) and wait for it to stop. Create the new database: empty, with a login that may create tables; on MySQL or MariaDB, utf8mb4. Edit [Database] to point at it, with Internal=0. If the service runs as NT SERVICE\hMailServer, move it to LocalSystem first (hMailServer.exe --service-account localsystem /Register) and back once the migration is verified: under the virtual account the COM methods DBSetupQuick.exe calls are refused before anything is made. Then: & "C:\Program Files\hMailServer\Bin\DBSetupQuick.exe" if ($LASTEXITCODE -ne 0) { throw "Database setup failed with exit code $LASTEXITCODE" } On MySQL and SQL Server Compact a creation that fails halfway leaves a partly built database: drop it and start this step again. Restore. Start the service (Start-Service hMailServer). At its first start against the new database the server adopts the file's [Settings] section into the empty store. Before you restore, check BackupMessagesDBOnly on the new database (hmctl ini get BackupMessagesDBOnly, or the Control Panel). The setting has to be identical for the backup and the restore, and the restore runs against the new database, which has a settings store of its own. If it is not 1 there, set it again, which applies when it is saved. That is the case when the old server could not write its line into the file, which it reported as HM5806 or HM5803, and under NT SERVICE\hMailServer, which does not write the file's copy. A restore without it is a normal one, which empties the data directory. Then: $app = New-Object -ComObject hMailServer.Application $app.Authenticate("Administrator", "<password>") $backup = $app.BackupManager.LoadBackup("D:\hmail-migration\HMBackup 2026-08-13 220511.7z") $backup.RestoreDomains = $true $backup.RestoreMessages = $true $backup.RestoreSettings = $true $backup.StartRestore() The server restarts itself at the end; that is expected. Verify all of these, because a partial restore looks like a working server: the domain, account, alias and list counts, against what you wrote down before you started; the folder list over IMAP as a real account; one whole message body; certificates, ports, IP ranges, routes and rules; one message in and one out; and an empty ERROR_hmailserver_<date>.log. Rolling back is exact until you delete the old database, which was only ever read: stop the service, put the original [Database] section back, start the service. Since 6.3.4 the settings come back with it, and a setting changed since the cutover is named as HM5804 on that start, which is the rollback working, not a fault. Ids change, so a script that recorded hm_accounts.accountid will break; key on the address instead. What it does not cover, and where to read more Moving the data folder to different storage is a separate operation, Relocating an installation to another drive; the path is [Directories] DataFolder. Moving to a new machine is Moving to new hardware, and why Windows to Linux is a rebuild. What the backup archive holds, and what it does not, is in Backup and restore. Sources: the wiki's Before You Install, Migrating the Database Backend and Installing on Linux; MigratingDatabaseBackend.md, DatabaseTls.md and Upgrading.md; and sections 3.5 and 33.3 of the public documentation.
  • 0 Votes
    1 Posts
    134 Views
    ProgressiverobotP
    Moving to new hardware is a restore onto fresh metal. Chapter 15 is the prerequisite, and the first time, rehearse onto a test machine. The plan (§33.2) Check the new machine against the chapter 3 checklist. Size the disk for the mail store you have now, not the one you had at installation. Install the same version, in the same path: a different version or directory is where these moves go wrong. Since hMailServer 5.4 a message row holds a bare file name and the path is rebuilt from the data directory; a database from before 5.4 can still hold full paths. Back up the old server. The built-in backup does not include messages unless you tick the option. For a large store, stop the service, copy Data\, dump the database natively, and take hMailServer.INI, Events\, your DKIM keys and, if one was made, an export of the stored-secrets key (--export-stored-secrets-key). Stop the old service for good and set it to Disabled. Anything it accepts after the backup is stranded. Restore the data directory, the database and hMailServer.INI, in chapter 15's order. Re-enter the protected secrets. Keep the host name if you can. Update the A record with a lowered TTL, get a PTR on the new IP, re-run the firewall rules, repoint your port forwards, and run Monitoring & troubleshooting, Diagnostics last. Leave the old machine powered off but intact for a week or two. It is the rollback. Budget for step 6. With ProtectStoredSecretsWithDPAPI=1, the default, stored passwords are encrypted with machine-scoped Windows DPAPI and cannot be decrypted on the new machine. Re-enter the database connection password with Bin\DBSetup.exe, then route, smart-host relayer and external-account passwords in the Control Panel. Forget this and you get a server that cannot reach its own database. Since 6.3.4 the stored-secrets key that seals second factors and the [Settings] secrets is kept in hMailServer.INI under the same DPAPI, so a copied INI does not carry it: import the exported file on the new machine with hMailServer.exe --import-stored-secrets-key <file> and restart the service. An upgraded server has no such key until an administrator makes one. Linux to Linux is easier: secrets sit under <DataFolder>/.hmailserver-secret-key, so copy that file too, preserving mode 0600 and its ownership. Windows to Linux is not a migration It is not supported and the project does not claim it is. The schema is the same on both, 6.3.4 needs 6104 either way, and the message store's on-disk format is the same. What is missing is a tested path: nobody has run a Windows installation onto Linux and verified the result (§35.14). What is known to break: SQL Server. An INI with Type=MSSQL or Type=MSSQLCE is refused by name. Move to PostgreSQL or MySQL on Windows first. Every DPAPI secret. Route, fetch-account and per-domain relay passwords, private-key passphrases and the administrator's TOTP secret. Each is reported once as HM6414 and answers empty. Event scripts and COM integrations. COM does not exist on Linux, and since 6.3.4 event scripts run there in JavaScript only: a VBScript file is rewritten and a JScript one ported, without COM objects. DKIM key paths. The per-domain key-file setting holds a Windows path such as C:\keys\mail.pem, which resolves to nothing on Linux. Copy the keys and re-point every signing domain. Directory case. The Linux build lower-cases directory names derived from an address; Windows leaves them as found. A store carrying Test.com is one this server looks for under test.com, and everything under it must end up owned by hmailserver. So treat it as a rebuild and a cutover. A new installation on Linux, accounts and mail brought over at mailbox level by mirroring folders over IMAP from the running Windows server. The Import Tool is not an option here: it is a Windows .NET program that drives the COM API, and Linux has neither (§33.8), verified against a still-running Windows server, DNS moved last.