Skip to content

Repository files navigation

mailbox

Website: parnoldx.github.io/mailbox-cli

mailbox is a fast, agent-oriented CLI and background daemon for email, calendars, tasks, daily habits, and contacts.

Instead of hitting IMAP, SMTP, CalDAV, CardDAV, and ManageSieve servers on every command, a single background daemon maintains a local SQLite Mirror of server state. Read commands are answered directly from the local mirror in milliseconds with zero network latency, while write operations synchronize with remote servers to guarantee consistency.

The mailbox CLI: box view, agenda, todo list, and compose

Overview: the Qt desktop client, calendar and event bar widgets, and the mail notification panel — all backed by the same daemon

┌────────────────────────────────────────────────────────┐
│               Agents / Scripts / Widgets               │
│         (mailbox CLI, mailbox.clock, Skills)           │
└───────────────────────────┬────────────────────────────┘
                            │ NDJSON over Unix Socket
┌───────────────────────────▼────────────────────────────┐
│                    mailbox daemon                      │
│   ┌─────────────────────┐       ┌──────────────────┐   │
│   │    SQLite Mirror    │       │  Durable Outbox  │   │
│   └──────────▲──────────┘       └─────────┬────────┘   │
└──────────────┼────────────────────────────┼────────────┘
               │ IMAP / CalDAV / CardDAV    │ SMTP
┌──────────────▼────────────────────────────▼────────────┐
│                     Mail & DAV Servers                 │
└────────────────────────────────────────────────────────┘

Highlights

  • Instant Local Reads: Commands like mailbox status, mailbox box view, mailbox agenda, or mailbox search read from the local SQLite mirror (~1ms response time) and work offline.
  • Write-Through Consistency: Modifying commands (move, seen, event add, todo done, route set) wait for the server acknowledgment before updating the mirror, ensuring an exit code of 0 means the change is live.
  • Durable Outbox: Sent mail is staged in a durable local queue before SMTP submission and retained for auditing and automatic retry.
  • Server-Side Sieve Routing: Owns and compiles Sieve filtering scripts on the server to automatically route mail into Inbox, Feed, Paper Trail, Screener, or Block.
  • First-Class Agent Support: Built-in --json envelopes with mirror freshness metadata, self-describing command discovery (mailbox commands), predictable exit codes, and an agent skill (skill/SKILL.md).
  • Unified Personal Data Surface: Covers email threads and attachments alongside RFC 5545 iCalendar (events, todos, habits) and RFC 6350 vCard (contacts).
  • Login Codes Collected, Not Delivered: A one-time code, magic link or registration link is recognised on arrival, copied to the clipboard with a desktop notification, marked read so it never raises a new-mail alert or a screener decision, and binned a quarter of an hour later.

Architecture & Concepts

1. The Mirror

The daemon keeps an offline-capable SQLite database representing the read model of all configured mailboxes and collections.

  • Freshness & Behind Notices: Every response includes mirror sync metadata. If the mirror has pending changes or disconnected networks, mailbox reports behind: true rather than failing or blocking.
  • Disposable: The mirror can be deleted and reconstructed at any time; no authoritative local-only data is lost.

2. The Outbox

The outbox stores outgoing messages on disk. If a network disruption occurs during submission or if the daemon restarts, the message enters a Held state rather than being silently dropped or endlessly duplicated.

3. Server-Side Routing & The Screener

Mail routing uses a structured Sieve script managed directly on the server:

  • Inbox: Important mail requiring attention.
  • Feed: Newsletters, automated updates, and reading material (marked read on arrival).
  • Paper Trail: Receipts, bills, delivery notices, and transactions (marked read on arrival).
  • Screener: Mail from first-time or unknown senders awaiting a routing decision.
  • Block: Unwanted senders dropped at the server.

4. Pickups

A login code is not mail to read, it is a token to collect: worth thirty seconds and then worth nothing. The daemon recognises one as it arrives in the Inbox or the Screener, copies it to the clipboard (wl-copy), raises a notification (notify-send), marks the mail read, and moves it to Trash fifteen minutes after it landed.

A magic link or a registration/activation link is the same errand and is treated the same way: the URL itself goes on the clipboard, and the notification names the host rather than the token. Copied, never followed — opening it would log you in, or confirm an address, from a notification you had not read yet.

Nothing about a pickup asks for a decision. It never counts as unread, never appears in mailbox screener, and a watch is told pickup rather than added/new — the sender is a login form you used once, not somebody to route.

