proton2m365 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- proton2m365-0.1.0/.gitignore +14 -0
- proton2m365-0.1.0/CHANGELOG.md +22 -0
- proton2m365-0.1.0/LICENSE +21 -0
- proton2m365-0.1.0/PKG-INFO +172 -0
- proton2m365-0.1.0/README.md +142 -0
- proton2m365-0.1.0/SECURITY.md +23 -0
- proton2m365-0.1.0/pyproject.toml +59 -0
- proton2m365-0.1.0/src/proton2m365/__init__.py +1 -0
- proton2m365-0.1.0/src/proton2m365/auth.py +255 -0
- proton2m365-0.1.0/src/proton2m365/cli.py +1342 -0
- proton2m365-0.1.0/src/proton2m365/content.py +195 -0
- proton2m365-0.1.0/src/proton2m365/doctor.py +219 -0
- proton2m365-0.1.0/src/proton2m365/graph_store.py +341 -0
- proton2m365-0.1.0/src/proton2m365/imap_store.py +554 -0
- proton2m365-0.1.0/src/proton2m365/lease.py +111 -0
- proton2m365-0.1.0/src/proton2m365/locks.py +80 -0
- proton2m365-0.1.0/src/proton2m365/pipeline.py +974 -0
- proton2m365-0.1.0/src/proton2m365/progress.py +60 -0
- proton2m365-0.1.0/src/proton2m365/proton_export.py +765 -0
- proton2m365-0.1.0/src/proton2m365/receipt.py +330 -0
- proton2m365-0.1.0/src/proton2m365/sample_export.py +122 -0
- proton2m365-0.1.0/src/proton2m365/state.py +577 -0
- proton2m365-0.1.0/src/proton2m365/status.py +211 -0
- proton2m365-0.1.0/src/proton2m365/targets.py +75 -0
- proton2m365-0.1.0/tests/__init__.py +0 -0
- proton2m365-0.1.0/tests/conftest.py +49 -0
- proton2m365-0.1.0/tests/fixtures/SYNTHETIC.txt +2 -0
- proton2m365-0.1.0/tests/snapshots/doctor.txt +22 -0
- proton2m365-0.1.0/tests/snapshots/plan-dry-run.txt +23 -0
- proton2m365-0.1.0/tests/snapshots/plan.txt +25 -0
- proton2m365-0.1.0/tests/snapshots/replan-exclude.txt +27 -0
- proton2m365-0.1.0/tests/snapshots/sample-export.txt +0 -0
- proton2m365-0.1.0/tests/snapshots/scan.txt +34 -0
- proton2m365-0.1.0/tests/snapshots/status.txt +5 -0
- proton2m365-0.1.0/tests/test_auth.py +237 -0
- proton2m365-0.1.0/tests/test_check_publish.py +199 -0
- proton2m365-0.1.0/tests/test_cli_safety.py +218 -0
- proton2m365-0.1.0/tests/test_content.py +120 -0
- proton2m365-0.1.0/tests/test_docs.py +31 -0
- proton2m365-0.1.0/tests/test_doctor.py +160 -0
- proton2m365-0.1.0/tests/test_graph_store.py +251 -0
- proton2m365-0.1.0/tests/test_homebrew.py +47 -0
- proton2m365-0.1.0/tests/test_imap_store.py +542 -0
- proton2m365-0.1.0/tests/test_lease.py +96 -0
- proton2m365-0.1.0/tests/test_locks.py +45 -0
- proton2m365-0.1.0/tests/test_mapping.py +73 -0
- proton2m365-0.1.0/tests/test_pipeline.py +1218 -0
- proton2m365-0.1.0/tests/test_plan.py +289 -0
- proton2m365-0.1.0/tests/test_progress.py +66 -0
- proton2m365-0.1.0/tests/test_proton_export.py +346 -0
- proton2m365-0.1.0/tests/test_snapshots.py +77 -0
- proton2m365-0.1.0/tests/test_state.py +334 -0
- proton2m365-0.1.0/tests/test_status.py +73 -0
- proton2m365-0.1.0/tests/test_targets.py +46 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
.gstack/
|
|
3
|
+
.pytest_cache/
|
|
4
|
+
.venv/
|
|
5
|
+
*.egg-info/
|
|
6
|
+
*.log
|
|
7
|
+
# migration data: never commit (scripts/publish-files.txt has the same never-list)
|
|
8
|
+
*.sqlite*
|
|
9
|
+
*.bak-v*
|
|
10
|
+
*.receipt.*
|
|
11
|
+
verify-report*.csv
|
|
12
|
+
token_cache*
|
|
13
|
+
upload-probe.log
|
|
14
|
+
upload-trial.log
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to proton2m365. Versions follow the git tags (`vX.Y.Z`).
|
|
4
|
+
|
|
5
|
+
## v0.1.0 (2026-11-07)
|
|
6
|
+
|
|
7
|
+
First public release. Microsoft 365 work and school accounts are supported; Outlook.com and Microsoft 365
|
|
8
|
+
Personal/Family are experimental.
|
|
9
|
+
|
|
10
|
+
- Move a Proton Mail Export Tool backup into Microsoft 365 over IMAP and Microsoft Graph: received dates, read
|
|
11
|
+
state, folders (from Proton's metadata) and labels as categories are kept; drafts, Trash and Spam are left out
|
|
12
|
+
unless included; stopping and resuming never uploads a message twice.
|
|
13
|
+
- `migrate EXPORT` runs the whole thing in one resumable command; `scan`, `plan`, `upload`, `tag`, `verify` and
|
|
14
|
+
`prune` remain available one at a time, each ending with a `Next:` line.
|
|
15
|
+
- `verify` ends with one outcome (PASSED, SAMPLE OK, INCOMPLETE, FAILED) and writes a receipt
|
|
16
|
+
(`.receipt.html`, `.txt`, `.json`) that accounts for every export file; `--all` also finds duplicate copies,
|
|
17
|
+
and `--content` compares text, attachments, HTML text and links with the export.
|
|
18
|
+
- Bring your own sign-in app (docs/setup-own-app.md); device-code sign-in over SSH; each state file is bound to
|
|
19
|
+
one mailbox; a lease in the mailbox keeps two computers from uploading into it at once.
|
|
20
|
+
- `plan --exclude-label`, `--exclude-file`, `--map-folder` and `--map-label`, saved and reused by a re-plan.
|
|
21
|
+
- Adaptive upload pacing, `selftest`, `doctor --issue`, `sample-export`, `--json` on every command, stable
|
|
22
|
+
`P2M-` error codes and exit codes, and tested on Linux, macOS and Windows.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jonathan Cilley
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: proton2m365
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Move a Proton Mail Export Tool backup into Microsoft 365 or Outlook.com, with a receipt for every message.
|
|
5
|
+
Project-URL: Homepage, https://github.com/jonathan-opmodi/proton2m365
|
|
6
|
+
Project-URL: Documentation, https://github.com/jonathan-opmodi/proton2m365#readme
|
|
7
|
+
Project-URL: Issues, https://github.com/jonathan-opmodi/proton2m365/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/jonathan-opmodi/proton2m365/blob/main/CHANGELOG.md
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: email migration,eml,imap,microsoft 365,outlook,proton,proton mail
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
15
|
+
Classifier: Intended Audience :: System Administrators
|
|
16
|
+
Classifier: Operating System :: MacOS
|
|
17
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
18
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Communications :: Email
|
|
23
|
+
Classifier: Topic :: Utilities
|
|
24
|
+
Requires-Python: >=3.12
|
|
25
|
+
Requires-Dist: httpx>=0.28.1
|
|
26
|
+
Requires-Dist: imapclient>=4.1.0
|
|
27
|
+
Requires-Dist: msal-extensions>=1.3.1
|
|
28
|
+
Requires-Dist: msal>=1.39.0
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# proton2m365
|
|
32
|
+
|
|
33
|
+
Move a **Proton Mail Export Tool** backup into **Microsoft 365** or **Outlook.com**, without Proton Mail
|
|
34
|
+
Bridge or a paid Proton plan, and get a receipt that accounts for every message.
|
|
35
|
+
|
|
36
|
+
- Keeps each message's original received date and read/unread state.
|
|
37
|
+
- Puts mail in the right folder (Inbox, Sent Items, Archive, your own folders) using Proton's metadata.
|
|
38
|
+
- Turns Proton labels into Outlook categories, and stars into a category or a flag.
|
|
39
|
+
- Can stop and resume at any time without uploading anything twice.
|
|
40
|
+
- Ends with `verify`: one outcome (PASSED, SAMPLE OK, INCOMPLETE or FAILED) and a receipt file listing every
|
|
41
|
+
export file as migrated, intentionally skipped (and why), or unresolved.
|
|
42
|
+
|
|
43
|
+
Status: v0.1. Microsoft 365 work and school accounts are tested live. **Outlook.com and Microsoft 365
|
|
44
|
+
Personal/Family are experimental** until the live checks in [docs/how-it-works.md](docs/how-it-works.md#outlookcom)
|
|
45
|
+
have passed. Support is best effort through GitHub issues.
|
|
46
|
+
|
|
47
|
+
## Prerequisites
|
|
48
|
+
|
|
49
|
+
- Python 3.12 or newer, and either [pipx](https://pipx.pypa.io) or [uv](https://docs.astral.sh/uv/).
|
|
50
|
+
- macOS: `brew install pipx` (or `brew install uv`)
|
|
51
|
+
- Windows: install Python from python.org, then `py -m pip install --user pipx` (or `winget install astral-sh.uv`)
|
|
52
|
+
- Linux: your distribution's `pipx` package (or the uv installer)
|
|
53
|
+
- A Proton Mail export made with Proton's **Export Tool** (the folder that contains `labels.json` and pairs of
|
|
54
|
+
`.eml` and `.metadata.json` files). Unzip it first.
|
|
55
|
+
- The mailbox you are moving into: a Microsoft 365 work or school account, or an Outlook.com account.
|
|
56
|
+
- **Your own sign-in app** for migrating (not needed to try it). proton2m365 does not ship one, so your mail is only
|
|
57
|
+
ever reached through an app you control. Registering it is a one-time setup, see
|
|
58
|
+
[docs/setup-own-app.md](docs/setup-own-app.md):
|
|
59
|
+
- work or school account: an administrator of your organization registers it, about 10 minutes;
|
|
60
|
+
- personal account (Outlook.com, Microsoft 365 Family): Microsoft lets only organizations register apps, so
|
|
61
|
+
you first create a free Azure account (it asks for a card to verify you; the app itself costs nothing),
|
|
62
|
+
about 20 minutes.
|
|
63
|
+
|
|
64
|
+
## Try it (no sign-in, nothing written to any mailbox)
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
pipx install proton2m365
|
|
68
|
+
proton2m365 sample-export ./sample
|
|
69
|
+
proton2m365 scan ./sample
|
|
70
|
+
proton2m365 plan --dry-run ./sample
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
With uv instead of pipx, run each command as `uvx proton2m365 ...` and skip the install line.
|
|
74
|
+
|
|
75
|
+
`plan --dry-run` shows what would be uploaded: 34 of the sample's 40 messages, which folders they would go
|
|
76
|
+
to, which labels become categories, and the 6 that are skipped and why. Try it on your own export next; it
|
|
77
|
+
still writes nothing anywhere.
|
|
78
|
+
|
|
79
|
+
## Migrate your export
|
|
80
|
+
|
|
81
|
+
First register your sign-in app and put its client ID and tenant in `~/.config/proton2m365/config.toml`
|
|
82
|
+
([docs/setup-own-app.md](docs/setup-own-app.md)). Then:
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
proton2m365 selftest # live checks with synthetic messages; cleans up after itself
|
|
86
|
+
proton2m365 scan /path/to/export
|
|
87
|
+
proton2m365 plan /path/to/export # writes ./state.sqlite; add --exclude-label "Name" to leave mail out
|
|
88
|
+
proton2m365 upload # hours for a large export; safe to stop and run again
|
|
89
|
+
proton2m365 tag # applies categories and flags
|
|
90
|
+
proton2m365 verify --all # the outcome, and the receipt
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Or all of it in one command, which asks before uploading and can be run again to continue where it stopped:
|
|
94
|
+
|
|
95
|
+
```sh
|
|
96
|
+
proton2m365 migrate /path/to/export # add --exclude-label "Name" to leave mail out
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Every command ends with a `Next:` line naming the command to run next. `verify` writes three receipt files next
|
|
100
|
+
to the state file: `state.receipt.html` (open it in a browser, or print it), `state.receipt.txt` and the full
|
|
101
|
+
per-message `state.receipt.json`. Keep your Proton export until you have read the receipt.
|
|
102
|
+
|
|
103
|
+
### Signing in
|
|
104
|
+
|
|
105
|
+
The first online command opens your browser. Choose the mailbox you are moving **into**, and approve the
|
|
106
|
+
permissions your app asks for. Over SSH or without a display, proton2m365 uses a device code instead (it may ask twice:
|
|
107
|
+
once for Microsoft Graph, once for IMAP). Each state file remembers its mailbox and refuses any other.
|
|
108
|
+
|
|
109
|
+
## What you will see in Outlook
|
|
110
|
+
|
|
111
|
+
| From Proton | In Outlook |
|
|
112
|
+
|---|---|
|
|
113
|
+
| Inbox, Sent, Archive | Inbox, Sent Items, Archive |
|
|
114
|
+
| your folders (`Projects/Alpha`) | the same folders, created for you |
|
|
115
|
+
| labels | categories (with the nearest Outlook color) |
|
|
116
|
+
| starred | a "Starred" category, or a flag with `plan --starred-as-flag` |
|
|
117
|
+
| received date, read/unread | kept |
|
|
118
|
+
| drafts, Trash, Spam | skipped (Trash and Spam can be included with `--include-trash`, `--include-spam`) |
|
|
119
|
+
|
|
120
|
+
Want it somewhere else? `plan --map-folder "Projects=Clients"` moves a folder (and its subfolders), and
|
|
121
|
+
`--map-label "Work=Office"` renames a category (`"Work="` drops it). The same mappings can live in
|
|
122
|
+
`~/.config/proton2m365/config.toml` under `[map.folders]` and `[map.labels]`.
|
|
123
|
+
|
|
124
|
+
Each uploaded message carries an added `X-Proton-Id` header, which is how proton2m365 finds it again. While
|
|
125
|
+
`upload` or `prune` runs, a folder `_proton2m365_lease` holds one small message so that a second computer does
|
|
126
|
+
not upload the same mail; it is emptied when the run ends.
|
|
127
|
+
|
|
128
|
+
## What is not preserved
|
|
129
|
+
|
|
130
|
+
Proton's conversation grouping, snooze and scheduled sends, and labels you exclude. Messages larger than the
|
|
131
|
+
mailbox's size limit (35 MB by default) are skipped and listed. `verify` checks dates, read state, flags,
|
|
132
|
+
categories, drafts and folders; `verify --content` also downloads the checked messages and compares their text,
|
|
133
|
+
attachments, HTML text and links with the export (`verify --all --content` downloads the whole mailbox). The
|
|
134
|
+
receipt says which of these were done.
|
|
135
|
+
|
|
136
|
+
## If it stops
|
|
137
|
+
|
|
138
|
+
Run the same command again: nothing that already arrived is sent twice. `proton2m365 status` shows where
|
|
139
|
+
things stand without signing in. To start over, delete the state file (and its `.receipt.*` files) and run
|
|
140
|
+
`plan` again; mail already uploaded is detected by its `X-Proton-Id` header.
|
|
141
|
+
|
|
142
|
+
### Exit codes
|
|
143
|
+
|
|
144
|
+
| Code | Meaning |
|
|
145
|
+
|---|---|
|
|
146
|
+
| 0 | done (verify: PASSED) |
|
|
147
|
+
| 1 | unexpected error: a crash log was written; attach `proton2m365 doctor --issue` output to an issue |
|
|
148
|
+
| 2 | usage, configuration, sign-in or connection error (the message starts with a `P2M-` code, see [docs/errors.md](docs/errors.md)) |
|
|
149
|
+
| 3 | mailbox full |
|
|
150
|
+
| 10 | verify: SAMPLE OK, some messages not checked yet (`verify --all`) |
|
|
151
|
+
| 11 | incomplete: messages still need work (the `Next:` line says which command) |
|
|
152
|
+
| 12 | failed: a check did not match, or a selftest check failed |
|
|
153
|
+
| 13 | interrupted: run the same command again to resume |
|
|
154
|
+
| 14 | verify would have PASSED, but its receipt could not be saved |
|
|
155
|
+
|
|
156
|
+
## Why not Thunderbird or a paid EML converter?
|
|
157
|
+
|
|
158
|
+
They can move `.eml` files, and that may be all you need. As far as we know none of them reads Proton's
|
|
159
|
+
metadata files, so Proton's folders, labels and read state are not carried over, and nothing tells you
|
|
160
|
+
afterwards whether every message arrived. proton2m365 exists for those things.
|
|
161
|
+
Tools built on Proton Mail Bridge (imapsync and others) need a paid Proton plan and a running Bridge.
|
|
162
|
+
|
|
163
|
+
## More
|
|
164
|
+
|
|
165
|
+
- [How it works](docs/how-it-works.md): export format, folder routing, why IMAP plus Graph
|
|
166
|
+
- [Errors](docs/errors.md): every `P2M-` code and what to do
|
|
167
|
+
- [Upgrading](docs/upgrading.md): pinning a version, resuming across versions
|
|
168
|
+
- [Using your own Entra app](docs/setup-own-app.md)
|
|
169
|
+
- [Security](SECURITY.md): what stays on your computer
|
|
170
|
+
- [Contributing](CONTRIBUTING.md)
|
|
171
|
+
|
|
172
|
+
MIT licensed. Not affiliated with Proton AG or Microsoft.
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# proton2m365
|
|
2
|
+
|
|
3
|
+
Move a **Proton Mail Export Tool** backup into **Microsoft 365** or **Outlook.com**, without Proton Mail
|
|
4
|
+
Bridge or a paid Proton plan, and get a receipt that accounts for every message.
|
|
5
|
+
|
|
6
|
+
- Keeps each message's original received date and read/unread state.
|
|
7
|
+
- Puts mail in the right folder (Inbox, Sent Items, Archive, your own folders) using Proton's metadata.
|
|
8
|
+
- Turns Proton labels into Outlook categories, and stars into a category or a flag.
|
|
9
|
+
- Can stop and resume at any time without uploading anything twice.
|
|
10
|
+
- Ends with `verify`: one outcome (PASSED, SAMPLE OK, INCOMPLETE or FAILED) and a receipt file listing every
|
|
11
|
+
export file as migrated, intentionally skipped (and why), or unresolved.
|
|
12
|
+
|
|
13
|
+
Status: v0.1. Microsoft 365 work and school accounts are tested live. **Outlook.com and Microsoft 365
|
|
14
|
+
Personal/Family are experimental** until the live checks in [docs/how-it-works.md](docs/how-it-works.md#outlookcom)
|
|
15
|
+
have passed. Support is best effort through GitHub issues.
|
|
16
|
+
|
|
17
|
+
## Prerequisites
|
|
18
|
+
|
|
19
|
+
- Python 3.12 or newer, and either [pipx](https://pipx.pypa.io) or [uv](https://docs.astral.sh/uv/).
|
|
20
|
+
- macOS: `brew install pipx` (or `brew install uv`)
|
|
21
|
+
- Windows: install Python from python.org, then `py -m pip install --user pipx` (or `winget install astral-sh.uv`)
|
|
22
|
+
- Linux: your distribution's `pipx` package (or the uv installer)
|
|
23
|
+
- A Proton Mail export made with Proton's **Export Tool** (the folder that contains `labels.json` and pairs of
|
|
24
|
+
`.eml` and `.metadata.json` files). Unzip it first.
|
|
25
|
+
- The mailbox you are moving into: a Microsoft 365 work or school account, or an Outlook.com account.
|
|
26
|
+
- **Your own sign-in app** for migrating (not needed to try it). proton2m365 does not ship one, so your mail is only
|
|
27
|
+
ever reached through an app you control. Registering it is a one-time setup, see
|
|
28
|
+
[docs/setup-own-app.md](docs/setup-own-app.md):
|
|
29
|
+
- work or school account: an administrator of your organization registers it, about 10 minutes;
|
|
30
|
+
- personal account (Outlook.com, Microsoft 365 Family): Microsoft lets only organizations register apps, so
|
|
31
|
+
you first create a free Azure account (it asks for a card to verify you; the app itself costs nothing),
|
|
32
|
+
about 20 minutes.
|
|
33
|
+
|
|
34
|
+
## Try it (no sign-in, nothing written to any mailbox)
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
pipx install proton2m365
|
|
38
|
+
proton2m365 sample-export ./sample
|
|
39
|
+
proton2m365 scan ./sample
|
|
40
|
+
proton2m365 plan --dry-run ./sample
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
With uv instead of pipx, run each command as `uvx proton2m365 ...` and skip the install line.
|
|
44
|
+
|
|
45
|
+
`plan --dry-run` shows what would be uploaded: 34 of the sample's 40 messages, which folders they would go
|
|
46
|
+
to, which labels become categories, and the 6 that are skipped and why. Try it on your own export next; it
|
|
47
|
+
still writes nothing anywhere.
|
|
48
|
+
|
|
49
|
+
## Migrate your export
|
|
50
|
+
|
|
51
|
+
First register your sign-in app and put its client ID and tenant in `~/.config/proton2m365/config.toml`
|
|
52
|
+
([docs/setup-own-app.md](docs/setup-own-app.md)). Then:
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
proton2m365 selftest # live checks with synthetic messages; cleans up after itself
|
|
56
|
+
proton2m365 scan /path/to/export
|
|
57
|
+
proton2m365 plan /path/to/export # writes ./state.sqlite; add --exclude-label "Name" to leave mail out
|
|
58
|
+
proton2m365 upload # hours for a large export; safe to stop and run again
|
|
59
|
+
proton2m365 tag # applies categories and flags
|
|
60
|
+
proton2m365 verify --all # the outcome, and the receipt
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Or all of it in one command, which asks before uploading and can be run again to continue where it stopped:
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
proton2m365 migrate /path/to/export # add --exclude-label "Name" to leave mail out
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Every command ends with a `Next:` line naming the command to run next. `verify` writes three receipt files next
|
|
70
|
+
to the state file: `state.receipt.html` (open it in a browser, or print it), `state.receipt.txt` and the full
|
|
71
|
+
per-message `state.receipt.json`. Keep your Proton export until you have read the receipt.
|
|
72
|
+
|
|
73
|
+
### Signing in
|
|
74
|
+
|
|
75
|
+
The first online command opens your browser. Choose the mailbox you are moving **into**, and approve the
|
|
76
|
+
permissions your app asks for. Over SSH or without a display, proton2m365 uses a device code instead (it may ask twice:
|
|
77
|
+
once for Microsoft Graph, once for IMAP). Each state file remembers its mailbox and refuses any other.
|
|
78
|
+
|
|
79
|
+
## What you will see in Outlook
|
|
80
|
+
|
|
81
|
+
| From Proton | In Outlook |
|
|
82
|
+
|---|---|
|
|
83
|
+
| Inbox, Sent, Archive | Inbox, Sent Items, Archive |
|
|
84
|
+
| your folders (`Projects/Alpha`) | the same folders, created for you |
|
|
85
|
+
| labels | categories (with the nearest Outlook color) |
|
|
86
|
+
| starred | a "Starred" category, or a flag with `plan --starred-as-flag` |
|
|
87
|
+
| received date, read/unread | kept |
|
|
88
|
+
| drafts, Trash, Spam | skipped (Trash and Spam can be included with `--include-trash`, `--include-spam`) |
|
|
89
|
+
|
|
90
|
+
Want it somewhere else? `plan --map-folder "Projects=Clients"` moves a folder (and its subfolders), and
|
|
91
|
+
`--map-label "Work=Office"` renames a category (`"Work="` drops it). The same mappings can live in
|
|
92
|
+
`~/.config/proton2m365/config.toml` under `[map.folders]` and `[map.labels]`.
|
|
93
|
+
|
|
94
|
+
Each uploaded message carries an added `X-Proton-Id` header, which is how proton2m365 finds it again. While
|
|
95
|
+
`upload` or `prune` runs, a folder `_proton2m365_lease` holds one small message so that a second computer does
|
|
96
|
+
not upload the same mail; it is emptied when the run ends.
|
|
97
|
+
|
|
98
|
+
## What is not preserved
|
|
99
|
+
|
|
100
|
+
Proton's conversation grouping, snooze and scheduled sends, and labels you exclude. Messages larger than the
|
|
101
|
+
mailbox's size limit (35 MB by default) are skipped and listed. `verify` checks dates, read state, flags,
|
|
102
|
+
categories, drafts and folders; `verify --content` also downloads the checked messages and compares their text,
|
|
103
|
+
attachments, HTML text and links with the export (`verify --all --content` downloads the whole mailbox). The
|
|
104
|
+
receipt says which of these were done.
|
|
105
|
+
|
|
106
|
+
## If it stops
|
|
107
|
+
|
|
108
|
+
Run the same command again: nothing that already arrived is sent twice. `proton2m365 status` shows where
|
|
109
|
+
things stand without signing in. To start over, delete the state file (and its `.receipt.*` files) and run
|
|
110
|
+
`plan` again; mail already uploaded is detected by its `X-Proton-Id` header.
|
|
111
|
+
|
|
112
|
+
### Exit codes
|
|
113
|
+
|
|
114
|
+
| Code | Meaning |
|
|
115
|
+
|---|---|
|
|
116
|
+
| 0 | done (verify: PASSED) |
|
|
117
|
+
| 1 | unexpected error: a crash log was written; attach `proton2m365 doctor --issue` output to an issue |
|
|
118
|
+
| 2 | usage, configuration, sign-in or connection error (the message starts with a `P2M-` code, see [docs/errors.md](docs/errors.md)) |
|
|
119
|
+
| 3 | mailbox full |
|
|
120
|
+
| 10 | verify: SAMPLE OK, some messages not checked yet (`verify --all`) |
|
|
121
|
+
| 11 | incomplete: messages still need work (the `Next:` line says which command) |
|
|
122
|
+
| 12 | failed: a check did not match, or a selftest check failed |
|
|
123
|
+
| 13 | interrupted: run the same command again to resume |
|
|
124
|
+
| 14 | verify would have PASSED, but its receipt could not be saved |
|
|
125
|
+
|
|
126
|
+
## Why not Thunderbird or a paid EML converter?
|
|
127
|
+
|
|
128
|
+
They can move `.eml` files, and that may be all you need. As far as we know none of them reads Proton's
|
|
129
|
+
metadata files, so Proton's folders, labels and read state are not carried over, and nothing tells you
|
|
130
|
+
afterwards whether every message arrived. proton2m365 exists for those things.
|
|
131
|
+
Tools built on Proton Mail Bridge (imapsync and others) need a paid Proton plan and a running Bridge.
|
|
132
|
+
|
|
133
|
+
## More
|
|
134
|
+
|
|
135
|
+
- [How it works](docs/how-it-works.md): export format, folder routing, why IMAP plus Graph
|
|
136
|
+
- [Errors](docs/errors.md): every `P2M-` code and what to do
|
|
137
|
+
- [Upgrading](docs/upgrading.md): pinning a version, resuming across versions
|
|
138
|
+
- [Using your own Entra app](docs/setup-own-app.md)
|
|
139
|
+
- [Security](SECURITY.md): what stays on your computer
|
|
140
|
+
- [Contributing](CONTRIBUTING.md)
|
|
141
|
+
|
|
142
|
+
MIT licensed. Not affiliated with Proton AG or Microsoft.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
## What leaves your computer
|
|
4
|
+
|
|
5
|
+
Only calls to Microsoft: sign-in (login.microsoftonline.com), Microsoft Graph and Outlook IMAP. proton2m365
|
|
6
|
+
sends no telemetry and talks to no other service.
|
|
7
|
+
|
|
8
|
+
## What stays on your computer, and holds mailbox data
|
|
9
|
+
|
|
10
|
+
| File | Holds | Protection |
|
|
11
|
+
|---|---|---|
|
|
12
|
+
| token cache (`~/.config/proton2m365/token_cache.bin`, or the OS secret store) | sign-in tokens | macOS Keychain, Windows DPAPI or Linux libsecret; otherwise an owner-only file (0600 on macOS and Linux) |
|
|
13
|
+
| state file (`state.sqlite` by default) and its `.bak-v*` upgrade backups | message IDs, folder and category names, the mailbox address | your file permissions |
|
|
14
|
+
| `<state>.receipt.json`, `.receipt.txt`, `.receipt.html`, `verify-report.csv` | the same, per message | owner-only (0600) where the OS supports it; on Windows they inherit your profile's permissions |
|
|
15
|
+
| `crash-*.log` | a traceback, which can include addresses | owner-only (0600) |
|
|
16
|
+
|
|
17
|
+
`doctor --issue` prints a report with names and addresses replaced by hashes; attach that to issues, never the
|
|
18
|
+
files above.
|
|
19
|
+
|
|
20
|
+
## Reporting a vulnerability
|
|
21
|
+
|
|
22
|
+
Please use GitHub's private vulnerability reporting on this repository rather than a public issue. Support is
|
|
23
|
+
best effort; expect a reply within a couple of weeks.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "proton2m365"
|
|
3
|
+
dynamic = ["version"]
|
|
4
|
+
description = "Move a Proton Mail Export Tool backup into Microsoft 365 or Outlook.com, with a receipt for every message."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
requires-python = ">=3.12"
|
|
9
|
+
keywords = ["proton", "proton mail", "microsoft 365", "outlook", "email migration", "imap", "eml"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 4 - Beta",
|
|
12
|
+
"Environment :: Console",
|
|
13
|
+
"Intended Audience :: End Users/Desktop",
|
|
14
|
+
"Intended Audience :: System Administrators",
|
|
15
|
+
"Operating System :: MacOS",
|
|
16
|
+
"Operating System :: Microsoft :: Windows",
|
|
17
|
+
"Operating System :: POSIX :: Linux",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
"Topic :: Communications :: Email",
|
|
22
|
+
"Topic :: Utilities",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"httpx>=0.28.1",
|
|
26
|
+
"imapclient>=4.1.0",
|
|
27
|
+
"msal>=1.39.0",
|
|
28
|
+
"msal-extensions>=1.3.1",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://github.com/jonathan-opmodi/proton2m365"
|
|
33
|
+
Documentation = "https://github.com/jonathan-opmodi/proton2m365#readme"
|
|
34
|
+
Issues = "https://github.com/jonathan-opmodi/proton2m365/issues"
|
|
35
|
+
Changelog = "https://github.com/jonathan-opmodi/proton2m365/blob/main/CHANGELOG.md"
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
proton2m365 = "proton2m365.cli:main"
|
|
39
|
+
|
|
40
|
+
[dependency-groups]
|
|
41
|
+
dev = ["pytest>=8"]
|
|
42
|
+
|
|
43
|
+
[build-system]
|
|
44
|
+
requires = ["hatchling", "hatch-vcs"]
|
|
45
|
+
build-backend = "hatchling.build"
|
|
46
|
+
|
|
47
|
+
[tool.hatch.version]
|
|
48
|
+
source = "vcs"
|
|
49
|
+
# Builds without git metadata (an sdist, a shallow checkout) still get a version.
|
|
50
|
+
fallback-version = "0.0.0+unknown"
|
|
51
|
+
|
|
52
|
+
[tool.hatch.build.targets.sdist]
|
|
53
|
+
include = ["src", "tests", "README.md", "LICENSE", "CHANGELOG.md", "SECURITY.md", "pyproject.toml"]
|
|
54
|
+
|
|
55
|
+
[tool.hatch.build.targets.wheel]
|
|
56
|
+
packages = ["src/proton2m365"]
|
|
57
|
+
|
|
58
|
+
[tool.pytest.ini_options]
|
|
59
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""proton2m365: move a Proton Mail export into a Microsoft 365 mailbox."""
|