← writingJul 2026 · 2 min · CLI · protocols

backupmail: one CLI for IMAP and JMAP mailboxes

Email backup tools tend to assume IMAP and stop there. backupmail is a TUI/CLI that backs up and migrates mailboxes across Gmail, SpaceMail, generic IMAP — and Fastmail over JMAP — from one interface.

IMAP and JMAP are two different worlds

IMAP is stateful and chatty: you SELECT a mailbox, then FETCH ranges, and the server holds your session. JMAP is a modern JSON-over-HTTP protocol — you Email/query and Email/get in batched method calls, stateless, with proper concurrency. Supporting both meant a transport interface that hides the difference:

interface MailBackend {
  listMailboxes(): Promise<Mailbox[]>;
  fetchMessages(box: string, onMsg: (raw: Uint8Array) => void): Promise<void>;
}

The IMAP backend streams BODY[] responses; the JMAP backend pages through Email/query, resolves blob ids, and downloads via the download URL from the session object. Same .eml output either way.

The fiddly parts

  • Session discovery for JMAP — you fetch the session resource first to learn the API/download URLs; nothing is hard-coded.
  • Idempotent resume — backing up 60k messages shouldn’t restart from zero if the connection drops, so progress is checkpointed per mailbox.
  • Rate/throttle — providers will happily throttle you; the pool backs off instead of hammering.

Understanding the protocol shape — stateful streaming vs. batched JSON — is most of the work. Once the transport interface is right, the backup logic is boring, which is exactly what you want for something guarding your email.