Skip to content

Backups: backup, count, meta, verify

mailcellar backup                  # every account, incremental
mailcellar backup personal         # one account
mailcellar count                   # compare each mailbox with its backup
mailcellar backup personal --full  # list every message instead of using history
mailcellar backup --unattended     # for scheduled runs: never opens a browser
mailcellar backup personal --prune  # then run 'mailcellar forget --deleted' (for a release)
mailcellar meta                    # fill meta.sqlite for older mail
mailcellar verify                  # are the backup's files still intact?

Exit status (for scripts and schedulers): 0 when everything worked. Otherwise 1, in any of these cases: - an account failed (with several accounts, the others still run); - downloads failed (the next run fetches them), or the forget that --prune runs refused (its 10% guard, or a mailbox it couldn't list in full); - count found mail missing from the backup; - verify found a problem.

restore exits 1 when any message was skipped or failed.

Checking the backup. Once mail is offloaded and the mailbox has emptied its Trash, the backup holds the only copy, so verify looks for damage that would otherwise go unnoticed: a failing disk, a sync tool rewriting files, an accidental delete. It needs no access to the mailbox and changes nothing (both databases are opened read-only).

mailcellar verify                  # every backup, every message
mailcellar verify work --offloaded # only offloaded mail: the copies with no other source
mailcellar verify --sample 500     # 500 random messages: quick enough for a routine check

For each message it checks that the .eml exists and parses, and that its content hash matches the one meta.sqlite recorded. The content hash covers what a reader sees, the visible text and attachments, and leaves out headers, links and addresses (it was made for matching duplicates), so plain verify catches damage to a message's content but not every changed byte; verify --chain checks the bytes. It also checks that msg-db.sqlite and meta.sqlite agree on which messages exist, and, with --offloaded, that every offloaded message is in the backup. Problems are listed (--top N sets how many) and the exit status is 1 if there are any. A message with no current meta.sqlite row has nothing to compare with: it is counted as unchecked, with a warning, and mailcellar meta fills it in. A message with no visible text or attachments (a body that is only a link, images or nothing) has no content hash, by design; it is counted apart, as having nothing to compare, and verify --chain checks its bytes instead. A full run parses every message, so it takes a while on a large backup; progress goes to stderr.

Backups live in <data root>/<email>/, in the format of gyb, so other tools can read them too. mailcellar restore puts mail back into its mailbox.

Options

Accounts are named by address, config name or a unique part of an address; with none named, every account in accounts.toml.

backup

Option What it does
--full List every message instead of reading Gmail's history since the last run (an IMAP account reads every folder on each run, and works out what changed)
--prune Then run mailcellar forget --deleted on the account: mail the mailbox has deleted for good, never offloaded, leaves the backup, held 30 days in <data root>/_forgotten/. Without --yes, forget's dry run. Not with --search or --folder. For this release; then use forget --deleted
--yes With --prune: forget them after you type the count
--force With --prune: go ahead although more than 10% of the backup's messages are gone from the mailbox
--unattended For scheduled runs: never open a browser. A token that needs signing in fails that account's run, with the command to fix it
--refresh-labels Also re-sync the labels of messages already backed up. Gmail lists them label by label, 500 messages per call, so about a minute for 50,000 messages. An IMAP account's labels are refreshed on every run
--search QUERY Only consider messages matching this Gmail search (Gmail accounts only)
--folder FOLDER Backup folder (default <data root>/<email>)
--config CONFIG Config folder (default ~/.config/mailcellar, or $MAILCELLAR_CONFIG)

An account that has been backed up before (its hash chain anchor is in the config folder) but has no backup in the folder is refused, and nothing is created there: most likely the data root is on a volume that isn't mounted. To start a new backup in that place on purpose, move <config>/anchors/<email>.json aside first.

count

Takes --folder and --config, as backup does.

meta

Option What it does
--offline Only parse the .eml files; don't ask the mailbox for thread IDs, labels and sizes
--folder FOLDER, --config CONFIG As for backup

verify

Option What it does
--offloaded Only offloaded mail, the copies with no other source
--sample N Check N messages chosen at random (of the offloaded ones, with --offloaded)
--chain Also check the backup against its hash chain and the anchor in the config folder (not with --sample or --offloaded)
--top N Problems listed per account (default 50)

verify takes account names and finds the backups in the data root.

The hash chain. Each backup folder has a chain.jsonl, which gyb ignores: one line, or link, per backup, restore, forget (and its --undo and --empty), and --prune before it ran forget, listing the messages it added to or removed from the backup with the SHA-256 of each .eml, and the hash of the link before. The latest link's number and hash are also kept in the config folder (anchors/<email>.json), and report health shows them. The first backup after upgrading starts the chain by hashing every file once.

verify --chain replays the chain and reads every file. It fails on a file changed since it was recorded, a recorded message missing from the backup, a message in the backup no link added, a chain that was edited, or an anchor that names another link, so a change shows even if meta.sqlite was rewritten to match. It warns, without failing, when there is no anchor (a copy of the backup on another machine) or the anchor is behind the chain. Something running as you could rewrite the chain and the anchor together; keep the head hash report health shows somewhere else as well.