Skip to main content

Migrate Proton Mail exports (.eml) to iCloud Mail via IMAP

Project description

proton-to-icloud

Migrate Proton Mail exports (.eml files) to iCloud Mail via IMAP.

Why?

Proton Mail lets you export your mailbox as .eml files, but Apple Mail has no reliable bulk-import for thousands of .eml files. Drag-and-drop silently drops messages, File > Import creates a folder per file, and .mbox renaming is rejected outright.

proton-to-icloud solves this by uploading .eml files directly to iCloud via IMAP APPEND, preserving original dates, read/unread status, and folder structure. It's a zero-dependency Python CLI tool that you can install globally with pipx or uv tool install.

Features

  • Direct IMAP upload — bypasses Apple Mail entirely, talks to imap.mail.me.com over SSL
  • Preserves original dates — parses the Date: header from each .eml and sets the IMAP internal date accordingly
  • Automatic resume — saves progress to a state file every 100 messages; resumes where you left off after interruptions
  • Progress bar — live terminal progress with ETA, throughput, and success/failure counts
  • Batch splitting — optionally split thousands of .eml files into numbered batch folders
  • Zero dependencies — stdlib only, no conflicts when installed globally
  • Folder routing — automatically routes emails to the correct IMAP folders (Inbox, Sent, Archive, etc.) based on Proton metadata
  • Dry-run mode — scan and count files without connecting or uploading

Installation

With uv (recommended)

uv tool install proton-to-icloud

With pipx

pipx install proton-to-icloud

From source

git clone https://github.com/abuchmueller/proton-to-icloud.git
cd proton-to-icloud
uv sync
uv run proton-to-icloud --help

Prerequisites

  1. Export your Proton Mail — use the Proton Mail Export Tool to download your mailbox as .eml files.

  2. Generate an App-Specific Password — go to appleid.apple.com → Sign-In and Security → App-Specific Passwords → Generate. This is required because iCloud IMAP does not accept your regular Apple ID password.

Usage

The Proton Mail Export Tool creates a folder structure like:

your.address@pm.me/
└── mail_20260223_210229/
    ├── messageId1.eml
    ├── messageId1.metadata.json
    ├── messageId2.eml
    ├── messageId2.metadata.json
    └── ...

Point --source at the mail_* directory (the one containing the .eml files). If you omit --source, an interactive folder picker launches — navigate with arrow keys, Enter to open a directory, Space to select, Esc to cancel.

Upload .eml files to iCloud

proton-to-icloud upload \
  --source "your.address@pm.me/mail_20260223_210229" \
  --mailbox "Proton-Import" \
  --email you@icloud.com

You'll be prompted securely for the app-specific password.

Options:

Flag Description Default
-s, --source Directory containing .eml files (recursive). Interactive picker when omitted. (picker)
-m, --mailbox Base IMAP folder for subfolder routing and fallback Proton-Import
-e, --email Your iCloud / Apple ID email (required)
-p, --password App-specific password (prompted if omitted) (prompted)
--direct Route into native iCloud folders instead of subfolders
--dry-run Scan only, don't connect or upload
--resume-from N Skip the first N files 0
--no-create-mailbox Don't auto-create the target folder

Folder Routing

When Proton metadata (labels.json and .metadata.json files) is present, emails are automatically routed to the correct folders based on their Proton labels.

Default mode — emails go into subfolders of --mailbox:

proton-to-icloud upload \
  --source "your.address@pm.me/mail_20260223_210229" \
  --email you@icloud.com
# → Proton-Import/Inbox, Proton-Import/Sent, Proton-Import/Archive, etc.

Direct mode (--direct) — emails go into native iCloud folders:

proton-to-icloud upload \
  --source "your.address@pm.me/mail_20260223_210229" \
  --email you@icloud.com \
  --direct
# → INBOX, Sent Messages, Archive, Junk, Deleted Messages, etc.
Proton Label Default mode (--mailbox X) --direct mode
Inbox X/Inbox INBOX
Sent X/Sent Sent Messages
Drafts X/Drafts Drafts
Spam X/Spam Junk
Trash X/Trash Deleted Messages
Archive X/Archive Archive
Unknown / no metadata X (fallback) X (fallback)

If no labels.json is found, all emails go to --mailbox (backward-compatible).

Split .eml files into batch folders

If you prefer to drag-and-drop smaller batches into Apple Mail instead:

proton-to-icloud batch \
  --source "your.address@pm.me/mail_20260223_210229" \
  --batch-size 1000

Options:

Flag Description Default
-s, --source Directory containing .eml files. Interactive picker when omitted. (picker)
-n, --batch-size Files per batch folder 1000
-o, --output Output directory for batch folders <source>/batches
--move Move files instead of copying copy

Resume & Interruption Handling

The upload command saves a .imap_upload_state.json file inside the source directory after every 100 messages. If the process is interrupted (Ctrl+C, network drop, etc.), simply re-run the same command — it will detect the state file and offer to resume.

You can also manually resume with --resume-from N to skip the first N files.

Performance

In testing, throughput is approximately 1–2 seconds per message depending on file size and network conditions. For 50,000 messages, expect roughly 14–28 hours of upload time. An ethernet connection and a machine that can run uninterrupted is recommended for large imports.

Development

uv sync                        # Install deps
uv run pytest                  # Run tests
uv run ruff check src/ tests/  # Lint
uv run ruff format src/ tests/ # Format

Claude Code Integration

If you use Claude Code, this repo includes two project-level skills:

  • /upload-guide — Interactive walkthrough that helps you construct the right upload command, choose routing modes, and handle long-running uploads.
  • /diagnose-failures — Automated diagnostic workflow for investigating upload failures (header analysis, IMAP test matrix, root-cause suggestions).

These are optional — the CLI and this README are the primary interface.

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

proton_to_icloud-0.1.0.tar.gz (39.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

proton_to_icloud-0.1.0-py3-none-any.whl (23.1 kB view details)

Uploaded Python 3

File details

Details for the file proton_to_icloud-0.1.0.tar.gz.

File metadata

  • Download URL: proton_to_icloud-0.1.0.tar.gz
  • Upload date:
  • Size: 39.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for proton_to_icloud-0.1.0.tar.gz
Algorithm Hash digest
SHA256 eb99d4c127b31912eb84938702b7619b6f9372a3800b26ae4e8d0e79df630d6e
MD5 e72c8ec0d521046e70201ef2fadb5deb
BLAKE2b-256 5296fe63952da018531da1cbe1ca4a138d79fcf34f3d8944c26a8f5b4d9de7e4

See more details on using hashes here.

Provenance

The following attestation bundles were made for proton_to_icloud-0.1.0.tar.gz:

Publisher: publish.yml on abuchmueller/proton-to-icloud

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file proton_to_icloud-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for proton_to_icloud-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 305468940b0ec4951dd8c2add8e302c427bb930ae8e2d06eb6b3771be6505566
MD5 088a07da5f554d47a8e996231355105e
BLAKE2b-256 b5760b5e67d98261768a2ec9639fdba276002f49f0f464601ae3486bb9201170

See more details on using hashes here.

Provenance

The following attestation bundles were made for proton_to_icloud-0.1.0-py3-none-any.whl:

Publisher: publish.yml on abuchmueller/proton-to-icloud

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page