Bor means pine in Slovenian. Bor is also an email reader inspired by pine.
I used to use pine and later its derivatives (alpine, realpine) until it became unsustainable. I switched to mu4e over a decade ago, but while I liked many things, it was never quite perfect.
A few years ago I switched away from emacs to VSCode and with the advent of AI coding I finally bite the bullet and made my very own mail client that works exactly the way I want it. Think of bor as pine dragged (by its feet) into 2025.
Bor uses mu for email access under the hoold and Textual for the user interface.
- Fast email searching using mu's powerful query language
- Threaded message view with visual indentation
- Message composition with address autocompletion and Ctrl-C/V/X handling global clipboard even in terminal
- Attachment handling with preview (using kitty terminal graphics capabilities)
- Keyboard-driven interface
- Tab-based workflow
- Configurable via TOML configuration file
- HTML email support via html2text rendering
- Python 3.10+
- mu (maildir indexer)
- Textual library
- Optional: html2text for HTML email rendering
- Optional: kitty terminal for image preview
- no support for folders. You can mu search with maildir:/folder, but all archived messages go to the archived folder. This is how my work-flow works. Feel free to add and make pull request.
pip install bormail# Clone the repository
git clone https://github.com/your-repo/bor.git
cd bor
# Install with pip
pip install -e .
# Or install dependencies manually
pip install textual tomli html2text# Install mu on Debian/Ubuntu
sudo apt install maildir-utils
# Install mu on Arch Linux
sudo pacman -S mu
# Install mu on macOS
brew install mu- Update the version in
pyproject.toml. - Create a git tag like
v0.1.1and publish a GitHub Release. - The Build workflow will upload the sdist and wheel to PyPI.
- Configure PyPI Trusted Publishing for
slosar/borbefore the first release.
- Configure PyPI Trusted Publishing for
Copy the example configuration file and customize it:
cp bor.conf.example ~/.config/bor.confEdit ~/.config/bor.conf to configure:
- SMTP server settings
- Maildir folder paths
- Display preferences
- Color scheme
- Synchronization command
- Email and text aliases
See the configuration documentation for details.
Start Bor:
python -m bor.appOr if installed:
bor| Key | Action |
|---|---|
| Alt+0 | Message Index tab |
| Alt+1-9 | Switch to tab |
| Ctrl+Q | Quit |
| Key | Action |
|---|---|
| ↑/↓ or N/P | Navigate messages |
| Enter | Open message |
| R | Reply |
| F | Forward |
| C | Compose new |
| S | Search (mu query) |
| I | Show Inbox |
| O | Show Archive |
| U | Show Drafts |
| M | Mark message |
| X | Archive message(s) |
| A | Apply flag |
| D | Delete message(s) |
| E | Edit draft |
| T | Toggle threading |
| Ctrl+T | Show thread |
| L | Sync |
| Z | Undo move |
| Ctrl+R | Refresh index |
| Key | Action |
|---|---|
| Space/PgDn | Scroll down |
| < | Return to index (keep tab open) |
| Q | Close tab and return |
| N/P | Next/Previous message |
| R | Reply |
| F | Forward |
| C | Compose new |
| M | Mark/unmark message and advance |
| X | Archive |
| D | Delete |
| A | Apply flag |
| O | Open URL (if multiple, pick [1-9]) |
| Z | View attachments |
| Ctrl+R | Toggle full headers |
| Key | Action |
|---|---|
| Tab | Autocomplete address/text |
| Ctrl+L L | Send message |
| Ctrl+L D | Save draft |
| Ctrl+L X | Cancel |
| Ctrl+L A | Attach file |
| Ctrl+L Z | Attach many files from a directory |
| Ctrl+I | Insert file |
Ctrl+L A opens a file-path prompt with bash-like tab completion: one match completes
immediately, multiple matches are listed without forcing a choice, and the input expands only
to the longest shared prefix.
Ctrl+L Z first prompts for a directory with the same tab-completion behavior, then opens a file picker where
Space marks files, Enter attaches the marked set, Esc cancels, and Ctrl+A toggles all.
| Key | Action |
|---|---|
| 1-9 | Select attachment |
| Enter | Open with system viewer |
| S | Save attachment |
| Shift+S | Save all |
| Q or Esc | Close and return to message view |
| < | Return to index (keep tab open) |
URL opener limitation
The URL picker shows and opens the first 9 URLs. If a message has more than 9 URLs, it is easier to copy/paste the link or use terminal link clicking instead.
Bor uses mu's query language. Examples:
# Search in inbox
maildir:/INBOX
# Unread messages
flag:unread
# From a specific sender
from:john@example.com
# Subject contains word
subject:meeting
# Date range
date:1w..now
# Combine queries
from:john subject:meeting flag:unread
MIT. See LICENSE.
See the mu query documentation for more.
bor/
├── __init__.py # Package init
├── app.py # Main Textual application
├── config.py # Configuration handling
├── mu.py # Mu interface
└── tabs/
├── __init__.py
├── base.py # Base tab class
├── message_index.py
├── message.py
├── compose.py
├── attachments.py
└── sync.py
pytest tests/MIT License - see LICENSE file for details.


