Skip to content

huseynsnmz/postr

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

38 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

postr

A keyboard-first email TUI with an AI agent, running entirely on Cloudflare Workers

postr inbox view

postr lets you send, receive, and manage emails from a single scrolling terminal buffer — all powered by your own Cloudflare account. Inbound mail arrives via Cloudflare Email Routing, each mailbox is isolated in its own Durable Object with a SQLite database, and attachments are stored in R2. An AI layer on top of Workers AI can summarize threads, draft replies, search across mail, and triage your inbox.

The interaction model borrows from Claude Code: a single scrolling column, a prompt, slash commands, and a small set of single-key bindings. No panes, no mouse, no chrome.

Inspired by Cloudflare's agentic-inbox — postr re-targets the same Workers + Durable Objects + Email Routing architecture onto a terminal client written in Rust, with the worker also ported to Rust via workers-rs.

How to set up

  1. Build and deploy the worker

    cd worker
    ./tools/install.sh     # one-time, installs pinned worker-build into worker/tools/
    npx wrangler deploy

    You'll be prompted for DOMAINS — the domain you want to receive mail for.

  2. Set up Email Routing — Create a catch-all rule on your domain that forwards to this worker.

  3. Enable Email Service — The worker needs the send_email binding for outbound mail. See Email Service docs.

  4. Set the CLI token

    npx wrangler secret put CLI_TOKEN
  5. Build, log in, create a mailbox

    cd ../cli && cargo build --release
    ./target/release/postr login https://<your-worker>.workers.dev
    ./target/release/postr mailbox add me@yourdomain.com --name "Your Name" --alias work
    ./target/release/postr tui

    mailbox add writes the marker R2 object the worker checks for; the address must be on a domain whose Email Routing forwards to this worker. The optional --name is attached to outbound From: headers, e.g. "Your Name" <me@yourdomain.com>. The optional --alias is a short label for /switch <alias> in the TUI. Manage existing mailboxes with postr mailbox list, postr mailbox update <addr> --name "..." --alias "..." (use --clear-name / --clear-alias to remove), and postr mailbox remove <addr>.

    To populate a fresh mailbox with curated sample messages — useful for screenshots or a quick tour — run postr demo-seed <addr>. It's idempotent.

Why we pin worker-build

worker-build 0.8.4 is the sweet spot: it externalizes cloudflare:email in its esbuild step (broken in 0.8.1) but doesn't yet pass --force-enable-abort-handler to wasm-bindgen (added in 0.8.5, which requires an externref table that Rust's wasm32-unknown-unknown doesn't currently emit). tools/install.sh drops 0.8.4 into worker/tools/worker-build/; wrangler.jsonc's build.command points there. Tracked in TODO.md.

Troubleshooting

  1. Token rejected. — The worker's CLI_TOKEN was rotated. Run postr login again.
  2. Could not resolve "cloudflare:email" / externref table required for catch wrappers — Run ./worker/tools/install.sh to (re-)install the pinned worker-build.
  3. Boxes instead of glyphs in the TUI — Use a Nerd-Font-capable terminal font.

Features

  • Keyboard-first TUI — single scroll buffer, prompt, slash commands, no mouse
  • Multi-mailbox — switch with /switch <addr|alias>, or /switch all for a unified inbox; rows show a #mailbox column so you can tell at a glance
  • Folders/folder for a picker (inbox, archive, sent, drafts, trash) or /folder <name> to jump directly
  • Soft-delete + 30-day trashd moves to trash; inside trash the same key permanently purges. A daily cron sweeps trashed messages older than 30 days and frees the R2 attachment blobs
  • Multi-selectionSpace toggles a row, then s/e/d/m apply over the whole set; the inbox title shows the count
  • Read statem toggles read/unread on the highlighted row (or every selected row); /mark-all-read clears the whole folder
  • Reply + forwardr/a/f open compose pre-seeded with On <date>, <sender> wrote: + quoted body; per-mailbox --name populates From: for outbound mail
  • AI slash commands/summarize, /draft <prompt>, /ask <query>, /triage backed by Workers AI; suggested-reply pills feed straight into compose
  • Per-mailbox isolation — each mailbox runs in its own Durable Object with state.storage().sql() + R2 for attachments
  • Bring-your-own-cloud — runs entirely on your Cloudflare account; no Anthropic, no Gmail, no third-party email server

Key bindings

Where Keys
Inbox nav j/k// select · 19 jump · open · g/G top/bottom
Inbox actions s star · e archive · d trash/delete · m read · u undo · c compose · r refresh
Multi-select Space toggle · Esc clear · s/e/d/m batch over selection
Reading j/k next/prev message · z toggle quoted · r/a reply · f forward · e/d/s/m apply to open message
Prompt / opens command popover · type to filter · runs · Esc clears
Anywhere ? shortcuts overlay · q back to inbox (and q again to quit) · ⌃c hard quit

Stack

  • CLI / TUI: Rust, ratatui, crossterm, tui-textarea, tokio, reqwest (rustls), keyring
  • Worker: Rust, workers-rs 0.8.5, mail-parser, hand-rolled worker::Router (no Hono)
  • Storage: Durable Object SQLite (one DB per mailbox, 8 migrations preserved from agentic-inbox) + R2 attachments
  • AI: Workers AI — @cf/moonshotai/kimi-k2.5 (summarize/draft/triage) + @cf/meta/llama-4-scout-17b-16e-instruct (ask filter inference)
  • Auth: Bearer token for the CLI (CLI_TOKEN set via wrangler secret put)

Getting started

After deploying the worker, run postr login <worker-url> then postr tui. Press / in the TUI to open the slash menu, or run postr --help for the CLI.

Prerequisites

  • Cloudflare account with a domain
  • Email Routing + Email Service enabled
  • Workers AI enabled
  • Rust 1.95+ with the wasm32-unknown-unknown target
  • A truecolor terminal with a Nerd-Font-capable monospace font

Architecture

┌─────────────┐    HTTPS+Bearer    ┌────────────────┐    stub.fetch    ┌──────────────┐
│  postr CLI  │ ─────/api/v1/*────▶│  postr-worker  │─────/rpc/*──────▶│  MailboxDO   │
│  ratatui    │                    │  workers-rs    │                  │  SQLite + R2 │
└─────────────┘                    └────────┬───────┘                  └──────────────┘
                                            │
                              env.ai("AI")  │  Workers AI
                                            ▼
                            /summarize /draft /ask /triage

Inbound:  Email Routing → #[event(email)] → mail-parser → MailboxDO
Outbound: Compose → /rpc/create_email → env.send_email("EMAIL").send(...)

License

Apache 2.0 — see LICENSE.

About

Keyboard-first email TUI with an AI agent, fully self-hosted on Cloudflare Workers. Rust everywhere — ratatui CLI + workers-rs worker, per-mailbox SQLite in Durable Objects, Workers AI for /summarize, /draft, /ask, /triage. Inspired by cloudflare/agentic-inbox

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors