# Move every mailbox from a cPanel host

> Bring a whole cPanel account's email at once, each mailbox with a new password and all of its mail, its forwarders and its catch-all.

Source: https://www.coritan.com/docs/mail/mail-hosting/move-from-cpanel/

In the dashboard:

- /dashboard/mail/…/mailboxes: https://www.coritan.com/dashboard/mail

A *migration* moves a whole cPanel account's email to a Mail Hosting service in one go. We read the account's email accounts over cPanel's own API, make a mailbox here for each one with a new password, turn its forwarders into aliases, and copy every message from each old mailbox, folder by folder, with its read and flagged state and its original date.

The copying runs on our side, so it carries on after you close the page, however much mail there is. To bring mail into one mailbox at a time instead, see [Move existing mail into a mailbox](/docs/mail/mail-hosting/move-mail-in/).

## Before you begin

- A Mail Hosting service with room for the mailboxes: a migration makes one mailbox for each email account, and gives each a quota that holds what the old one holds.
- The domain of the email accounts on the service, and proved to be yours. See [Add a domain to Mail Hosting](/docs/mail/mail-hosting/add-a-domain/). A migration leaves out the accounts of a domain that is not ready, and says so.
- The cPanel account's username, and an API token for it. In cPanel, open Security, then Manage API Tokens, and create one. The cPanel password works too, unless the account uses two-factor authentication.
- A way to read each old mailbox. cPanel does not let anyone read a mailbox without its password, so either give each old mailbox's current password, or let us set a new password on the old mailboxes you leave blank.

> [!WARNING]
> When we set a new password on an old mailbox, phones and computers that still sign in to the old server stop receiving its mail there. Move them to the new mailbox, with its new password, once the migration has started.

## Move the account

