const { useState, useEffect } = React; // --- Global hooks --- function useScrollReveal() { useEffect(() => { const els = document.querySelectorAll(".reveal"); if (!els.length) return; if (!("IntersectionObserver" in window)) { els.forEach((el) => el.classList.add("visible")); return; } const obs = new IntersectionObserver( (entries) => { entries.forEach((e) => { if (e.isIntersecting) { e.target.classList.add("visible"); obs.unobserve(e.target); } }); }, { threshold: 0.08, rootMargin: "0px 0px -40px 0px" }, ); els.forEach((el) => obs.observe(el)); return () => obs.disconnect(); }, []); } function useMouseGlow() { useEffect(() => { const move = (e) => { document.documentElement.style.setProperty("--mx", e.clientX + "px"); document.documentElement.style.setProperty("--my", e.clientY + "px"); }; window.addEventListener("mousemove", move, { passive: true }); return () => window.removeEventListener("mousemove", move); }, []); } // --- Components --- function FloatpaneMark({ size = 20 }) { return ( Floatpane logo ); } function MatchaWordmark() { const [version, setVersion] = useState("v0.8.2"); useEffect(() => { 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 QuickStart() { const CMD1 = "brew install floatpane/matcha/matcha"; const CMD2 = "matcha"; const [l1, setL1] = useState(""); const [l2, setL2] = useState(""); const [phase, setPhase] = useState("pre"); // pre → t1 → pause → t2 → done useEffect(() => { let t; if (phase === "pre") { t = setTimeout(() => setPhase("t1"), 1400); } else if (phase === "t1") { if (l1.length < CMD1.length) { t = setTimeout(() => setL1(CMD1.slice(0, l1.length + 1)), 36); } else { t = setTimeout(() => setPhase("pause"), 420); } } else if (phase === "pause") { t = setTimeout(() => setPhase("t2"), 320); } else if (phase === "t2") { if (l2.length < CMD2.length) { t = setTimeout(() => setL2(CMD2.slice(0, l2.length + 1)), 90); } else { setPhase("done"); } } return () => clearTimeout(t); }, [phase, l1, l2]); const caret1 = phase === "pre" || phase === "t1" || phase === "pause"; const showL2 = phase === "t2" || phase === "done"; return (
terminal
        $ 
        {l1}
        {caret1 && }
        {showL2 && (
          <>
            {"\n"}
            $ 
            {l2}
            
          
        )}
      
); } function Hero() { return (
by floatpane · local-first · secure · no telemetry

Email for people who
live in the terminal.

Matcha is a keyboard-native email client built for the shell. Multi-account IMAP, PGP encryption, markdown composing, and a CLI that pipes. One static binary. No cloud. No trackers.

Install now Read the docs
license MIT
runtime single static binary
platforms macOS · Linux · Windows
); } const FEATURES = [ { k: "01", title: "Keyboard-native", body: "Read, reply, delete, archive — all from the keyboard. Navigate messages, switch accounts, and jump between folders without touching the mouse.", mono: "j k r d a ↵ esc", }, { k: "02", title: "Visual mode batch ops", body: "Enter visual mode to select a range of messages, then delete, archive, or move them all 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 in one window. Switch between them instantly so you never reply from the wrong address.", mono: "← me@andrinoff\n→ drew@floatpane", }, { k: "05", title: "Fuzzy filter", body: "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.", 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: "Inline image rendering", body: "Images render inline via iTerm2 or kitty graphics where supported. Toggle with a key. Off by default, always.", mono: "→ ◧ images on", }, { k: "09", title: "Full-disk encryption", body: "Encrypt all local data with a password that is never stored — not on disk, not in the keyring. Matcha shows a lock screen on startup. Forget the password and there is no reset.", mono: "matcha is locked\n> ••••••••\nenter: unlock", }, ]; 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 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, i) => (
{f.k} ——

{f.title}

{f.body}

{f.mono}
))}
); } 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", }, scoop: { plat: "Windows", cmd: "$ scoop install 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/nix-matcha\n$ matcha", }, nixpkgs: { plat: "NixOS · nixpkgs", cmd: "$ nix profile install nixpkgs#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.

install matcha read the docs →
); } function Footer() { return ( ); } function App() { useScrollReveal(); useMouseGlow(); return (
); } window.MatchaApp = App;