Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

5. Reading and searching from the command line

← Contents

Every verb here reads the local store and prints for a human; add --json and it prints a document your scripts can rely on. Nothing in this chapter changes your mail, and only read and attachments ever go to the server — to fetch a body or an attachment that has not been downloaded yet, once.

mailcoded search '<query>' [--limit 50] [--cursor <c>] [--account <id>] [--folder <id>]
                           [--order relevance|date] [--no-snippet]

Full-text over subject, sender, recipients and body, plus structured predicates. Local only.

SyntaxMatches
invoicethe word, anywhere; diacritics fold, so café also finds cafe
"two words"the phrase
inv*words beginning inv
from:acme, to:bob@example.com, cc:teamthe address or name contains
subject:invoicethe subject contains
tag:triagedcarries the local Tag
folder:INBOX, in:INBOXin that folder
is:unread, is:read, is:flagged, is:unflagged, is:draft, is:repliedserver Flags; seen, starred and answered are accepted synonyms
has:attachmentcarries an attachment
before:2026-01-31, after:2026-01-01, since:2026-01-01strictly earlier; at or after. ISO dates
-term, -tag:spam, not termnegation

Terms are combined with AND. OR is not supported; it is reported in errors and skipped. A query is capped at 64 terms and 4096 characters. A malformed query never fails: the parser reports what it could not read and searches with the rest, so look at errors in the JSON when a result seems thin.

Chinese, Japanese and Korean text is indexed by trigram, with a slower exact match for one- and two-character terms.

$ mailcoded search 'invoice'
      14  2025-01-19T08:00:00.000Z  [unread]  accounts@example.com
          Invoice 4471
          Invoice attached.
1 hit(s), truncated=false

The first column is the message id the other verbs take. Latin-script text queries come back by relevance; Chinese, Japanese and Korean text, and queries with no text at all, always come back newest first. --order date asks for newest first explicitly.

Paging. --limit is 1 to 200, default 50. When there is more, the human output ends with the exact command to continue —

3 hit(s), truncated=true
next: mailcoded search '<same query>' --cursor k1737374400000.23

— and the JSON carries truncated and next_cursor. Pass the cursor back verbatim. truncated: true with a null cursor means the rest lies beyond what any cursor reaches: narrow the query, or use --order date. Follow the cursor rather than raising --limit.

read

mailcoded read <id> [--no-fetch] [--max-chars 20000] [--skip-chars 0]

One message, as plaintext. There is no HTML mode and no flag that adds one.

$ mailcoded read 14
id:      14
date:    2025-01-19T08:00:00.000Z
from:    accounts@example.com
to:      bob@example.org
subject: Invoice 4471
flags:   unread
tags:    unread
attachments: yes (bytes are not exposed to the CLI)

Invoice attached.

When the body has not been downloaded yet, read connects to the account’s IMAP server, fetches it once, stores it and indexes it. --no-fetch stays offline and prints whatever is stored. For a long message, --max-chars (1 to 1000000, default 20000) and --skip-chars page through the body.

Anything in a message that could drive your terminal — escape sequences, invisible and direction-changing characters — is neutralised before it is printed (chapter 12).

thread

mailcoded thread <id|threadKey> [--limit 200]

Every message in one conversation, oldest first. A number is taken as a local message id and its thread is resolved for you; anything else is treated as a thread key. --limit is 1 to 1000.

attachments

mailcoded attachments <id> [--no-fetch]
mailcoded attachments <id> --save <index> [--out <dir>] [--overwrite]

The first form lists them:

0  invoice.pdf  application/pdf  84 KB

Save one with: mailcoded attachments 14 --save <index> --out <dir>

The second writes one to disk — into --out, or the current directory — under the flattened, path-safe filename the parser assigned, never a path taken from the message. It refuses to overwrite an existing file unless you pass --overwrite. read never hands you attachment bytes; this is the only verb that does, and it writes them to a file rather than printing them.

folders

mailcoded folders [--account <id>]

Each folder with its id, the locally stored unread and total counts, and its path; --json adds the role and the last successful sync.

     1      37 unread       37 total  INBOX

stats and health

mailcoded stats
mailcoded health

stats is counters: schema version, database and blob sizes, process memory, the outbox, how many sends are left in the current hour, and per-folder counts. Nothing in it identifies a message or a person.

health is state: the store path and schema, which secret backend is active, whether the send and SQL gates are open, each account’s connection and authentication state, and a store-wide count of sends stuck mid-dispatch.

status:  ok
store:   v6 at /home/you/.local/share/mailcoded/store.db
secrets: chain(libsecret,file)
gates:   send=off sql=off

health exits 0 even when something is degraded; with --json, branch on its status field, which is ok or degraded. (ok is true in every successful document and says nothing about health.)

Output and exit codes

Every --json document starts with schema_version and ok. Errors go to stderr — with --json, as a JSON object whose error.code is the RPC numeric code — and the exit code says what kind went wrong: 2 you sent bad arguments, 3 nothing by that id, 7 the credential failed, 8 the network did, and so on. The full table is in chapter 14.