A notification is not always a way to reach you: under Do Not Disturb it is silenced, and the code is already on the clipboard with nothing on screen saying so. So a collected pickup raises the mailbox bar icon — a key glyph for a minute after the code landed, which is the window in which it is still worth something — and the panel under it lists what is ready to paste for the whole fifteen minutes the mail is kept, clicking a row or its button to copy it again. Both read mailbox pickup list, which is the same $pickup mail the expiry scan bins, so nothing can outlive the code:

mailbox pickup list                # Codes and links still held, with their age
mailbox pickup copy Screener:2340  # Hand one over again

Detection gates on the subject and only then reads the body. That is the opposite of the usual approach and it is a measured choice: over 1445 real messages, body-first matching on keywords, bare code-shaped lines or opaque token URLs produced 200+ hits and one true positive, because order confirmations and booking references are indistinguishable from one-time codes once you stop reading the subject line. English and German are both covered. Only mail that arrived in the last fifteen minutes is considered at all.

A mail whose subject matched but which carried nothing to collect is logged rather than acted on, so the phrase list can be tuned against real arrivals:

journalctl --user -u mailbox.service -f | grep pickup

Expired pickups go to Trash, not oblivion, so a code you turn out to still need is recoverable for as long as the server holds it.

Because the daemon takes the urgent mail out of the Screener before anything else sees it, the bar widget has no screener in it at all — not an icon, not a count, not a triage row. Screening is a decision owed whenever you next sit down, so it lives in the desktop client. The widget only says "new inbox mail arrived".


Installation & Setup

Prerequisites

  • Linux (systemd user session recommended)
  • Go 1.22+
  • Node.js (optional, for running plugin test suites)

Building and Installing

# Build the binary into bin/mailbox
make build

# Run unit tests
make test

# Install the CLI to ~/.local/bin/mailbox
make install

# Install the agent skill to ~/.agents/skills and ~/.claude/skills
make skill

# Optional extras (each is opt-in):
make install-gui       # the Qt desktop client -> ~/.local/bin/mailbox-gui + .desktop entry (needs Qt 6 + WebEngine)
make install-plugins   # the Quickshell bar widgets -> ~/.config/omarchy/plugins/
make install-all       # CLI + skill + GUI + widgets, all of the above
make register-mailto   # make mailbox-gui the default handler for mailto: links (after install-gui)

Initial Configuration

Run the interactive setup wizard to discover folders, calendars, address books, and configure credentials:

mailbox setup

Configuration is stored securely in ~/.config/mailbox/config.toml (mode 0600).

Optional settings that the wizard does not ask about:

[pickup]
expiry = "15m"   # how long a collected login code's mail is kept before Trash

[fileee]
address = "abc123@in.fileee.com"   # where the GUI's "send to fileee" button mails a PDF

Running the Daemon

You can run the daemon directly in the foreground:

mailbox daemon

Or enable the systemd user service and socket (configured during mailbox setup):

systemctl --user enable --now mailbox.socket

Command Reference

Mail & Reading

mailbox status                     # Summary of unread/screener mail & mirror health
mailbox box list                   # List all boxes and message counts
mailbox box view Screener --limit 20 # View recent messages in a box
mailbox message view 36722         # View full headers and text of a message
mailbox thread 36722               # View an entire conversation thread
mailbox search "rechnung" --in feed # Full-text search across mirrored messages
mailbox attachment list 36722      # List files attached to a message
mailbox attachment save 36722:1    # Save attachment to disk

Composing & Sending

mailbox compose --to user@example.com --subject "Hello" --body "Message body"
mailbox reply 36722 --body "Thanks for the update."
mailbox forward 36722 --to colleague@example.com
mailbox compose --to user@example.com --subject "Draft" --body "WIP" --draft  # File in drafts instead of sending
mailbox rsvp 36722 --accept        # Accept a meeting invite (iMIP + calendar)
mailbox unsubscribe 36722          # Leave the list this came from (one-click POST, mailto, or a URL to open)
mailbox draft list                 # Mail written but not yet sent
mailbox draft send 12              # Send a draft (optionally override --to/--subject/--body)
mailbox outbox list                # View queue status and held messages
mailbox outbox retry 1             # Retry a held outgoing message

Organization & Routing

