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.comover SSL - Preserves original dates — parses the
Date:header from each.emland 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
.emlfiles 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
-
Export your Proton Mail — use the Proton Mail Export Tool to download your mailbox as
.emlfiles. -
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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
eb99d4c127b31912eb84938702b7619b6f9372a3800b26ae4e8d0e79df630d6e
|
|
| MD5 |
e72c8ec0d521046e70201ef2fadb5deb
|
|
| BLAKE2b-256 |
5296fe63952da018531da1cbe1ca4a138d79fcf34f3d8944c26a8f5b4d9de7e4
|
Provenance
The following attestation bundles were made for proton_to_icloud-0.1.0.tar.gz:
Publisher:
publish.yml on abuchmueller/proton-to-icloud
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
proton_to_icloud-0.1.0.tar.gz -
Subject digest:
eb99d4c127b31912eb84938702b7619b6f9372a3800b26ae4e8d0e79df630d6e - Sigstore transparency entry: 1006426271
- Sigstore integration time:
-
Permalink:
abuchmueller/proton-to-icloud@b4843284e860988f9605fc8cb6aee5feb391e2c9 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/abuchmueller
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b4843284e860988f9605fc8cb6aee5feb391e2c9 -
Trigger Event:
release
-
Statement type:
File details
Details for the file proton_to_icloud-0.1.0-py3-none-any.whl.
File metadata
- Download URL: proton_to_icloud-0.1.0-py3-none-any.whl
- Upload date:
- Size: 23.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
305468940b0ec4951dd8c2add8e302c427bb930ae8e2d06eb6b3771be6505566
|
|
| MD5 |
088a07da5f554d47a8e996231355105e
|
|
| BLAKE2b-256 |
b5760b5e67d98261768a2ec9639fdba276002f49f0f464601ae3486bb9201170
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
proton_to_icloud-0.1.0-py3-none-any.whl -
Subject digest:
305468940b0ec4951dd8c2add8e302c427bb930ae8e2d06eb6b3771be6505566 - Sigstore transparency entry: 1006426272
- Sigstore integration time:
-
Permalink:
abuchmueller/proton-to-icloud@b4843284e860988f9605fc8cb6aee5feb391e2c9 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/abuchmueller
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b4843284e860988f9605fc8cb6aee5feb391e2c9 -
Trigger Event:
release
-
Statement type: