Terminal-based email reader using mu and textual
Project description
Bor - Terminal Email Reader
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.
Some screenshots
Message index
Tabbed message reading
In-terminal preview using kitty terminal protocol
Features
- 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
Requirements
- Python 3.10+
- mu (maildir indexer)
- Textual library
- Optional: html2text for HTML email rendering
- Optional: kitty terminal for image preview
Known issues
- 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.
- Clicking with mouse on the link does not work. For emails with one or two links, 'O' shortcut is preferrable, but for complex long messages, clicking on the link would be better.
Installation
From PyPI
pip install bormail
From source
# 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
Dependencies
# 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
Release
- 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
Configuration
Copy the example configuration file and customize it:
cp bor.conf.example ~/.config/bor.conf
Edit ~/.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.
Usage
Start Bor:
python -m bor.app
Or if installed:
bor
Keyboard Shortcuts
Global
| Key | Action |
|---|---|
| Alt+0 | Message Index tab |
| Alt+1-9 | Switch to tab |
| Ctrl+Q | Quit |
Message Index
| 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 |
Message View
| 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 |
Compose
| Key | Action |
|---|---|
| Tab | Autocomplete address/text |
| Ctrl+L L | Send message |
| Ctrl+L D | Save draft |
| Ctrl+L X | Cancel |
| Ctrl+I | Insert file |
| Ctrl+A | Attach file |
Attachments
| 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.
Search Queries
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
License
MIT. See LICENSE.
See the mu query documentation for more.
Development
Project Structure
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
Running tests
pytest tests/
License
MIT License - see LICENSE file for details.
Credits
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file bormail-0.5.0.tar.gz.
File metadata
- Download URL: bormail-0.5.0.tar.gz
- Upload date:
- Size: 70.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
41494385302097138d32e89c27b21b40b5ddb7b95b8e7457592afa04b4e0f5da
|
|
| MD5 |
da81f8c3b72a4b9e1c8eedc06b982a4f
|
|
| BLAKE2b-256 |
44a7f112aaf1161c12915f55c67b287e176f215fea241c3360d96b7131d28dd9
|
Provenance
The following attestation bundles were made for bormail-0.5.0.tar.gz:
Publisher:
build.yml on slosar/bor
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
bormail-0.5.0.tar.gz -
Subject digest:
41494385302097138d32e89c27b21b40b5ddb7b95b8e7457592afa04b4e0f5da - Sigstore transparency entry: 1039334943
- Sigstore integration time:
-
Permalink:
slosar/bor@88bc9c0044c72b3f9b5d537c8f25596dc4985740 -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/slosar
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
build.yml@88bc9c0044c72b3f9b5d537c8f25596dc4985740 -
Trigger Event:
release
-
Statement type:
File details
Details for the file bormail-0.5.0-py3-none-any.whl.
File metadata
- Download URL: bormail-0.5.0-py3-none-any.whl
- Upload date:
- Size: 62.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d4c0d4b7d8d8aebd377f8d1a8f6ee152a18fb1f2229e79f56f4b6914f802d99f
|
|
| MD5 |
2b449999bf0e9eabff75b0a508dfe033
|
|
| BLAKE2b-256 |
26be4ee0448132d94007bd1e9863dadd695cc0bd768b48e9bc40ce21c66ebf7d
|
Provenance
The following attestation bundles were made for bormail-0.5.0-py3-none-any.whl:
Publisher:
build.yml on slosar/bor
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
bormail-0.5.0-py3-none-any.whl -
Subject digest:
d4c0d4b7d8d8aebd377f8d1a8f6ee152a18fb1f2229e79f56f4b6914f802d99f - Sigstore transparency entry: 1039334986
- Sigstore integration time:
-
Permalink:
slosar/bor@88bc9c0044c72b3f9b5d537c8f25596dc4985740 -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/slosar
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
build.yml@88bc9c0044c72b3f9b5d537c8f25596dc4985740 -
Trigger Event:
release
-
Statement type: