CLI to convert filesystem-based mailboxes, written in Rust.
Caution
m2m is in active development and shipped as v0.x.x. Expect breaking changes between releases until stabilisation.
- Convert from/to Maildir specs, Maildir++ specs and m2dir specs filesystem stores.
- Parallel copy loop sized to
--workers(default = available parallelism, capped at 16). - Idempotent re-runs via
--skip-existing, which dedup-matches byMessage-IDthen by source id. - Dry-run mode (
--dry-run) lists what would be touched without creating or modifying any file. - Inbox detection via
--inbox <NAME>(defaultINBOX): case-insensitively identifies the source inbox folder and renames it to the canonical alias on the target. Maildir++ root surfaces under the same alias. - Custom keyword round-trip between Maildir-family stores via two configurable side channels: the per-folder
dovecot-keywordsslot file (Dovecot / mbsync convention) and a body header (X-KeywordsorX-Label). - Header stripping on read (
--strip-headers) to drop legacy markers (X-Mozilla-Status,X-Status, ...). - Maildir++ layout awareness: the
maildir++subcommand reads / writes dotted folder siblings (.Work.Foo/) and reports them as logicalWork/Foomailboxes. - Dovecot fs-layout via
--fs-layouton themaildirsubcommand: nested subfolders (Work/Foo/) instead of flat dotted siblings.
Tip
m2m is written in Rust and uses cargo features to gate backend support. The default feature set is declared in Cargo.toml.
cargo install --locked --git https://github.com/pimalaya/m2m.git
If you have the Flakes feature enabled:
nix profile install github:pimalaya/m2m
Or run without installing:
nix run github:pimalaya/m2m
git clone https://github.com/pimalaya/m2m
cd m2m
nix run
The grammar is a chain of two subcommands, one per side, each carrying its own positional <PATH> and per-format flags:
m2m [SHARED-FLAGS] <SOURCE-CMD> <PATH> [SOURCE-FLAGS] <TARGET-CMD> <PATH> [TARGET-FLAGS]
Backends accepted as both source and target: maildir, maildir++, m2dir.
Examples:
m2m maildir ~/Mail m2dir ~/.Mail/example
m2m --workers 8 --skip-existing maildir ~/Mail --dovecot-keywords --keywords-header x-keywords m2dir ~/.Mail
m2m m2dir ~/.Mail maildir++ ~/Maildir
m2m --dry-run maildir ~/Mail m2dir /mnt/backup
| Flag | Purpose |
|---|---|
-w, --workers <N> |
Worker threads (default = available parallelism, capped at 16). |
--inbox <NAME> |
Canonical inbox folder name (default INBOX); detected case-insensitively on the source, used as-is on the target. |
--include-mailbox <NAME> |
Process only these mailboxes (repeatable, case-insensitive). |
--exclude-mailbox <NAME> |
Skip these mailboxes (repeatable, case-insensitive). |
--skip-existing |
Skip messages already present on the target. |
--dry-run |
List the conversion plan without writing anything. |
--json |
Machine output instead of TTY spinner. |
--log-level <off,error,warn,info,debug,trace> |
Filter log output. |
maildir accepts four optional flags on either side, maildir++ accepts three (no --fs-layout), m2dir accepts none.
| Flag | What it does |
|---|---|
--dovecot-keywords |
Reads / writes the per-folder dovecot-keywords sidecar (Dovecot / mbsync convention). Custom keywords in filenames (a..z) round-trip through the slot table. |
--keywords-header <x-keywords|x-label> |
Mirrors custom keywords into the chosen body header. x-keywords follows the OfflineIMAP / mbsync convention (comma-separated); x-label follows the mutt / notmuch convention (space-separated). |
--strip-headers <CSV> |
Drops these header names from each message on read; useful to clean Mozilla / Pegasus markers (X-Mozilla-Status, X-Status, ...). |
--fs-layout |
Dovecot fs layout: subfolders are nested filesystem directories (Work/Foo/) instead of flat dotted siblings. Only on the maildir subcommand. |
When both sides set --dovecot-keywords and the dovecot slot table grows past 26 entries, the 27th keyword and onwards are dropped with a warn! log line per message; the conversion continues.
- Chat on Matrix
- News on Mastodon or RSS
- Mail at pimalaya.org@posteo.net
Special thanks to the NLnet foundation and the European Commission that have been financially supporting the project for years:
- 2022 → 2023: NGI Assure
- 2023 → 2024: NGI Zero Entrust
- 2024 → 2026: NGI Zero Core
- 2027 in preparation…
If you appreciate the project, feel free to donate using one of the following providers:
