Skip to content

Migration

Christopher Holloway edited this page Oct 22, 2026 · 10 revisions

Migration

Written on 24 September 2026 and brought up to 6.3.4 (27 September 2026, schema 6104), on 26 September 2026, from hmailserver/docs/Migration.md. The routes from another IMAP server (a mirroring external account), from mbox files, from a Maildir, from Outlook over IMAP and from the upstream hMailServer are in 6.3.3 and later; the webmail's own Other accounts with Gmail and Outlook.com sign-in, the migration jobs (schema 6090) and PST import are new in 6.3.4. Brought up on 5 October 2026 to 6.3.6 (6 October 2026, schema 6108), for what 6.3.6 changes on this page. 6.3.7, the latest release (17 October 2026, schema 6108), is not yet reflected here.

How to bring existing mailboxes into this server. Moving an installation of hMailServer is elsewhere: Upgrading hMailServer for a release-to-release upgrade and the move from the original project up to 5.6 (its 5.7 releases are not upgraded in place: the table below has them), Migrating the Database Backend for a change of database, and Relocating an Installation for another folder or drive, and Backup and Restore 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 (6.3.4) Folders brought over as copies, then new mail collected Rules, signatures
A few mailboxes that must stay in step until the cut-over An external account with the mirror on - the server collects every folder over IMAP until it is switched off Folders, every message byte for byte, the five standard flags, the internal date Subscriptions, custom keywords, ACLs
Many mailboxes - hundreds - from an IMAP server, Google Workspace or Microsoft 365, with contacts, calendars and a report per mailbox A migration: a job per mailbox (6.3.4) Folders into their special-use counterparts, every message byte for byte, flags and keywords, the date; contacts and calendars Rules, signatures, shared mailboxes, ACLs, Gmail's labels as labels
mbox files (Thunderbird's profile, Apple Mail, a Unix spool) The Import Tool, one folder per file Every message, the date from the headers Flags - mbox has no standard for them
A Maildir (Dovecot, Courier) The Import Tool's Select Maildir The Maildir++ hierarchy, every message, the flags in the file names Custom keywords
Outlook PST files PST import (6.3.4): a migration of kind pst, a job per file into one account, the file copied into the server's PST import directory first Folders and their special-use counterparts, messages rebuilt as Internet mail with flags, categories and dates; contacts, calendar items and tasks into the account's address books and calendars; notes and journal entries as messages Attachments by reference and OLE objects, distribution lists, the 4K-page layout of newer OSTs, a cached Exchange OST, a file protected by Windows Information Protection
What PST import refuses or leaves out Outlook itself, over IMAP: add this server as an IMAP account and drag the folders across Whatever Outlook uploads: folders, messages, read and flagged state Anything Outlook does not put on the wire
A server of the original hMailServer's 5.7 (5.7.0, 5.7.1 and 5.7.2) A migration of kind imap from it, with this server installed on another machine and the domains and accounts made here first: setup cannot upgrade a 5.7 database, and from the release after 6.3.7 refuses such an installation before it changes anything; the installers of 6.3.7 and earlier do not look, and must not be run over one (Upgrading hMailServer 18.2) What a migration from another IMAP server keeps The settings, rules and the rest of its configuration, made again here
The accounts themselves The Import Tool's text-file import, or a spreadsheet over REST and hmctl (6.3.4), or SCIM from the identity provider (6.3.4) - first, since every route above needs the account to exist Addresses, passwords, quotas Mail

The external account and its mirror are on the account dialog's External tab in the Control Panel, and on the Deck's fetch-accounts view; Mirror every folder (MirrorFolders) is what turns a collection of the INBOX into a copy of every folder. A new external account for the mirror is the safe choice: one that has been collecting the INBOX alone collects it once more when the mirror is switched on.

The person moving their own mailbox

Since 6.3.4. Settings, Other accounts in the webmail. The person picks their old provider - Gmail, Outlook.com, Yahoo Mail and iCloud Mail fill in the server, port and encryption and say what each asks for - gives the user name and password, chooses whether to leave the mail there, and ticks Bring the folders over too. It is an external account like any other, with the filters on, the host held to public addresses unless the administrator turns on reader_fetch_accounts_private_addresses, an interval of at least fifteen minutes, and at most reader_fetch_accounts_max of them (3 by default).

Days to keep messages is per collected message, and means what it means for any external account, POP3 or IMAP: -1 deletes each message from the remote folder once it is safely filed here - a move; 0, the default, leaves every message there, so the two servers stay in step for as long as the account polls; a number of days N deletes a message N days after it was collected. A migration usually runs with the messages left in place until the cut-over, then one last poll and the account is disabled. (From 6.2.25 to 6.3.5 an IMAP account read 0 as "delete" and -1 as "keep", the other way round from a POP3 account; the upgrade to 6.3.6, schema 6106, translated each IMAP account's value so that it goes on doing what it did, and the Control Panel, the Deck, the webmail and the REST API now all describe the one meaning.)

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 this server with Google and Microsoft, the page offers Sign in to Gmail and Sign in to Outlook.com: the person signs in there and grants IMAP access, and the external 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 import_oauth_redirect_base_url (the redirect URI is that plus /portal/oauth/callback) and each provider's client id and secret, on PUT /api/v1/settings.

Five hundred mailboxes: migration jobs

Schema 6090, since 6.3.4. Moving an organisation needs something the server runs itself: a job per mailbox with a state, no more load on the source than it will bear, a job that goes on from where it was after a failure or a restart, a report per mailbox, and the contacts and calendars beside the mail. It is administered over REST under /api/v1/migrations and in the Control Deck's Migrations view; there is no COM interface and no Control Panel page.

  1. Create the accounts first - with a spreadsheet import, the Import Tool or SCIM.

  2. Create the migration, naming its source: imap (any IMAP server, a password per mailbox), google or microsoft365 (each mailbox signed in to by its owner) - or pst, below. A migration runs concurrency jobs at once (4 by default, up to 32) at messages_per_minute each.

  3. Add the jobs - a CSV of sourceuser,secret,target, up to 5,000 lines, every line checked before any job is made and the first bad line named:

    sourceuser,secret,target
    anna@old.example,Anna's old password,anna@example.com
    carla@example.com,,carla@example.com
    

    For Google and Microsoft 365 the secret is left empty: the answer carries a one-use sign-in link per job for its owner to open, sign in as that mailbox and grant access. A sign-in as anybody else attaches nothing.

  4. Start it, and watch each job's state, phase (mail, contacts, calendars, report), progress and last error in the Deck, or download summary.csv for the spreadsheet.

What a job does. It lists the source's folders and maps each to one here: 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, everything else by name. Every message is copied byte for byte with its flags, keywords and date. Then the contacts and calendars: 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.

It survives being interrupted. After every fifty messages and at the end of each folder the job writes its resume marks; whatever stops it - a network failure, a pause, the service restarting - it goes on from those marks, and it checks what it wrote since it started so that each message arrives once, however often it is interrupted. A failure time may mend is retried after 1, 5 and 15 minutes and then hourly; wrong credentials fail the job at once, and Retry takes a mended password.

It is throttled three ways: concurrency per migration, MigrationConnectionsPerHost (4 by default) across every migration from one source host - a provider counts sessions per client address

  • and at most 64 jobs on the server at once.

The report per job 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), then the contacts and calendars, the bytes, the duration and the errors met.

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; a job's own progress is not recorded. Jobs are not carried by a backup: a restore is not a moment in a migration.

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 ([MS-PST] and the documents beside it) and no third-party PST code. It is a migration of kind pst: one job per file, each into one account, with the same state, throttle, resume marks, report and Deck view as every other migration.

Off until a directory is named. A job names a file and never a path. The file is looked for only in the subdirectory named for the target account's domain, inside the directory the setting pst_import_directory names (PstImportDirectory in hm_settings, set with PUT /api/v1/settings or on the Control 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 anna@example.com names archive.pst
  1. Copy the file into the domain's subdirectory, where the service's account can read it. A share on another machine is refused; so is a name that climbs out of the domain's directory by any spelling, link or junction.
  2. Make the migration and its jobs - POST /api/v1/migrations with {"name": "Archives", "kind": "pst"}, then a CSV of file,password,target lines (the password only for a file that has one) - over REST, with hmctl, or in the Deck's Migrations view. There is no Import Tool button for it on Windows and no COM route.
  3. Start it. It needs manage_accounts and manage_mailbox in the target's domain. The rule that a job reads only its own domain's subdirectory holds for everyone, the server administrator included, because the directory is shared between domains.

What comes in. The folders a person sees in Outlook - the IPM subtree - each at its own path, with Sent Items, Deleted Items, Drafts and Junk E-mail found by what the store says they are rather than by their names and filed into the account's own folders of that use. Every message is rebuilt as Internet mail, deterministically, so a resumed job knows what it filed: headers, plain and HTML bodies (and HTML carried in RTF), attachments, embedded messages, S/MIME as it was. Read, flagged, replied, forwarded and draft state come across as IMAP flags, categories as keywords, and the delivery date as the internal date. Contacts go into the account's address book, calendar items and tasks into its calendars (recurrence included), notes and journal entries into mail folders as messages. ANSI files (Outlook 97 to 2002) are read, and an OST where it is a PST in all but its name.

What it refuses, with a sentence saying what to do. The 4K-page layout Outlook 2013 and later write for an OST, which Microsoft has not published; an OST that caches an Exchange mailbox, whose folder identifiers the published format does not map; a file protected with Windows Information Protection, which only Outlook on the protecting machine can open; a missing or wrong password (give it as the job's secret and retry). A file Outlook still has open pauses the job until it is closed. Attachments by reference and OLE objects, distribution lists, a contact with no SMTP address, the attachments of appointments and tasks, and a message over the reader's limits (128 MB of text and data together) are left out and named in the report; a damaged message is counted unreadable and the rest of its folder comes in.

The reader runs in a process of its own. Each phase of a job starts the server's own program as the reader (hMailServer.exe --pst-read on Windows, hmailserver --pst-read on Linux - not a command for an administrator), hands it the file it opened and checked and nothing else of the service's - no configuration, no database, no log - and kills it on a pause, a cancel, a restart or the service stopping, or when it has not answered a request in ten minutes. A file that crashes the reader costs the message it was on, not the service: a new reader is started and the job goes on. This is crash containment, not a sandbox: the reader runs as the service's own account with its privileges taken away, bounded in memory (2 GB committed on Windows, 4 GB of address space on Linux), at a lower priority and with no handle of the service's but the file's - and it can still reach whatever that account can reach on the machine. The fixture API.PstImport runs on Windows; nothing runs it on Linux yet.

The full account, with every limit, refusal and code page, is the Outlook data files (PST) section of Migration.md.

What is not migrated

  • Rules and filters (Gmail's filters, Outlook's inbox rules, Sieve), signatures, out-of-office replies and forwarding - make them again here.
  • Shared mailboxes and permissions: other users' and shared mailboxes are left with a note, and 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. Over IMAP a label is a folder; a google migration copies each message once, into the first folder it meets it in, and All Mail brings only what no label held. The star arrives as \Flagged.
  • Contact groups, and contacts with no address.
  • From Microsoft 365, the default contacts folder and the default calendar only.
  • From a PST, what the section above lists as left out or refused; for those, Outlook over IMAP is still the route - add this server as an IMAP account in Outlook and drag the folders across.

The full document, with every field, route and refusal, is Migration.md.

See also: The Webmail · The Control Deck · Sign-in and Identity · Connecting a Mail Client · Upgrading hMailServer

Home — full index

Start here

1. Install and run

2. Secure it

3. Operate it

4. Extend it

5. Contribute to it

Look it up — from any journey


About this wiki

Clone this wiki locally