Date header, so threads and list views sort historically. Dates that are implausible, meaning before 1990 or in the future, are clamped to the import time. Threading is rebuilt from In-Reply-To and References exactly as it is for live mail, and imported threads land in the inbox folder.
Imported messages appear in normal message lists and threads, but do not generate inbound-message webhooks and are not evaluated against contact rules. Messages imported by a job carry its ID in import_job_id.
Creating, refreshing the upload target, starting, and cancelling an import require a human session, an organization API key, or an API key scoped to a claimed agent. Reading and listing import jobs also accept API keys scoped to unclaimed agents, subject to normal mailbox visibility.
Formats
Because ZIP entries ending
.mbox are imported, a mail export archive that contains one or more .mbox files can be uploaded as downloaded, with no need to unpack or repack it first.
Producing an importable file
Most providers offer a full-mailbox export that produces an MBOX file, sometimes delivered inside a ZIP archive. Upload size is capped at 1 GiB, and a full export of a long-lived mailbox often exceeds that. If yours does, narrow the export to a subset of labels or folders and run one import per file, or split the MBOX and import each part. Duplicate messages across those files are skipped rather than imported twice, so overlapping exports are safe.Complete SDK flow
The Python and TypeScript SDKs upload directly using the target returned bycreate, so you do not need to construct the multipart upload yourself.
wait fetches immediately, then polls every five seconds by default. It returns a job for every terminal state, including failed and cancelled; those states are results, not request errors. A local timeout only stops waiting and does not cancel the import. Call cancel separately when you want processing to stop.
Direction and read state
Setoriginal_addresses to every address from which you sent mail in the old mailbox. An imported message whose From address matches the destination mailbox or one of these addresses is classified as outbound; other messages are classified as inbound. Without original_addresses, sent mail from an old address may appear inbound.
Direction inference is best-effort by design. From headers in an export are unauthenticated, so a message that spoofed one of your addresses classifies as outbound. Do not treat direction on imported mail as a trustworthy signal.
Imported messages are marked read by default. Set mark_as_read to false in the API, mark_as_read=False in Python, markAsRead: false in TypeScript, or pass --mark-unread to the CLI to import them unread.
Create import
pending_upload and receive an upload target.
Request body
Request example
JSON
Response (201)
JSON
pending_upload, queued, or running returns 409.
Error responses
Import errors use a structured
detail object rather than a plain string:
JSON
Upload file
Send amultipart/form-data POST to upload.url. Include every value from upload.fields as a form field, in the order returned and unmodified, followed by the file in a field named file. The field set is opaque and may change, so copy it verbatim rather than hard-coding names. The SDK upload methods handle this directly.
Refresh upload target
upload object. Python uses refresh_upload_target(email_address, job_id) and TypeScript uses refreshUploadTarget(emailAddress, jobId).
Error responses
Start import
queued or running is idempotent. The response is the updated import job.
Starting also records the uploaded file’s identity. Replacing that file afterward does not restart or extend the import: the job fails instead of processing different data. If an upload was interrupted, re-upload before calling start, never after.
Error responses
Get import
404.
List imports
A
cursor that was not produced by this endpoint returns 400.
Cancel import
pending_upload, queued, or running job. Processing stops at the next item or control boundary, so a running import may continue briefly. Messages already imported remain in the mailbox.
Error responses
Statuses
The terminal states are
completed, failed, and cancelled.
Counters
Unsafe rejection is per message: the rejected message is counted in
messages_rejected_unsafe, and the import continues when possible.
Counters are durable checkpoints, not a percentage. They can remain unchanged while a large message is processed, and they stay cumulative if processing restarts: a restart resumes from the last checkpoint rather than recounting. Use status to determine completion and display the counters as independent totals.
Job fields
Limits
ZIP imports support stored or deflated, unencrypted, single-disk archives. ZIP64 archives are not supported, and the archive must contain at least one importable mail entry.
Imported messages count toward the mailbox storage allowance.
Queueing and terminal states
Queued jobs are processed oldest first, with at most one running import per organization. Imports also share service-wide processing capacity, so a queued job may wait while imports for other organizations run. A job that sits inqueued with unchanging counters is waiting its turn, not stuck.
A job ends failed, with the reason in error_detail, when it cannot continue. The common causes are:
- The upload contained no importable messages, including a ZIP with no usable mail entries.
- The next message would exceed the mailbox storage allowance. Messages imported before that point remain.
- The uploaded file was replaced after
start. - The archive violated one of the safety limits above.
messages_skipped_duplicate instead of being duplicated.
