const { useState, useEffect } = React; function FloatpaneMark({ size = 20 }) { return ( Floatpane logo ); } function MatchaWordmark() { const [version, setVersion] = useState("v0.8.2"); useEffect(() => { // floatpane/matcha repo — fetch latest release tag fetch("https://api.github.com/repos/floatpane/matcha/releases/latest") .then((r) => (r.ok ? r.json() : null)) .then((d) => { if (d && d.tag_name) setVersion( d.tag_name.startsWith("v") ? d.tag_name : "v" + d.tag_name, ); }) .catch(() => {}); }, []); return (
Matcha logo matcha — {version}
); } function TopNav() { const [stars, setStars] = useState(null); useEffect(() => { fetch("https://api.github.com/repos/floatpane/matcha") .then((r) => (r.ok ? r.json() : null)) .then((d) => { if (d && typeof d.stargazers_count === "number") { const n = d.stargazers_count; setStars(n >= 1000 ? (n / 1000).toFixed(1) + "k" : String(n)); } }) .catch(() => {}); }, []); return (
install
); } function Hero({ datasetKey, setDatasetKey }) { const [pressed, setPressed] = useState(false); const TUI = window.MatchaTUI; return (
by floatpane · local-first · secure · no telemetry

A powerful, feature-rich
email client for your terminal.

Matcha is a modern TUI email client for people who live in the shell. Vim keybindings, PGP, IMAP multi-account, markdown composing, visual-mode batch ops, and a CLI that speaks your language.

Install Read the docs
license MIT
runtime single static binary
platforms macOS · Linux · Windows
demo ·
{pressed ? ( ✓ keyboard live ) : ( click · j k · tab · )}
{TUI && ( setPressed(true)} /> )}
); } const FEATURES = [ { k: "01", title: "Email, the way you move", body: "Read, reply, delete, archive — all from the home row. j/k to move, h/l between accounts, tab between folders. No mouse, no modals.", mono: "j k h l r d a ↵", }, { k: "02", title: "Visual mode, for real", body: "Press v to enter Vim-style multi-select. Expand with j/k, then d, a, or m to run batch ops as a single IMAP command.", mono: "v j j j d\n→ deleted 4 messages", }, { k: "03", title: "Compose in markdown", body: "Write in the syntax you already know. Headings, lists, fenced code, and tables render cleanly on the other side.", mono: "# subject\n- bullet\n`inline`", }, { k: "04", title: "Multi-account, tabbed", body: "IMAP, Gmail, Fastmail, Proton Bridge — all tabbed in one window. h and l switch between them so you never reply from the wrong address.", mono: "← me@andrinoff\n→ drew@floatpane", }, { k: "05", title: "Fuzzy filter", body: "Press / to fuzzy-filter across senders, subjects, and bodies in the active view. Results stream in as you type.", mono: "/lena → 3 hits", }, { k: "06", title: "Local-first drafts", body: "Every keystroke hits disk before it hits the wire. Close the laptop, open it anywhere, pick up mid-sentence. Esc saves.", mono: "~/.cache/matcha/drafts", }, { k: "07", title: "CLI that composes", body: "Pipe errors into apologies. Send from scripts, CI, or cron. `matcha send` does one thing well.", mono: "$ matcha send --to …", }, { k: "08", title: "AI, on your terms", body: "Rewrite drafts with the model of your choice. Let agents send on your behalf — with strict scopes and an audit log.", mono: "alt + r: make it more formal", }, { k: "09", title: "Smart image rendering", body: "Images render inline via iterm2 or kitty-graphics where supported. Toggle with i. Off by default, always.", mono: "i → ◧ images on", }, ]; function Features() { return (
§ features

Everything you'd expect.
And nothing you wouldn't.

Matcha is opinionated. It won't follow you around the web, won't upsell you on AI credits, and won't sync your signatures to a SaaS. It reads mail. It writes mail. It stays out of the way.

{FEATURES.map((f) => (
{f.k} ——

{f.title}

{f.body}

{f.mono}
))}
); } function Keybinds() { const rows = [ { g: "motion", items: [ ["j / k", "next / prev message"], ["↑ / ↓", "next / prev message"], ["h / l", "prev / next account"], ["← / →", "prev / next account"], ["tab", "next folder"], ["shift-tab", "prev folder"], ], }, { g: "inbox", items: [ ["↵", "open email"], ["r", "refresh"], ["d", "delete"], ["a", "archive"], ["/", "filter"], ["v", "visual mode"], ["esc", "back / main menu"], ], }, { g: "visual mode", items: [ ["v", "enter visual mode"], ["j / k", "expand selection"], ["d", "delete all selected"], ["a", "archive all selected"], ["m", "move to folder"], ["v / esc", "exit visual mode"], ], }, { g: "email view", items: [ ["j / k", "scroll body"], ["r", "reply"], ["d", "delete"], ["a", "archive"], ["tab", "focus attachments"], ["i", "toggle images"], ["esc", "back to inbox"], ], }, { g: "attachments", items: [ ["j / k", "navigate"], ["↵", "download & open"], ["tab / esc", "back to body"], ], }, { g: "composer", items: [ ["tab / shift-tab", "navigate fields"], ["↵ on From", "select account"], ["↵ on Attachment", "open file picker"], ["↵ on Send", "send email"], ["↑ / ↓", "contact suggestions"], ["esc", "save draft & exit"], ], }, ]; return (
§ keybinds

Vim-native.
Home row to inbox zero.

Every binding is documented at{" "} docs.matcha.email . Muscle-memory for vimmers, learnable for everyone else.

{rows.map((row) => (
── {row.g} ──
{row.items.map(([k, label]) => (
{k} {"·".repeat(26)} {label}
))}
))}
); } const INSTALL_TABS = { brew: { plat: "macOS · Linux", cmd: "$ brew install floatpane/matcha/matcha\n$ matcha", }, winget: { plat: "Windows 10 / 11", cmd: "$ winget install --id=floatpane.matcha\n$ matcha", }, snap: { plat: "Ubuntu · Linux", cmd: "$ sudo snap install matcha\n$ matcha" }, flatpak: { plat: "Linux", cmd: "$ flatpak install https://matcha.email/matcha.flatpakref\n$ matcha", }, aur: { plat: "Arch Linux", cmd: "$ yay -S matcha-client-bin\n$ matcha" }, nix: { plat: "NixOS · any Nix", cmd: "$ nix profile install github:floatpane/matcha\n$ matcha", }, }; function Install() { const [tab, setTab] = useState("brew"); const [meta, setMeta] = useState({ version: "0.8.2", date: "apr 23, 2026", size: null, }); useEffect(() => { fetch("https://api.github.com/repos/floatpane/matcha/releases/latest") .then((r) => (r.ok ? r.json() : null)) .then((d) => { if (!d) return; const version = (d.tag_name || "").replace(/^v/, "") || "0.8.2"; const date = d.published_at ? new Date(d.published_at) .toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", }) .toLowerCase() : "apr 23, 2026"; const asset = (d.assets || []).find((a) => a.size) || null; const size = asset ? (asset.size / (1024 * 1024)).toFixed(1) + " MB" : null; setMeta({ version, date, size }); }) .catch(() => {}); }, []); const t = INSTALL_TABS[tab]; return (
§ install

One binary.
Pick your package manager.

No runtime. Ships natively for macOS, Linux, Windows. Source and issues at{" "} github.com/floatpane/matcha .

{Object.keys(INSTALL_TABS).map((k) => ( ))}
{t.plat}
{t.cmd}
latest {meta.version} · {meta.date}
source github.com/floatpane/matcha
); } function CTA() { return (
$ _

Your inbox is waiting
in the terminal.

make your emails secure read the docs →
); } function Footer() { return ( ); } // ---------- Tweaks ---------- function Tweaks({ datasetKey, setDatasetKey, visible }) { if (!visible) return null; return (
Tweaks
Demo content
{Object.entries({ default: "drew's inbox", dev: "floatpane dev", personal: "personal", }).map(([k, label]) => ( ))}
); } function App() { const [datasetKey, setDatasetKey] = useState(() => { try { return localStorage.getItem("matcha-dataset") || "default"; } catch { return "default"; } }); const [tweaksVisible, setTweaksVisible] = useState(false); useEffect(() => { try { localStorage.setItem("matcha-dataset", datasetKey); } catch {} }, [datasetKey]); useEffect(() => { const onMsg = (e) => { const d = e.data || {}; if (d.type === "__activate_edit_mode") setTweaksVisible(true); if (d.type === "__deactivate_edit_mode") setTweaksVisible(false); }; window.addEventListener("message", onMsg); window.parent.postMessage({ type: "__edit_mode_available" }, "*"); return () => window.removeEventListener("message", onMsg); }, []); useEffect(() => { window.parent.postMessage( { type: "__edit_mode_set_keys", edits: { datasetKey } }, "*", ); }, [datasetKey]); return (
); } window.MatchaApp = App;