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
