Overview
The IMAP Migration API allows you to programmatically migrate emails from one IMAP server to another. Migrations run in a queue system with a maximum of 4 concurrent jobs to ensure stability.
Endpoints
| Method | Endpoint | Description |
|---|---|---|
POST | /api/v1/imap/test-connection | Test IMAP connection |
POST | /api/v1/imap/list-folders | List folders with message counts |
POST | /api/v1/imap/migrate | Start migration (queued) |
GET | /api/v1/imap/migrate/{id} | Get migration status & progress |
Features
- Full folder migration with messages, flags, and timestamps
- Optional destination prefix (route all folders into a subdirectory)
- Queue system with position tracking (max 4 concurrent)
- Real-time progress via polling
- Automatic folder hierarchy creation
Authentication
Authorization: Bearer YOUR_API_TOKEN
Typical Workflow
- Test source connection:
POST /api/v1/imap/test-connection - List source folders:
POST /api/v1/imap/list-folders - Test destination connection:
POST /api/v1/imap/test-connection - Start migration:
POST /api/v1/imap/migrate - Poll status:
GET /api/v1/imap/migrate/{id}every 2-5 seconds
Test Connection & List Folders
Test Connection
POST /api/v1/imap/test-connection
Authorization: Bearer YOUR_API_TOKEN
Content-Type: application/json
{
"host": "imap.example.com",
"port": 993,
"user": "user@example.com",
"password": "your-password",
"secure": true
}
Response (success):
{
"success": true,
"connected": true,
"folderCount": 5,
"listFailed": false,
"statusOnly": false,
"server": { "host": "imap.example.com", "port": 993, "secure": true },
"message": "Connected successfully. 5 folders found."
}
Response (success with LIST fallback - some servers/proxies reject LIST):
{
"success": true,
"connected": true,
"folderCount": 1,
"listFailed": true,
"statusOnly": false,
"server": { "host": "imap.example.com", "port": 993, "secure": true },
"message": "Connected but LIST command failed. Folders will be enumerated individually."
}
Response (failure):
{ "success": false, "error": "Authentication failed" }
List Folders
POST /api/v1/imap/list-folders
Authorization: Bearer YOUR_API_TOKEN
Content-Type: application/json
{
"host": "imap.example.com",
"port": 993,
"user": "user@example.com",
"password": "your-password",
"secure": true
}
Response (success):
{
"success": true,
"data": {
"folders": [
{ "path": "INBOX", "name": "INBOX", "messages": 3200, "unseen": 42, "delimiter": "/", "flags": [], "specialUse": "\Inbox" },
{ "path": "Sent", "name": "Sent", "messages": 890, "unseen": 0, "delimiter": "/", "flags": [], "specialUse": "\Sent" },
{ "path": "Drafts", "name": "Drafts", "messages": 12, "unseen": 0, "delimiter": "/", "flags": [], "specialUse": "\Drafts" },
{ "path": "Trash", "name": "Trash", "messages": 421, "unseen": 0, "delimiter": "/", "flags": [], "specialUse": "\Trash" },
{ "path": "Archive", "name": "Archive", "messages": 0, "unseen": 0, "delimiter": "/", "flags": [], "specialUse": null }
],
"totalFolders": 5,
"totalMessages": 4523,
"server": { "host": "imap.example.com", "user": "user@example.com" },
"usedFallback": false,
"statusOnly": false
}
}
Response (failure - nothing found):
{ "success": false, "error": "Connected but LIST failed and no folders could be found." }
Important: The folder list is wrapped in the data object. Use data.folders[].path values as input for the folders array when starting a migration.
Proxy fallback: Some servers (e.g., Zimbra proxies) reject the LIST command. The API then discovers folders via alternate LIST patterns and STATUS probes. In that case usedFallback is true; when only STATUS-based discovery was possible, statusOnly is true. The connection itself is still valid.
Start Migration
POST /api/v1/imap/migrate
Authorization: Bearer YOUR_API_TOKEN
Content-Type: application/json
{
"source": {
"host": "imap.old-server.com",
"port": 993,
"user": "user@old-server.com",
"password": "source-password",
"secure": true
},
"destination": {
"host": "imap.new-server.com",
"port": 993,
"user": "user@new-server.com",
"password": "dest-password",
"secure": true
},
"folders": ["INBOX", "Sent", "Drafts", "Archive"],
"destPrefix": "Old-Account"
}
Response:
{
"success": true,
"migrationId": "abc-123-def",
"status": "pending",
"queue": {
"position": 2,
"totalInQueue": 3
},
"statusUrl": "/api/v1/imap/migrate/abc-123-def"
}
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
source.host | string | yes | IMAP hostname of source server |
source.port | number | no | Port (default: 993) |
source.user | string | yes | Username / email |
source.password | string | yes | Password |
source.secure | boolean | no | SSL/TLS (default: true) |
destination.* | - | yes | Same fields as source |
folders | string[] | yes | Array of folder paths to migrate (max 200) |
destPrefix | string | no | Optional: Prefix for destination folders (e.g. "Backup" → INBOX becomes Backup/INBOX) |
deduplicate | boolean | no | Optional: Skip messages whose Message-ID already exists in the destination folder (default: false) |
Monthly limit: Migrations are limited per plan (free: 1, starter: 5, pro: 20, business: 50, enterprise: unlimited). Exceeding the limit returns HTTP 429 with {"success": false, "error": "Monthly limit reached (n)"}.
Check Status
GET /api/v1/imap/migrate/{migrationId}
Authorization: Bearer YOUR_API_TOKEN
Response:
{
"success": true,
"migrationId": "abc-123-def",
"status": "running",
"srcHost": "imap.old-server.com",
"srcUser": "user@old-server.com",
"dstHost": "imap.new-server.com",
"dstUser": "user@new-server.com",
"destPrefix": "Old-Account/",
"folders": ["INBOX", "Sent", "Drafts", "Archive"],
"progress": {
"currentFolder": "INBOX",
"foldersDone": 1,
"foldersTotal": 4,
"messagesDone": 523,
"messagesTotal": 2841,
"skippedByDedup": 0,
"folderProgress": { "INBOX": { "lastUid": 523, "doneCount": 523 } },
"errors": []
},
"totalFolders": 4,
"totalMessages": 523,
"errorCount": 0,
"startedAt": "2026-01-15T10:30:00.000Z",
"completedAt": null,
"createdAt": "2026-01-15T10:29:55.000Z",
"queue": null
}
Status Values
| Status | Description |
|---|---|
pending | In queue, waiting for available slot |
running | Currently migrating emails |
completed | Successfully finished |
failed | Failed (check errorCount and progress.errors) |
Polling
Poll the status endpoint every 2-5 seconds to track progress. The progress object updates in real-time during migration.