Terminal email client with vim-style keybindings
Why Epist • Quick Start • Keybindings • Commands • Configuration • Comparison
Epist is a keyboard-driven email client for the terminal, built with Glyph. Gmail OAuth + IMAP/SMTP, two-column layout, threaded conversations — manage your inbox without leaving the terminal.
Inbox view — email list on the left, full message on the right
Built for people who live in the terminal and want email to feel like vim, not like a web browser.
- Vim keybindings —
j/k,gg/G,h/l,/to search,:for commands. Feels like home. - Two-column layout — email list on the left, full view on the right. No pane juggling.
- Gmail + IMAP/SMTP — Gmail OAuth with PKCE and generic IMAP/SMTP. Use any provider, mix both.
- Secure credentials —
password_commandintegration with Keychain,pass, 1Password CLI, Bitwarden, env vars. - Threads & labels — conversation threading with
[/], Gmail labels with colored dots, collapsible categories. - Two-step search — instant local filtering + remote search with debouncing.
- Compose & reply — full compose, reply, reply-all, forward, and quick inline reply with contact autocomplete.
- Attachments & calendar — view/save/open attachments, parse
.icsinvites with RSVP support. - Bulk actions & undo — select multiple threads with
x, act on many, undo withz. - Command palette — fuzzy-matched command bar (
:) and context-aware help (?). - Local-first — SQLite cache, instant startup, your data stays yours.
- Themeable — customize colors via TOML configuration.
Full comparison with other terminal email clients →
brew tap semos-labs/tap brew install epistgit clone https://github.com/semos-labs/epist.git cd epist && bun install bun dev # development bun start # productionEpist supports Gmail (OAuth) and IMAP/SMTP (any email provider). You can use both simultaneously.
Add your account to ~/.config/epist/config.toml:
[[accounts]] name = "Work" email = "me@work.com" provider = "imap" [accounts.imap] host = "imap.work.com" port = 993 security = "tls" username = "me@work.com" password_command = "security find-generic-password -a me@work.com -s epist -w" [accounts.smtp] host = "smtp.work.com" port = 587 security = "starttls" username = "me@work.com" password_command = "security find-generic-password -a me@work.com -s epist -w"Password options:
| Method | Example |
|---|---|
| macOS Keychain | password_command = "security find-generic-password -a me@work.com -s epist -w" |
pass (GPG) | password_command = "pass show email/work" |
| 1Password CLI | password_command = "op read op://Personal/WorkEmail/password" |
| Bitwarden CLI | password_command = "bw get password work-email" |
| Environment var | password_command = "echo $WORK_EMAIL_PASSWORD" |
| Plain text | password = "hunter2" (not recommended) |
You can add multiple [[accounts]] blocks for multiple IMAP/SMTP accounts.
- Go to Google Cloud Console → create a project
- Enable Gmail API, Google Calendar API, and People API
- Go to "APIs & Services" → "Credentials" → "Create Credentials" → "OAuth client ID"
- Configure OAuth consent screen:
- User Type: External (or Internal for Workspace)
- Add your email as a test user
- Scopes:
gmail.modify,gmail.send,calendar.events,calendar.readonly,contacts.readonly,userinfo.email,userinfo.profile
- Create OAuth client ID — Application type: Desktop app
- Copy the Client ID and Client Secret
Add credentials to ~/.config/epist/config.toml:
[google] clientId = "your-client-id.apps.googleusercontent.com" clientSecret = "your-client-secret"Or use environment variables:
export EPIST_GOOGLE_CLIENT_ID="your-client-id.apps.googleusercontent.com" export EPIST_GOOGLE_CLIENT_SECRET="your-client-secret"Then connect:
:login Follow the OAuth flow in your browser. Epist supports multiple Google accounts — IMAP accounts are loaded automatically from config.toml.
| Key | Action |
|---|---|
j / ↓ | Next email |
k / ↑ | Previous email |
gg | First email |
G | Last email |
Enter / Space | Open email |
l / → | View email |
Tab / ` | Switch to view pane |
s | Toggle star |
e | Archive |
D | Delete |
u | Toggle read/unread |
r | Reply |
R | Reply all |
f | Forward |
c | Compose new |
m | Move to folder |
x | Toggle thread selection |
A | Select all threads |
z | Undo last action |
/ | Search emails |
: | Open command bar |
? | Show help |
| Key | Action |
|---|---|
j / ↓ | Scroll down |
k / ↑ | Scroll up |
Ctrl+d | Page down |
Ctrl+u | Page up |
gg | Scroll to top |
G | Scroll to bottom |
h / ← / Esc | Back to list |
] | Next message in thread |
[ | Previous message in thread |
Tab | Next link |
Shift+Tab | Previous link |
Enter | Open link |
Q | Quick inline reply |
i | Toggle headers |
I | Toggle image navigation |
a | Toggle attachments |
s | Toggle star |
e | Archive |
D | Delete |
r | Reply |
R | Reply all |
f | Forward |
m | Move to folder |
z | Undo |
| Key | Action |
|---|---|
y | Accept invite |
n | Decline invite |
t | Maybe / Tentative |
| Key | Action |
|---|---|
j / k | Navigate attachments |
Enter / o | Open attachment |
s | Save attachment |
S | Save all attachments |
| Key | Action |
|---|---|
Ctrl+s | Send email |
Ctrl+f | Toggle fullscreen |
Ctrl+b | Toggle Cc/Bcc fields |
Ctrl+a | Attach file |
Ctrl+g | Manage attachments |
Esc | Cancel |
| Key | Action |
|---|---|
Ctrl+s | Send reply |
Ctrl+f | Expand to full reply |
Esc | Cancel |
| Key | Action |
|---|---|
Ctrl+f | Toggle folder sidebar |
j / k | Navigate folders |
Space / → | Toggle categories section |
← | Collapse categories |
Esc | Close sidebar |
| Key | Action |
|---|---|
q | Quit |
Ctrl+c | Quit |
Ctrl+f | Toggle folder sidebar |
: | Open command palette |
/ | Search emails |
? | Show help (context-aware) |
Open the command palette with : and type a command:
| Command | Action |
|---|---|
archive | Archive current email |
delete | Delete current email |
star | Toggle star |
unread | Toggle read/unread |
move | Move to folder |
undo | Undo last action |
compose | Compose new email |
reply | Reply to current email |
reply-all | Reply all |
forward | Forward current email |
search | Search emails |
login | Add Google account (OAuth) |
logout | Remove all accounts |
profile | Manage connected accounts (Gmail + IMAP) |
sync | Force sync with server |
reset-sync | Clear cache & full resync |
help | Show keybindings |
quit | Exit application |
Create or edit ~/.config/epist/config.toml:
[general] downloads_path = "~/Downloads" auto_mark_read = true auto_save_interval = 5 # seconds undo_timeout = 5 # seconds [signature] enabled = true text = """ -- Sent from Epist """ # Colors: black, red, green, yellow, blue, magenta, cyan, white, blackBright, etc. [theme] accent_color = "cyan" header_bg = "white" selected_bg = "blackBright" starred_color = "yellow" unread_style = "bold" # bold, color, or both [google] clientId = "" clientSecret = "" [keybinds] # Override defaults: action = "key" # Gmail account (uses OAuth — run :login to authenticate) [[accounts]] name = "Personal" email = "me@example.com" provider = "gmail" is_default = true # IMAP/SMTP account (any email provider) [[accounts]] name = "Work" email = "me@work.com" provider = "imap" signature = "--\nSent from my work account" [accounts.imap] host = "imap.work.com" port = 993 security = "tls" # "tls" (993), "starttls" (143), or "none" username = "me@work.com" password_command = "pass show email/work" [accounts.smtp] host = "smtp.work.com" port = 587 security = "starttls" # "tls" (465), "starttls" (587), or "none" username = "me@work.com" password_command = "pass show email/work"Epist follows the XDG Base Directory Specification:
Configuration (~/.config/epist/): config.toml — accounts, credentials, theme, keybinds.
Data (~/.local/share/epist/):
| File | Description |
|---|---|
epist.db | SQLite database (emails, labels, sync state) |
accounts.json | OAuth tokens and account info |
account-settings.json | Custom account display names |
drafts/ | Saved email drafts |
logs/ | Application logs |
Override with XDG_CONFIG_HOME and XDG_DATA_HOME environment variables.
Side-by-side with other terminal email clients:
| Feature | Epist | NeoMutt | aerc | Himalaya | Alpine | meli |
|---|---|---|---|---|---|---|
| Protocol | Gmail API + IMAP/SMTP | IMAP/POP3/SMTP | IMAP/SMTP/Notmuch | IMAP/SMTP | IMAP/POP3/SMTP | IMAP/Notmuch/Maildir |
| Gmail OAuth (built-in) | ✅ | ❌¹ | ❌ | ❌ | ||
| IMAP/SMTP support | ✅ Any provider | ✅ | ✅ | ✅ | ✅ | ✅ |
| Setup complexity | brew install + :login | Extensive .muttrc config | Moderate config files | Moderate config | Menu-driven setup | TOML config |
| Secure credentials | ✅ password_command | ✅ | ✅ | ✅ | ❌ | ✅ |
| Vim keybindings | ✅ Out of the box | ✅ Customizable | ✅ Inspired | ❌ CLI only | ❌ Menu-driven | |
| Two-column layout | ✅ List + preview | ❌ Single pane | ❌ Single pane | ❌ CLI only | ❌ Single pane | ✅ |
| Thread view | ✅ Navigate with [/] | ✅ | ✅ | ✅ | ✅ | |
| Multi-account | ✅ Mix Gmail + IMAP | ✅ Complex config | ✅ | ✅ | ✅ | ✅ |
| Calendar invites (ICS) | ✅ Parse + RSVP | ❌ | ❌ | ❌ | ❌ | |
| Contact autocomplete | ✅ From history | ✅ With aliases | ✅ | ❌ | ✅ | ❌ |
| Local cache / offline | ✅ SQLite | ❌ | ❌ | ❌ | ||
| Search | ✅ Local + remote | ✅ With notmuch | ✅ | ✅ Basic | ✅ | ✅ With notmuch |
| Gmail labels & categories | ✅ Colored dots | |||||
| Undo actions | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Command palette | ✅ Fuzzy matching | ❌ | ✅ | ❌ | ❌ | ❌ |
| Inline quick reply | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Context-aware help | ✅ Press ? anywhere | ✅ | ❌ | ❌ | ✅ | ❌ |
| Bulk actions | ✅ Select + act | ✅ Tag patterns | ✅ | ❌ | ✅ | ✅ |
| Themeable | ✅ TOML config | ✅ .muttrc | ✅ stylesets | ❌ | ✅ Limited | ✅ Themes |
| Written in | TypeScript (Bun) | C | Go | Rust | C | Rust |
¹ Requires external helper scripts (e.g. oauth2.py) or app-specific passwords
² Supports OAuth via external credential commands, requires manual setup
TL;DR — Epist is built for people who want a modern, keyboard-driven terminal email experience with zero friction. Gmail users get one-command OAuth setup. IMAP/SMTP users get secure
password_commandintegration with any secret manager. Mix both in a single client.
| Component | Technology |
|---|---|
| Runtime | Bun |
| UI Framework | Glyph — React renderer for terminal UIs |
| State Management | Jotai |
| Database | SQLite via Drizzle ORM |
| IMAP | ImapFlow |
| SMTP | Nodemailer |
| MIME Parsing | Mailparser |
| Date/Time | Luxon |
| NLP Dates | chrono-node |
| HTML Rendering | Cheerio + Turndown + Marked |
| Validation | Zod |
- Gmail sync via OAuth with PKCE
- IMAP/SMTP support (any email provider)
- Secure credentials via
password_command - Multi-account support (mix Gmail + IMAP)
- Two-column layout (list + view)
- Thread view with message navigation
- Compose, reply, reply-all, forward
- Quick inline reply
- Contact autocomplete from email history
- Dynamic Gmail labels & folders with colored dots
- IMAP folder auto-discovery (special-use flags + name heuristics)
- Collapsible Gmail categories
- Two-step search (local + remote)
- Attachment view, save, and open
- Calendar invite parsing (inline +
.icsattachments) - Calendar invite RSVP
- Bulk selection & actions
- Undo support
- Move to folder picker
- Star, archive, delete, mark read/unread
- Command palette with fuzzy matching
- Context-aware help dialog
- Local SQLite cache with instant startup
- Background sync (10s interval)
- Image navigation mode
- Link navigation with Tab
- Configurable theme & keybinds
- XDG Base Directory support
- Draft auto-save
- Homebrew distribution
- Downloadable binaries
- Offline mode improvements
- IMAP IDLE push notifications
- Email templates
- PGP/GPG encryption
MIT © 2025
Built with Glyph • React • a lot of ANSI escape codes