1. In the dashboard, go to [**Email**](https://www.coritan.com/dashboard/mail), open the Mail Hosting service, then the **Mailboxes** tab.
2. Select **Move from cPanel…**.
3. In **cPanel host**, enter where you sign in to cPanel, such as `example.com`, or your host's server name. In **Username**, enter the cPanel username.
4. Under **Sign in with**, keep **API token** and paste the token in **API token**, or choose **Password** and enter the cPanel password.
5. Select **Find mailboxes**. We list every email account with how much it holds and what it becomes here: **New mailbox**, **Already here**, or **Cannot move** with the reason under it.
6. Check the mailboxes to move. For each new one, set **Quota (GB)**; we fill in a size that holds what the old mailbox holds.
7. For each old mailbox whose password you know, enter it in **Current password**: we read it without changing anything on the old server. Leave the others blank, and keep **Set a new password on old mailboxes left blank** checked so we can read them too.
8. Turn on **Also bring Trash** or **Also bring Spam** to copy those folders. **Turn forwarders into aliases** and **Make the catch-all** are on, and appear when there is something for them to do. The dialog lists the aliases we make and what does not move.
9. Select **Start the migration**. When we are about to set new passwords on the old server, we ask first: select **Set new passwords and start**.
10. Save the new passwords. Each new mailbox signs in with its address and the password shown, and we show them only this once. Use **Copy all** or **Download CSV**, then select **Done**.

We check everything before anything changes: the plan's room, the old server's IMAP, and each password you gave. When a check fails, the dialog says why and nothing is made.

## What moves

| On the cPanel account | Here |
| --- | --- |
| Each email account | A mailbox with the same address and a new password, or the mailbox already here with that address |
| Its mail, in every folder | The same folders. Inbox, Sent, Drafts and Archive go into the mailbox's own, and Trash and Spam too when you ask for them |
| A forwarder to mailboxes that move | An alias to those mailboxes |
| A domain's default address, when it is one of the mailboxes | The domain's catch-all |

What does not move is listed with the reason, so you can set it up by hand:

- A forwarder to an address outside the service, such as a Gmail address. Forwarding there needs that address to confirm it, so set it up for a mailbox in webmail, under Settings, Forwarding.
- A forwarder on an address that is also a mailbox, a forwarder to a program or a file, and autoresponders.
- An account whose sign-in is suspended on the old server: its mailbox is made here, without its mail.

## Follow the migration

The **Moving mail in** card above the mailboxes shows each migration: how many mailboxes are done and how many messages came across. Select **Show mailboxes** to see each one with its import's status, its counts and any note. **Pause** and **Resume** steer one mailbox's import.

An import never stops because there is a lot of mail. When our mail server takes in mail more slowly for a while, the import waits and goes on by itself, and its row says when, as `Goes on at` and a time.

Mail keeps reaching the old server until your domain's MX records point here. Once they do:

1. Select **Sync again**, then **Sync again** in the question. A second pass brings what arrived since, and copies nothing twice.
2. When the last pass is done, select **Finish…**, then **Finish migration**. We forget the old mailboxes' passwords, and you cannot sync it again.

We keep those passwords until you finish the migration, or for 30 days.

## Troubleshooting

`… refused that username and token`
: cPanel did not accept the sign-in. Check the username and the token. An account with two-factor authentication needs an API token rather than the password.

`… did not show a certificate valid for its name on port 2083`
: Enter the server's own host name instead of your domain. Your host's welcome email, or cPanel's Server Information page, gives it.

`We could not reach the old server's mail over IMAP (TLS, port 993): …`
: The old server does not offer IMAP over TLS on any of the names we tried. Ask your host for the IMAP server name, and enter it as the cPanel host.

`The old server refused some of the passwords given; nothing was changed`
: The answer lists each mailbox whose password did not work. Correct those passwords, or leave them blank and let us set new ones.

`The plan has room for … more mailboxes, and the migration makes …`
: Choose fewer mailboxes, or change the plan first.

## Related

- [Move existing mail into a mailbox](/docs/mail/mail-hosting/move-mail-in/)
- [Add a domain to Mail Hosting](/docs/mail/mail-hosting/add-a-domain/)
- [Create and manage mailboxes](/docs/mail/mail-hosting/mailboxes/)
- [Connect a mail app](/docs/mail/mail-hosting/connect-a-mail-app/)

## With the API

All the paths below sit under `/client/mail/{service_id}/migrations`. A relay has no mailboxes: the same paths under `/client/smtp-relay/{service_id}/migrations` answer `404`.

First see what a migration would do. Nothing changes on either side:

```bash
curl -X POST https://api.coritan.com/api/v1/client/mail/4812/migrations/cpanel/discover \
  -H "Authorization: Bearer $CORITAN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"host": "example.com", "username": "example", "token": "CPANEL_API_TOKEN"}'
```

`host` is the cPanel host, as you sign in to it: `example.com`, `example.com:2083`, or the server's name. Send `password` instead of `token` for an account without two-factor authentication. The answer lists each email account under `mailboxes`, with its `used_bytes`, the quota we suggest (`suggested_quota_bytes`) and its `state`:

| `state` | Meaning |
| --- | --- |
| `new` | A mailbox is made for it |
| `exists` | The mailbox with that address is already here, and its mail goes into it |
| `domain_missing`, `domain_unproved` | Its domain is not on the service, or not proved to be yours |
| `disabled` | The mailbox here is turned off |
| `taken` | The address is in use on Coritan as an alias or on another service |

`forwarders`, `catch_all` and `autoresponders` say what becomes of the rest, each with a `note` when it does not move. `room` says whether the plan has space for the new mailboxes (`fits`).

Then start it:

```bash
curl -X POST https://api.coritan.com/api/v1/client/mail/4812/migrations/cpanel \
  -H "Authorization: Bearer $CORITAN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"host": "example.com", "username": "example", "token": "CPANEL_API_TOKEN", "reset_passwords": true,
       "mailboxes": [{"address": "info@example.com"}, {"address": "alex@example.com", "password": "alex-old-password"}]}'
```

The body takes the sign-in above and:

`mailboxes`
: The old mailboxes to move, each `{"address"}` with an optional `password` (its current one, which changes nothing on the old server), `quota_bytes` (at least 100 MB) and `import` (`false` makes the mailbox without its mail). Leave the list out to move every mailbox that can.

`reset_passwords`
: `false` by default. With `true`, we set a new password on each old mailbox that has no `password` here, to read its mail. Without it, such a mailbox is made here without its mail.

`forwarders`, `catch_all`
: `true` by default: make the aliases and the catch-all.

`trash`, `spam`
: `false` by default: whether to copy the old Trash and Spam folders.

Nothing changes until every check passes. The answer is `201` with the migration, and `passwords` holds each new mailbox's address and password, once:

```json
{
  "id": 41,
  "status": "active",
  "source": {"host": "example.com", "port": 2083, "username": "example", "imap_host": "mail.example.com", "trash": false, "spam": false},
  "keeps_passwords": true,
  "keep_until": "2026-11-07T10:20:00+00:00",
  "mailboxes": [
    {"address": "info@example.com", "used_bytes": 2147483648, "account_id": 5121, "created": true, "read": "reset", "note": null,
     "import": {"id": 77, "status": "queued", "imported": 0, "duplicates": 0, "skipped": 0, "failed": 0, "next_try_at": null}}
  ],
  "made": [{"kind": "alias", "address": "hello@example.com", "targets": ["info@example.com"]}],
  "not_moved": [{"kind": "forwarder", "address": "boss@example.com", "reason": "Forwarding to someone@gmail.com needs that address to confirm it; set it up for a mailbox in webmail, under Settings, Forwarding"}],
  "totals": {"imported": 0, "duplicates": 0, "skipped": 0, "failed": 0, "done": 0, "importing": 1, "paused": 0, "stopped": 0},
  "passwords": [{"address": "info@example.com", "password": "…"}]
}
```

Each mailbox's `read` says how its mail is read: `given` (the password you gave), `reset` (a password we set on the old server) or `none` (not read; `note` says why). Its `import` is the import as [Move existing mail into a mailbox](/docs/mail/mail-hosting/move-mail-in/#with-the-api) describes it, and you steer it with that page's `pause`, `resume` and `cancel`.

The other operations:

| Operation | Answer |
| --- | --- |
| `GET .../migrations` | `{"items": [...], "max_mailboxes": 100, "keep_days": 30}`: the last 20 migrations, newest first |
| `GET .../migrations/{migration_id}` | The migration, without `passwords` |
| `POST .../migrations/{migration_id}/sync` | The migration: a paused import goes on, one under way is left alone, any other starts again |
| `POST .../migrations/{migration_id}/finish` | The migration, `finished`, its kept passwords forgotten |

A refused request answers with a `detail` object that holds a `code` and a `message`:

| Status | `code` | When |
| --- | --- | --- |
| `422` | `cpanel_incomplete` | The host, the username, or both the token and the password are missing, or the port is not `2083` or `443` |
| `422` | `cpanel_refused`, `cpanel_unreachable`, `cpanel_error`, `cpanel_empty` | cPanel refused the sign-in, did not answer, said something went wrong, or holds no email accounts |
| `422` | `migration_invalid` | A mailbox chosen cannot move; `errors` names each one and why. Nothing was changed |
| `422` | `plan_full`, `plan_storage` | The plan has too few mailboxes or too little storage left. Nothing was changed |
| `422` | `imap_unreachable`, `source_refused` | The old server's IMAP did not answer, or refused a password given; `errors` names each mailbox. Nothing was changed |
| `404` | `not_mail_hosting` | The service is not Mail Hosting |
| `409` | `service_inactive` | The service is not active |
| `409` | `migration_finished` | A sync of a migration whose passwords are forgotten |

A `migration_id` that is not the service's answers `404` `Migration not found`. A service may look at cPanel accounts 30 times an hour and start or sync migrations 10 times a day; past that the answer is `429`.

## API

- `GET /api/v1/client/mail/{service_id}/migrations`: List the service's migrations (https://www.coritan.com/docs/api/reference/client/mail/mail-migrations/#op-get-api-v1-client-mail-service-id-migrations)
- `POST /api/v1/client/mail/{service_id}/migrations/cpanel`: Move a cPanel account's mail here (https://www.coritan.com/docs/api/reference/client/mail/mail-migrations/#op-post-api-v1-client-mail-service-id-migrations-cpanel)
- `POST /api/v1/client/mail/{service_id}/migrations/cpanel/discover`: Find what a cPanel account holds and what moving it here would do (https://www.coritan.com/docs/api/reference/client/mail/mail-migrations/#op-post-api-v1-client-mail-service-id-migrations-cpanel-discover)
- `GET /api/v1/client/mail/{service_id}/migrations/{migration_id}`: Get one migration and how far each of its mailboxes has got (https://www.coritan.com/docs/api/reference/client/mail/mail-migrations/#op-get-api-v1-client-mail-service-id-migrations-migration-id)
- `POST /api/v1/client/mail/{service_id}/migrations/{migration_id}/finish`: Finish a migration and forget the old mailboxes' passwords (https://www.coritan.com/docs/api/reference/client/mail/mail-migrations/#op-post-api-v1-client-mail-service-id-migrations-migration-id-finish)
- `POST /api/v1/client/mail/{service_id}/migrations/{migration_id}/sync`: Bring in what reached the old mailboxes since the migration started (https://www.coritan.com/docs/api/reference/client/mail/mail-migrations/#op-post-api-v1-client-mail-service-id-migrations-migration-id-sync)
- `GET /api/v1/client/smtp-relay/{service_id}/migrations`: List the service's migrations (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-migrations/#op-get-api-v1-client-smtp-relay-service-id-migrations)
- `POST /api/v1/client/smtp-relay/{service_id}/migrations/cpanel`: Move a cPanel account's mail here (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-migrations/#op-post-api-v1-client-smtp-relay-service-id-migrations-cpanel)
- `POST /api/v1/client/smtp-relay/{service_id}/migrations/cpanel/discover`: Find what a cPanel account holds and what moving it here would do (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-migrations/#op-post-api-v1-client-smtp-relay-service-id-migrations-cpanel-discover)
- `GET /api/v1/client/smtp-relay/{service_id}/migrations/{migration_id}`: Get one migration and how far each of its mailboxes has got (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-migrations/#op-get-api-v1-client-smtp-relay-service-id-migrations-migration-id)
- `POST /api/v1/client/smtp-relay/{service_id}/migrations/{migration_id}/finish`: Finish a migration and forget the old mailboxes' passwords (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-migrations/#op-post-api-v1-client-smtp-relay-service-id-migrations-migration-id-finish)
- `POST /api/v1/client/smtp-relay/{service_id}/migrations/{migration_id}/sync`: Bring in what reached the old mailboxes since the migration started (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-migrations/#op-post-api-v1-client-smtp-relay-service-id-migrations-migration-id-sync)
