Migration: Titan / any IMAP email provider → cPanel / Dovecot
This guide is written to be reusable for any domain. Replace the example values with your own domain, mailbox, server IP and credentials.
The safest migration is a staged process. First copy the existing mail, then verify it, then change mail routing, then configure authentication and finally perform a last synchronization.
| Component | Example | Purpose |
|---|---|---|
| Domain | example.com | The domain being migrated. |
| Old IMAP host | imap.titan.email | Source mail server. |
| New mail host | mail.example.com | Destination cPanel/Dovecot server. |
| Mail server IP | 203.0.113.10 | Public IPv4 of the new mail server. |
| Mailbox | user@example.com | Mailbox being migrated. |
| IMAP | 993 / SSL/TLS | Used by imapsync to copy messages. |
| SMTP | 465 / SSL/TLS | Used by mail clients to send mail. |
Keep the old mail provider active until migration, verification, final synchronization and DNS cutover are complete. This prevents messages from being lost during the transition.
dig NS example.com +short
The returned nameservers tell you where the authoritative DNS zone is hosted. DNS records should be changed at that provider unless cPanel is authoritative for the domain.
In cPanel:
user@example.com.To list mailboxes from the shell, where supported:
uapi --user=CPANEL_USERNAME Email list_pops
Create an A record for the hostname used by cPanel mail clients:
| Type | Name | Value |
|---|---|---|
| A | mail | NEW_MAIL_SERVER_IP |
Verify:
dig A mail.example.com +short
Expected result:
203.0.113.10
Verify that cPanel/Dovecot is listening for IMAPS:
ss -ltnp | grep ':993'
Port 993 should normally show Dovecot listening on IPv4 and/or IPv6.
imapsync \
--host1 imap.titan.email \
--port1 993 \
--ssl1 \
--user1 'user@example.com' \
--justconnect
imapsync \
--host1 mail.example.com \
--port1 993 \
--ssl1 \
--user1 'user@example.com' \
--justconnect
These tests establish network/TLS connectivity. A successful --justconnect test does not necessarily prove that the password is correct.
imapsync \
--host1 imap.titan.email \
--port1 993 \
--ssl1 \
--user1 'user@example.com' \
--host2 mail.example.com \
--port2 993 \
--ssl2 \
--user2 'user@example.com' \
--justfolders
Enter the source and destination passwords only when prompted. A successful run should report successful authentication and no errors.
Once connectivity and authentication are confirmed, run the actual synchronization:
imapsync \
--host1 imap.titan.email \
--port1 993 \
--ssl1 \
--user1 'user@example.com' \
--host2 mail.example.com \
--port2 993 \
--ssl2 \
--user2 'user@example.com'
The command copies messages from host1 to host2. With this basic command, the source mailbox remains available.
Detected 0 errors and a successful exit status.--delete1 or --delete2 unless you fully understand the consequences and have a backup. For a migration, preserving the source is the safer default.Do not delete the source mailbox yet.
Only change MX after the mailbox has been migrated and verified.
Remove the old provider's MX records and add the new mail server. Example:
| Type | Name | Priority | Target |
|---|---|---|---|
| MX | @ | 10 | mail.example.com. |
The exact priority is not the important part; the important part is that the intended mail server is the MX destination and obsolete MX records are not left active unintentionally.
dig NS example.com +short
dig MX example.com @AUTHORITATIVE_NAMESERVER +short
Also check public resolvers:
dig MX example.com @1.1.1.1 +short
dig MX example.com @8.8.8.8 +short
Different DNS resolvers may temporarily return different MX records because they cache records according to their TTL. The authoritative nameservers are the best reference for determining whether the change was actually published.
mail.example.com while Google DNS temporarily reports the old provider, the authoritative change is already published but some public caches have not expired.Do not repeatedly edit MX records simply because one resolver still has the old value.
SPF tells receiving servers which systems are authorized to send mail for your domain.
If the new mail server sends directly from 203.0.113.10, a simple SPF example is:
v=spf1 ip4:203.0.113.10 ~all
A domain should normally have one SPF TXT policy. Do not create multiple independent SPF TXT records beginning with v=spf1.
If the old provider is no longer sending mail, remove its SPF include. If another legitimate service still sends mail (website, marketing platform, CRM, Microsoft 365, Google Workspace, etc.), its authorization must remain in the same SPF record.
Verify:
dig TXT example.com +short
DKIM adds a cryptographic signature to outgoing mail. cPanel/Exim can generate the DKIM key pair.
In cPanel, open:
Email → Email Deliverability → Manage for the domain.
cPanel will show a suggested DKIM record, normally using a selector such as:
default._domainkey
The DNS record generally looks like:
Host/Name:
default._domainkey
Type:
TXT
Value:
v=DKIM1; k=rsa; p=YOUR_PUBLIC_KEY
cPanel commonly stores DKIM keys under:
/var/cpanel/domain_keys/private/
/var/cpanel/domain_keys/public/
Do not paste the contents of a private-key file into DNS.
DMARC tells receiving systems how to handle mail that fails authentication/alignment and provides reporting capabilities.
A safe starting policy is monitoring mode:
| Type | Name | Value |
|---|---|---|
| TXT | _dmarc | v=DMARC1; p=none; |
After SPF/DKIM are confirmed and legitimate senders are known, the policy can be strengthened gradually, for example with quarantine and eventually reject.
p=reject if you have not identified every legitimate system that sends mail for the domain.From Gmail, Outlook or another external mailbox, send:
external-account@example.net
↓
user@example.com
Confirm the message arrives in cPanel Webmail.
From cPanel Webmail, send:
user@example.com
↓
external Gmail/Outlook address
Confirm delivery. Check both Inbox and Spam.
For a delivered message, inspect the recipient's message headers. Look for results similar to:
SPF: PASS
DKIM: PASS
DMARC: PASS
A Gmail rejection such as:
550-5.7.26 Your email has been blocked because the sender is unauthenticated.
SPF = did not pass
DKIM = did not pass
usually means DNS authentication is missing, incorrect, not propagated, or the sending server is not authorized.
After MX is changed and mail is being delivered to the new server, perform one final source-to-destination sync if the old mailbox remained active during the transition.
imapsync \
--host1 imap.titan.email \
--port1 993 \
--ssl1 \
--user1 'user@example.com' \
--host2 mail.example.com \
--port2 993 \
--ssl2 \
--user2 'user@example.com'
This catches messages that arrived on the old server before DNS propagation completed.
dig MX example.com +short
dig MX example.com @1.1.1.1 +short
dig MX example.com @8.8.8.8 +short
dig MX example.com @AUTHORITATIVE_NAMESERVER +short
If authoritative DNS is correct but a public resolver is old, wait for its cache to expire.
mail.example.com resolves to the correct IP.Useful commands:
dig MX example.com +short
dig A mail.example.com +short
systemctl status exim
tail -f /var/log/exim_mainlog
This can happen when the destination mailbox already contained a welcome/system message before migration. Do not delete it automatically. Compare the message and decide whether it should remain.
| Setting | Recommended value |
|---|---|
| Incoming protocol | IMAP |
| Incoming server | mail.example.com |
| IMAP port | 993 |
| IMAP security | SSL/TLS |
| Outgoing server | mail.example.com |
| SMTP port | 465 SSL/TLS (or the port shown by cPanel) |
| Username | Full email address |
| Password | Mailbox password |
CREATE MAILBOX
↓
CREATE mail.domain.com A RECORD
↓
TEST IMAP CONNECTION
↓
TEST AUTHENTICATION
↓
SYNC FOLDERS
↓
MIGRATE EMAILS
↓
VERIFY MAIL + ATTACHMENTS
↓
CHANGE MX
↓
WAIT / CHECK DNS PROPAGATION
↓
TEST INBOUND
↓
CONFIGURE SPF
↓
CONFIGURE DKIM
↓
CONFIGURE DMARC
↓
TEST OUTBOUND
↓
FINAL IMAPSYNC
↓
KEEP OLD PROVIDER TEMPORARILY
↓
FINAL CUTOVER COMPLETE
This documentation uses a server-side SQLite counter. Each successful page load increments the total view count.
The counter is stored on the server, so it is not reset when the browser is refreshed or closed.