# Using inbuxa-migrate The full command reference. The [README](../README.md) says what the tool is and how to install it. ## Commands and global flags ``` inbuxa-migrate [args...] ``` Every command takes a few global flags -- verbosity, worker pool size, retry policy, TLS handling -- besides its own. The ones that matter most: | Flag | Purpose | | --- | --- | | `-j, --threads ` | Worker pool size (default: logical CPUs). | | `--dry-run` | Compute the full plan; perform no writes. | | `-v`, `-vv`, `-vvv` | Increase log verbosity. | | `-q, --quiet` | Warnings and errors only. | | `--max-retries ` | Max retries per request on transient failures (default 5). | | `--allow-invalid-certs` | Accept a self-signed or otherwise invalid certificate from the server named by `--url` (see below). | ### Invalid certificates `--allow-invalid-certs` is for a server with a self-signed certificate, and it applies to that server only: the host in `--url`, whether that is the source of an import or the target of an export. For an Exchange import with no `--url`, it covers the mailbox's own domain and the hosts under it, which is where on-premises Autodiscover looks, and then only the EWS endpoint Autodiscover finds. Every other host is verified as usual, including a host the server redirects to or names for its API, uploads or downloads. The Microsoft and Google sign-in and cloud endpoints are always verified, with or without the flag: a certificate that fails there is an attack or a broken network, never a server to trust. Connections time out rather than wait forever: 30 seconds to connect, 5 minutes for the server's first byte, and 30 minutes to read a whole response. A timed-out request is retried like any other transient failure. Secrets come from the `INBUXA_MIGRATE_*` environment variables or a prompt; see [Credentials](../README.md#credentials). The command line takes them too, but should not be given them. ## Import ``` inbuxa-migrate import [source-args...] ``` Reads a source account into the SQLite `ARCHIVE`, creating it if it is absent. An archive remembers which account filled it; every importer takes `--allow-source-change` to fill it from a different one anyway. ### JMAP ``` inbuxa-migrate import jmap \ --url \ (--auth-basic [--auth-password ] | --auth-bearer [TOKEN]) \ (--account-id | --account-name ) \ [--objects ] \ ``` Imports one JMAP account, from inbuxa, Stalwart or any other JMAP server. `--objects` accepts a comma-separated list of object tokens (`mailbox,email,calendar,calendarevent,addressbook,contactcard,identity,sievescript,participantidentity,filenode`). The default is everything the server advertises. ### IMAP ``` inbuxa-migrate import imap \ --url imap(s)://host[:port] \ (--auth-basic [--auth-password ] | --auth-bearer [TOKEN] --auth-user ) \ [--include ...] [--exclude ...] [--exclude-special ...] \ [--folder ...] [--subscribed-only] [--noautomap] \ [--include-deleted] [--allow-cleartext] [--compress] \ [--fetch-batch ] [--imap-connections <1..8>] \ ``` Imports mail, and only mail, from any IMAP server. Folders are chosen with `--include` and `--exclude` patterns, or by exact name with `--folder`, but not both. `--exclude-special` drops folders by SPECIAL-USE role. ### CalDAV ``` inbuxa-migrate import caldav \ --url \ (--auth-basic [--auth-password ] | --auth-bearer [TOKEN]) \ [--allow-cleartext] [--dav-connections <1..8>] [--multiget-batch ] \ ``` Discovers the user's CalDAV principal (or accepts a URL pointing straight at a calendar-home or calendar), then imports calendars and events. ### CardDAV ``` inbuxa-migrate import carddav \ --url \ (--auth-basic [--auth-password ] | --auth-bearer [TOKEN]) \ [--allow-cleartext] [--dav-connections <1..8>] [--multiget-batch ] \ ``` Same shape as `caldav`, but for address books and contacts. ### WebDAV ``` inbuxa-migrate import webdav \ --url \ (--auth-basic [--auth-password ] | --auth-bearer [TOKEN]) \ [--allow-cleartext] [--dav-connections <1..8>] [--multiget-batch ] \ ``` Imports a plain WebDAV file collection as a JMAP `FileNode` tree. ### ManageSieve ``` inbuxa-migrate import managesieve \ --url sieve(s)://host[:port] \ (--auth-basic [--auth-password ] | --auth-bearer [TOKEN] --auth-user ) \ [--allow-cleartext] \ ``` Imports Sieve scripts only. Each script is stored once by content, and the active one is recorded. ### Maildir ``` inbuxa-migrate import maildir \ [--include ...] [--exclude ...] [--folder ...] \ [--noautomap] [--include-deleted] ``` Reads a local Maildir++ tree: a directory with `cur/`, `new/` and `tmp/`. No network. Folders are chosen as for IMAP. ### Google Takeout ``` inbuxa-migrate import takeout [--noautomap] ``` Finds every `.mbox`, `.ics` and `.vcf` file under a directory and imports it. It is shaped for Google Takeout but reads any such tree. `--noautomap` stops it giving Gmail's system labels their mailbox roles. ### Microsoft Exchange (EWS) ``` inbuxa-migrate import exchange-ews \ [--url ] [--mailbox ] \ [--mailbox-kind primary|archive|public-folders] \ (--auth-basic [--auth-password ] \ | --auth-bearer [TOKEN] [--ews-tenant --ews-client-id \ (--ews-device-code | --ews-client-secret )]) \ [--ews-connections <1..8>] [--ews-getitem-batch ] [--ews-attachment-batch ] \ [--ews-no-syncfolderitems] \ ``` Imports a mailbox from an on-premises Exchange Server through EWS. Without `--url` it uses Autodiscover, and then needs `--mailbox`. It signs in with Basic, with a bearer token acquired beforehand, with OAuth's interactive device-code flow, or with app-only client credentials. For Exchange Online, use `exchange-graph` instead. Microsoft is retiring EWS in Exchange Online: from October 1, 2026 it is blocked unless a tenant administrator sets `EwsEnabled` to `True` and adds the client id to `EwsAllowedAppIDs`, and on April 1, 2027 it is switched off for every tenant. On-premises Exchange Server is not affected. ### Microsoft Exchange (Graph) ``` inbuxa-migrate import exchange-graph \ (--client-id [--tenant ] | --access-token [TOKEN]) \ [--user ] \ [--mailbox-kind primary|archive] \ [--objects mail,calendar,contacts] \ [--event-body-format text|html] \ [--graph-connections <1..16>] [--top <1..1000>] \ ``` Imports a mailbox from Exchange Online through Microsoft Graph. Without `--access-token` it signs in with the interactive device-code flow. `public-folders` is rejected here: Graph does not expose public folders, so they can only be imported with `exchange-ews`, which for Exchange Online is subject to the retirement described above. ## Export ``` inbuxa-migrate export \ --url \ (--auth-basic [--auth-password ] | --auth-bearer [TOKEN]) \ (--account-id | --account-name ) \ [--objects ] [--prune [--yes]] \ ``` Writes `ARCHIVE` into an account on a JMAP server, usually inbuxa. It keeps no state of its own: every run matches the archive against the target afresh. By default it only adds: what the archive holds and the target lacks is created, and anything already on the target is left as it is -- an item that matches is not updated yet, so a change made at the source after the first export does not reach the target on a second one. Email is matched by Message-ID, or without one by sender, subject, date and recipients; where several messages share one, size decides. A message the source kept in several folders -- IMAP and Maildir copies, Gmail labels -- is written once, in all of them, and a later run adds any folder it is still missing on the target. `--prune` also deletes what is on the target and not in the archive. It asks first; `--yes` answers for it, for scripts. Export speaks JMAP only. ## Inspect ``` inbuxa-migrate inspect [TYPE] [--limit ] [--offset ] ``` Read-only dump of a local archive. This command never opens a network connection and never writes to the archive. - Omit `TYPE` for a per-type summary (counts of every object kind plus blob storage stats). - Pass an object type to dump it: `mailbox`, `email`, `identity`, `sievescript`, `addressbook`, `contactcard`, `calendar`, `calendarevent`, `participantidentity`, `filenode`. - `mailbox` and `filenode` render as a tree (`--limit`/`--offset` are ignored); all other types use a paginated list and respect `--limit` and `--offset`. ## Live tests The tests against real servers -- a Stalwart server as a JMAP source, and Dovecot, Cyrus, Radicale, Baikal and Apache `mod_dav` -- are marked `#[ignore]`. Each test binary boots its own throwaway container through `testcontainers`, pulling the image on first use, so Docker must be running (`docker info` succeeds) before they start. Run them one binary at a time, always with one test thread: ```sh cargo test --test sync_jmap -- --ignored --test-threads=1 # live JMAP import/export/convergence/prune cargo test --test sync_imap -- --ignored --test-threads=1 cargo test --test sync_managesieve -- --ignored --test-threads=1 cargo test --test sync_maildir -- --ignored --test-threads=1 cargo test --test sync_caldav -- --ignored --test-threads=1 cargo test --test sync_carddav -- --ignored --test-threads=1 cargo test --test sync_webdav -- --ignored --test-threads=1 cargo test --test live_stalwart -- --ignored --test-threads=1 cargo test --test seed_smoke -- --ignored --test-threads=1 cargo test --test seed_only -- --ignored --test-threads=1 # Third-party-server tests (one container each): cargo test --test integration_radicale -- --ignored --test-threads=1 cargo test --test integration_baikal -- --ignored --test-threads=1 cargo test --test integration_webdav -- --ignored --test-threads=1 cargo test --test integration_dovecot -- --ignored --test-threads=1 cargo test --test integration_cyrus -- --ignored --test-threads=1 # Slow tests cargo test --test mock_jmap -- --ignored ``` One thread is required, not advised. The tests in a binary share its container, and each one creates and removes the same disposable domain, `inbuxa-migrate.org`, and opens its archive with SQLite's `EXCLUSIVE` lock. Separate binaries each boot their own container on their own ports, so running them one after another is safe.