Use the CLI
Every operation localmail performs is reachable from
the localmail command. This page is a tour of the most
common workflows; localmail --help and
localmail COMMAND --help have the authoritative flag
list.
Accounts & credentials
Accounts are stored in the database — it is the single
source of truth. localmail init-db seeds any
[[accounts]] blocks from config.toml on first run,
but after that the database is canonical. You can manage accounts entirely
from the CLI, or point-and-click in the
admin web UI.
| Command | What it does |
|---|---|
localmail list-accounts |
Show every account in the database and whether a credential (password or OAuth refresh token) is in the keyring. |
localmail add-account NAME |
Prompt for an IMAP password (or app password) and store it. Seeds
the row from config.toml if it isn't in the DB yet. |
localmail oauth-login NAME |
Run the Gmail OAuth desktop flow. Browser opens; refresh token is stored. |
localmail enable-account NAMElocalmail disable-account NAME |
Resume / pause syncing for an account without deleting it. Takes effect on the next daemon reload. |
localmail remove-account NAME |
Drop the stored credentials from the keyring (the DB row is kept).
Add --delete-row to remove the account, and
--force to cascade when it still holds messages. |
An account with auth method archive has no IMAP host and
is never synced — it's a holding place for
imported mbox / Maildir mail.
One-shot sync
If you don't want a daemon — e.g. you want to run a sync from cron
every 15 minutes — localmail sync is the one-shot
incremental sync command.
localmail sync # every account, every folder
localmail sync --account horst-gmail # one account
localmail sync --account horst-gmail --limit-per-folder 10
# smoke test / staged backfill
It is safe to interrupt — Postgres SAVEPOINTs and
mailboxes.uidnext checkpoints mean the next run resumes
cleanly. The daemon and one-shot sync share exactly the same code
path, so anything that works in one works in the other.
Cron example
# /etc/crontab or `crontab -e`
*/15 * * * * yourusername /home/yourusername/.local/bin/localmail sync >> ~/.local/state/localmail-cron.log 2>&1
Initial backfill workflow
On a fresh archive with several years of mail, you typically want to do the heavy lifting up front, in the foreground, so you can watch progress:
-
Mirror mail bytes.
localmail syncRun for as long as it takes. Hours for tens of thousands of messages with attachments. Re-run if interrupted.
-
Detect message body languages.
localmail lang-backfillPopulates
messages.body_langsolang:filters work in search. Required once on a fresh install. -
Extract attachment text.
localmail extract-backfillRuns the extract worker until the queue is empty. PDFs, DOCXs, XLSX, HTML, etc. become searchable. Requires the
extractionextra if you want full Docling support; the lightweight extractor handles 11 formats with no extra deps. -
Compute embeddings.
localmail embed-backfillDrains the embedding queue. First run downloads ~250 MB of model weights to
~/.cache/fastembed/. -
Check status.
localmail search-statusReports message/chunk/extraction counts and how many rows still have
body_langpending.
Search
localmail search "Berlin conference"
localmail search "invoice has:attachment after:2025-01-01 from:anna"
localmail search "Heizung" --format json | jq
localmail search "minutes lang:en before:2025-06-01"
localmail search "that invoice grandma sent last spring" --smart
The optional --smart flag runs your query through a local
LLM first — rewriting it for better recall and pulling natural-language
dates / senders into proper filters. It's entirely local and degrades
gracefully if the model isn't running. See
Smarter search.
Search DSL operators
| Operator | Example | Notes |
|---|---|---|
from: | from:anna@example.com | Substring match on the sender header. |
to: | to:team@example.com | Substring match on the recipient header. |
subject: | subject:invoice | Token match on Subject. |
label: | label:Receipts | Gmail label / IMAP folder display name. |
account: | account:horst-gmail | By config.toml account name. |
folder: | folder:INBOX | By folder name. |
account_id: | account_id:1 | By DB id (faster). |
folder_id: | folder_id:42 | By DB id. |
after: | after:2025-01-01 | YYYY-MM-DD, inclusive. |
before: | before:2025-06-01 | YYYY-MM-DD, exclusive. |
lang: | lang:en | ISO 639-1. Repeatable; lang:en lang:de matches either. |
has:attachment | has:attachment | Restrict to messages with attachments. |
Operators stack with free-text query terms. Anything not matching an operator is fed to the hybrid retriever.
Failure recovery
localmail wraps every per-message and per-attachment operation in a
Postgres SAVEPOINT. A single bad message can't take down a batch — it
lands in failed_messages with its full raw bytes, error
class, and traceback, ready to retry later.
localmail list-failed # show poisoned messages
localmail list-failed --limit 50
localmail retry-failed # re-process every failed message
localmail retry-failed --account NAME
localmail list-failed-extractions # attachments the extractor choked on
localmail retry-failed-extractions
localmail list-failed-embeddings
localmail retry-failed-embeddings
Successful retries delete the failure row and insert the message
where it belongs. Permanent failures bump retry_count
and stay listed.
Status & health
localmail search-status # row counts + backlog
localmail search-status --format json # for monitoring scripts
GUI server (HTTPS API + admin UI)
If you want the desktop app — or any other tool — to reach the
archive over a network boundary instead of direct DB access, run
localmail serve. The same process also hosts the browser-based
admin web UI at /admin and,
optionally, the MCP server for AI agents
at /mcp.
localmail rotate-tls --cert ~/.config/localmail/tls.crt \
--key ~/.config/localmail/tls.key
localmail add-api-user alice # desktop-app / API user
localmail grant-account alice horst-gmail
localmail add-api-user admin --admin # bootstrap an admin-UI user
localmail serve --bind 127.0.0.1 --port 8443 \
--tls-cert ~/.config/localmail/tls.crt \
--tls-key ~/.config/localmail/tls.key
See the Desktop app page for the end-to-end client flow, and the Admin web UI page for managing accounts, users, the daemon, and imports from a browser.
Importing existing mail
Bulk-load an old mbox file or Maildir tree into
an archive account:
localmail import ~/archives/family-2019.mbox \
--account family-archive --kind mbox
Re-running is idempotent. See Importing mail for the full workflow.
Useful one-liners
Once you have a populated archive, everything is in Postgres, so you can mix shell and SQL however you like:
# Largest senders, all accounts, last 12 months.
psql -d localmail -c "
SELECT from_addr, COUNT(*)
FROM messages
WHERE date_sent > now() - interval '12 months'
GROUP BY from_addr
ORDER BY 2 DESC
LIMIT 20;"
# Total bytes of unique attachments stored on disk.
find ~/localmail/blobs -type f -printf '%s\n' | awk '{s+=$1} END {print s}'
# Search and pipe results to less.
localmail search "subject:weekly" --format json | jq -r '.results[].subject' | less