Migrating from Plesk
The generic engine (Migration) copies mail from any IMAP server. This is what Plesk adds on top: the old panel tells us who exists, what their quotas are, what forwards where and who is on holiday — so a migration starts from a list rather than a spreadsheet.
The shape is the same as cPanel. What differs is what Plesk can be asked, and that is worth reading before you plan a migration.
Which API, and why it is XML
Section titled “Which API, and why it is XML”Plesk has two APIs, and only one of them can answer any of the questions a migration asks.
| API | Covers | Mail? |
|---|---|---|
REST, /api/v2 |
Clients, subscriptions, domains, DNS, extensions, CLI | No mail endpoints at all |
XML-RPC, /enterprise/control/agent.php |
Everything, including mail |
Yes — mailboxes, aliases, redirects, auto-responders |
So this connector speaks XML where the cPanel one speaks JSON. Both of Plesk’s APIs are on 8443.
What the panel credential can and cannot do
Section titled “What the panel credential can and cannot do”| Can | List mail domains, mailboxes and their quotas, mail aliases, redirects, auto-responders. |
| Cannot | Read a single message. Recover a mailbox password — Plesk stores them hashed. Report how much mail a mailbox holds. Export a single mail filter. |
As with cPanel, a Plesk migration uses two credentials for two jobs: the panel credential, which
discovers, and whatever opens IMAP, which copies. They are stored together and sealed together but they are not interchangeable,
and a job with only a panel key will discover forty mailboxes and then fail all forty on auth.
Nothing here writes to the source. mail:set and mail:del sit behind the same endpoint and the
same credential; not wiring them up is deliberate, and is what keeps “a failed migration cannot have
damaged the customer’s old mail” true of this connector too.
Getting the credentials
Section titled “Getting the credentials”The panel credential
Section titled “The panel credential”Two forms. Prefer the first.
- An API key, sent as
KEY. Revocable, and can be bound to the address it is used from — neither of which a password can do. Made by the Plesk administrator; a key created through Plesk’s REST API works too, and both header spellings are sent so you do not have to know which kind you hold. - The panel login and password, which is what Plesk’s own documentation still shows. It works, and it is going to sit in our database until the cutover finishes.
An admin credential sees every domain on the box. A customer or subscription login sees only theirs.
Scoping to one domain
Section titled “Scoping to one domain”The job takes an optional mail domain. Leave it blank and every domain the login can see is read —
right for a customer login, usually far too much for admin on a shared box. Naming one is also how
you migrate a single customer off a server that hosts fifty.
The scope is enforced here, not just asked for. What Plesk says a result is about wins over what the job asked for, and a domain that comes back unasked-for is dropped with a warning. A Plesk that ignores the filter — or a filter spelling that matched nothing — would otherwise have every mailbox relabelled onto the named domain, inventing addresses nobody has and pre-creating mailboxes for people who are not there.
Opening the mailboxes
Section titled “Opening the mailboxes”In descending order of how much you will enjoy it:
- A Dovecot master user, if the old host’s root configured one. One password reads every mailbox. Plesk does not ship one, so this only exists if somebody deliberately added it.
- Each mailbox’s own password, entered when mapping. Fine for five mailboxes, tedious for fifty.
- Reset the passwords in Plesk and use the new ones. We do not do this for you, for the same reason as with cPanel.
The IMAP side is always 993 with TLS. The job’s source can carry another IMAP port and encryption, but the New migration page always sends 993/TLS for a Plesk job, so a host that serves IMAP only on 143 with STARTTLS cannot be set up from the admin UI.
Running one
Section titled “Running one”- New migration → Plesk. Hostname, port, the key (or login and password), Mail server, if it is not the same
machine when IMAP is served elsewhere, and optionally the one
domain to read. Test proves the credential by listing the domains. Give a mailbox address as well
and the test also tries IMAP: with a master user, or that mailbox’s password (asked for when there
is no master user, never stored), it opens the mailbox; with neither it only checks the server
answers (see
migration.md→ How a migration runs). - Add the domains on the Domains page for any that do not exist here yet. The migration will not create them, and mailboxes on a missing domain are skipped.
- Read the source on the job’s Discover step. Every mailbox with its quota, plus counts of forwarders and auto-responders, plus the warnings below.
- Create and map the missing mailboxes, optionally carrying each quota across; it is applied when the migration
ends (
migration.md→ Quotas). The generated passwords are shown once. - Import the forwarders and auto-responders.
- From here it is an ordinary migration: Migrating a domain and cutting over.
How each thing is translated
Section titled “How each thing is translated”One mail:get_info reads the lot, so the domain list and the mailbox list come out of the same answer
and cannot disagree.
Mailnames → mailboxes, forwarders or aliases
Section titled “Mailnames → mailboxes, forwarders or aliases”Plesk has one object where this server has three. A mailname is an address; whether it holds mail
depends on <mailbox><enabled>.
| In Plesk | Here |
|---|---|
| A mailname with a mailbox | A mailbox |
| A redirect on a mailname with a mailbox | A forwarder with keep a copy — delivered here and sent on |
| A redirect on a mailname without one | An alias, with every destination for that address |
A mail alias (<alias>, a second address for a box) |
An alias pointing at that mailbox |
The middle two are the ones worth getting right: creating a mailbox for a redirect-only address would swallow the redirect, and mail that used to reach two people would land in an empty mailbox nobody reads. It is decided by looking the address up, never by guessing from the shape of the data.
A mailname Plesk has switched off is reported suspended in discovery, and is created here as an ordinary active mailbox; switch on Suspended afterwards if needed.
Quotas, and the absence of sizes
Section titled “Quotas, and the absence of sizes”Plesk reports the quota in bytes. Unlimited is spelled both -1 and 0, and both are read as no
quota reported — never a zero-byte allowance — so the mailbox takes the domain default.
Plesk does not report how much mail a mailbox holds. The size column is empty and you are told
why; the copy itself does not need it, but you cannot use this screen to estimate how long it will
take. imapsync reports the real figures per mailbox once a run starts.
Auto-responders → Sieve vacation
Section titled “Auto-responders → Sieve vacation”Subject, body and window come across. Two things to know:
- A responder whose window has already closed is imported switched off. Silently reactivating an out-of-date away message on a server the sender has just started using is not a helpful default.
- Plesk’s
freqis “at most N replies per day to one sender”; ours is “at most one reply per N days”. They only agree at 1, and every other Plesk value is more talkative than anything we can express — so the strictest honest reading is used, one day.
Filters — there are none to import
Section titled “Filters — there are none to import”Plesk exposes no mail filters through its API. Its mail rules are the server-wide SpamAssassin settings, which are not filters and would not translate anyway (this server scores with Rspamd and writes different headers).
So a Plesk discovery reports zero filters, and says out loud that none were read — which is not the same as there having been none. Any rules the old server ran have to be recreated by hand. If the old Plesk had ManageSieve enabled and users wrote Sieve scripts, those are invisible to the panel API; copy them out of the old server yourself before the cutover.
Tolerated differences between Plesk versions
Section titled “Tolerated differences between Plesk versions”Two things are tried in turn rather than assumed, because guessing wrong about the shape of a request is not a reason to tell an admin their server has no mail on it:
- The domain filter is
site-nameon Plesk 17 anddomain-nameon older builds. If the newer spelling is rejected as a schema error, the older one is tried. - The datasets. The full request asks for mailboxes, redirects, aliases and auto-responders. A Plesk that rejects it gets a mailboxes-only request instead, and you are told that the rest could not be read.
Neither retry happens for an auth or a connection failure — a rejected credential fails immediately rather than being retried as though it were a version problem.
The request carries no protocol version, so Plesk answers with the newest it supports. Naming a version we guessed is how you get “unsupported protocol version” from a box perfectly capable of answering the question.
Why domains are not created
Section titled “Why domains are not created”Same as cPanel: pre-create makes mailboxes, never domains. Adding a domain rotates a DKIM key, publishes DNS records and consumes licence entitlement — it belongs on the Domains page, done once and deliberately. Mailbox creation itself still goes through the licence check.
Errors specific to this connector
Section titled “Errors specific to this connector”| Code | Means |
|---|---|
source_auth (400) |
The key or password was rejected, or the login cannot see that domain. Plesk reports this at HTTP 200 with <system><status>error</status>, so it is checked for. |
source_tls (400) |
The panel’s certificate did not verify. Plesk’s 8443 is self-signed on plenty of boxes; re-create the migration with Accept an invalid TLS certificate if you recognise the host. |
source_error (502) |
The named domain does not exist on that Plesk (1013). |
source_unreachable (502) |
DNS, firewall or the wrong port. 8443 is Plesk’s. |
source_rate_limited (429) |
The panel is throttling the API. Wait. |
source_error (502) |
Anything else, with the panel’s own words. An HTML page at HTTP 200 means the wrong port, and says so. |
A single domain that fails inside a whole-server read does not fail the request: the domains that worked are returned and the failure becomes a warning. A read that finds nothing but failures throws.
The per-mailbox copy errors are the ordinary ones: When a migration fails.
Where it lives
Section titled “Where it lives”| Piece | Where |
|---|---|
| Schemas | packages/core/src/migration.ts (PleskSourceConfig, PleskSourceSecret) |
| Shared panel HTTP | packages/migration/src/connectors/http.ts |
| XML client and envelopes | packages/migration/src/connectors/plesk-api.ts |
| The connector | packages/migration/src/connectors/plesk.ts |
| Pre-create and import | apps/api/src/services/migration-import.ts (shared with cPanel) |
| Routes | POST /migrations/:id/{discover,precreate,import} |
| UI | apps/admin/src/routes/migration.tsx (SourceDiscovery) |