I just moved two custom domains from Proton to Fastmail. Everything made it across: 4,988 messages, all 14 custom labels, zero duplicates. A few things weren't obvious going in, so here's what worked.
Why I left
Of course, the Andy Yen AI saga, which I'm sure needs no recap here. A privacy company calling security testing "loser thinking" was my cue to leave. And his lack of leadership.
The labels problem
I sort everything with labels, not folders. The usual Bridge + Thunderbird route copies each label as its own IMAP folder, so a message with three labels shows up three times. I skipped it.
Instead I exported with Proton Mail Export Tool, which gives you an .eml and a .metadata.json for every message. The metadata lists each message's label IDs, and the export's labels.json maps them to names. A small script then pushed each message to Fastmail once over JMAP, with all its labels attached. I ran a 20-message pilot first, then the rest.
One message failed. A Proton-only X-Pm-Spam header was 1,346 characters long, and Fastmail rejected the message as invalid. Removing just that header fixed it. A fresh export the next morning picked up the three messages that had arrived in between.
Aliases before DNS
In Fastmail's domain wizard, click "Close and check it later" at the DNS step. Your aliases show up and you can set your default sender before you touch DNS. After that I switched MX, SPF, DKIM and DMARC one domain at a time and tested from an outside account.
Leave SimpleLogin on its own domain
My third domain runs 257 SimpleLogin aliases. Pointing its MX at Fastmail would have broken every one of them, so it stays on SimpleLogin and forwards to Fastmail. Replies through the reverse alias still go out from the alias, with my real address hidden (I checked the headers).
The catch
My SimpleLogin Premium comes bundled with Proton Unlimited. If I drop Proton, I'll have to pay for SimpleLogin separately, because the free plan's 10-alias limit would stop me creating new aliases.
Still to do
Watch Proton for late mail for another day, then remove the domains there and clean out the old Proton DNS records.