---
title: "Bringing mailboxes in: IMAP mirror, Google, Microsoft 365, mbox, Maildir and PST"
url: https://www.hmailserver.co.uk/topic/28230/bringing-mailboxes-in-imap-mirror-google-microsoft-365-mbox-maildir-and-pst
author: "Progressiverobot"
published: 2026-09-27T19:12:13.731Z
updated: 2026-09-27T19:12:13.731Z
category: "Installation, migration & upgrades"
tags: ["migration","imap","webmail","control-deck","rest-api"]
site: hMailServer Community Forum
---

# Bringing mailboxes in: IMAP mirror, Google, Microsoft 365, mbox, Maildir and PST

*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](/topic/28277). 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](https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.8/hmailserver/docs/Migration.md)'s *From the original hMailServer* says. See [the 6.3.8 announcement](/topic/28278).*

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](/topic/28265), or [SCIM](/topic/28267), and only then bring the mail. Moving an *installation* is a different subject: [Upgrading to 6.3.4](/topic/28193) for an upgrade and [Moving to new hardware](/topic/28194) 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`.

1. Create the local account.
2. 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.
3. 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.
4. 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`.

```text
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
```

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

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](/topic/28241) 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*.

```text
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, and so is a name that climbs out of the domain's directory.
2. 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.
3. 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](https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/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](/topic/28244) has where it is done, what moves and what is refused.

### Where to read more

- The wiki page: [Migration](https://gitlab.com/hmailserver/hmailserver/-/wikis/Migration)
- The document, with every field, limit and refusal: [Migration.md](https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/Migration.md)
- Renaming and moving a mailbox: [MailboxMove.md](https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/MailboxMove.md)
- The documentation page: [Importing accounts and mail, the Import Tool](https://www.progressiverobot.com/hmailserver-documentation/#338-importing-accounts-and-mail-the-import-tool)
- On this forum: [The Control Deck](/topic/28192), where the Migrations view is, and [The webmail portal at /portal](/topic/28191), where Other accounts is.