mailbox screener                   # List senders waiting for a routing decision
mailbox route set sender@domain --to feed        # Route sender to Feed
mailbox route set sender@domain --to inbox       # Route sender to Inbox
mailbox route set sender@domain --to paper        # Route sender to Paper Trail
mailbox route set sender@domain --to block       # Block sender at server level
mailbox route set @stripe.com --to paper         # Every address at that domain
mailbox aside add 36722            # Put a message into the read-later pile
mailbox reply-later add 36722      # Put a message into the reply-later pile
mailbox move 36722 --to Archive    # Move message to a box
mailbox seen 36722                 # Mark message as read
mailbox unseen 36722               # Mark message as unread
mailbox trash 36722                # Move message to Trash
mailbox spam 36722                 # Move message to Junk
mailbox label add 36722 --to Rechnungen  # Put a label on a message
mailbox label list                # Labels, and how much mail carries each
mailbox label view Rechnungen     # Mail carrying a label

Login codes need no command: the daemon collects them by itself. To watch them happen, or to hand one to a script of your own:

mailbox watch --events pickup                     # One line per code collected
mailbox watch --events pickup --run-async 'echo "$MAILBOX_CODE"'

To find one again after the clipboard has moved on — the bar icon does the same thing when you click it:

mailbox pickup list                # Codes and links still held, with their age
mailbox pickup copy Screener:2340  # Back on the clipboard

Calendars, Todos, Habits & Contacts

mailbox agenda --days 7            # Show upcoming events and due tasks
mailbox calendar list              # List all discovered calendars and task lists
mailbox event add "Team Sync" --start "tomorrow 10:00" --end "tomorrow 11:00"
mailbox todo list                  # List active tasks
mailbox todo add "Pay invoice" --due "2026-09-01"
mailbox todo done 42               # Mark task completed
mailbox habit list                 # View daily habits and streaks
mailbox habit done "meditation"    # Log today's habit completion
mailbox contact search "Jane"      # Search address books
mailbox contact add "Jane Doe" --email jane@example.com --phone "+1 555 0199"

System & Diagnostics

mailbox doctor                     # Inspect daemon, socket, mirror, and server connectivity
mailbox sieve get                  # Retrieve active server Sieve script
mailbox commands                   # Output machine-readable command registry
mailbox version                    # Print version information

Agent & Automation Integration

Structured Output (--json)

Every command supports --json, returning a structured envelope:

{
  "ok": true,
  "data": { ... },
  "mirror": {
    "behind": false,
    "synced_at": "2026-08-30T16:00:00Z"
  }
}

Standard Exit Codes

  • 0: Success (server acknowledged for writes, mirror answered for reads).
  • 1: Usage error (invalid syntax, missing argument, unknown flag).
  • 2: Not found (no message, event, contact, or item matches the ID).
  • 7: Server or daemon error (network down, credential rejected, server failure).
  • 9: No daemon listening on the Unix socket.

Skills & Plugins

  • Agent Skill: skill/SKILL.md provides instruction mappings for AI coding assistants.
  • Calendar Bar Widget: plugins/mailbox.clock/ — Omarchy / Quickshell calendar and reminder widget backed directly by the daemon socket.
  • Mail Notification Widget: plugins/mailbox.email/ — Omarchy bar widget and dropdown panel for new inbox mail alerts, also on the daemon socket.
  • Desktop Client: gui/ — a HEY-style Qt desktop mail client that follows the live Omarchy theme.

Configuration & Environment Variables

Variable Description Default
MAILBOX_CONFIG Path to configuration file ~/.config/mailbox/config.toml
MAILBOX_SOCKET Path to Unix socket $XDG_RUNTIME_DIR/mailbox.sock
MAILBOX_MIRROR Path to SQLite mirror database ~/.local/share/mailbox/mirror.db
MAILBOX_OUTBOX Path to outbox spool directory ~/.local/share/mailbox/outbox/
MAILBOX_FOLDER Comma-separated boxes to mirror (dev only) (All boxes)

Development

# Run unit tests across all packages
make test

# Format code
make fmt

# Static analysis and live tag verification
make vet

# Run live server integration tests (requires live config in ~/.config/mailbox/config.toml)
make live LIVE=./internal/sievedrv/
make live LIVE=./internal/davdrv/
make live LIVE=./internal/imapdrv/

Design notes and the numbered decisions (ADR-00xx, cited from the code) are in docs/DESIGN.md; the vocabulary is in CONTEXT.md.

License

MIT — see LICENSE.

About

mailbox is a fast, agent-oriented CLI and background daemon for email, calendars, tasks, daily habits, and contacts. GUI and bar widgets are for omarchy.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages