A unified Python tool for reading, replying, and composing HTML emails in mutt/neomutt.
Handles Outlook/OWA emails with Markdown composition, terminal rendering, and browser viewing.
- TUI rendering — styled terminal output via
html2text --colourwith Tokyo Night Storm ANSI colors (bold→purple, headers→blue, forwarded headers→dim gray, Teams boilerplate stripped) - Browser viewing — full HTML with inline CID images resolved, opens in default browser
- Markdown reply/compose — write in markdown, send as HTML preserving Outlook thread formatting
- Obsidian-style extensions: callouts (
> [!note]), task checkboxes ([/][-][>]),strikethrough, definition lists, fenced code, tables - Inline image CID handling for both replies and new messages
- Reply detection via
In-Reply-TowithReferencesheader fallback - Graceful fallback to new message mode when notmuch can't find the original
- Built-in email trimming (
mutt-trim) - Notmuch integration for message lookup
# From GitHub
uv tool install "muttlook @ git+https://github.com/monkeyxite/muttlook.git" --force
# From local clone
uv tool install -e . --forceclick, mail-parser (>=3.9.3), mail-parser-reply, markdown (>=3.1.1), pymdown-extensions (>=10.0), rich (>=13.0), shortuuid
html2text— Rust crate (cargo install html2text-cli) for TUI rendering (--colourmode)- Pandoc — HTML template processing for new messages
- Notmuch — finding original messages by Message-ID
- Neomutt — MUA integration
| Action | Input | Output | Use case |
|---|---|---|---|
--action tui <file> |
Raw HTML file | Styled ANSI text to stdout | Neomutt mailcap pager, nm-html-extract |
--action tui (stdin) |
Full RFC822 email | Styled ANSI text to stdout | Pipe from notmuch show |
--action tui-rich |
Raw HTML file or RFC822 | Rich-styled ANSI text | Experimental alternative renderer |
--action view (stdin) |
Full RFC822 email | Opens HTML in browser | ,w macro, nms Ctrl+O |
--action draft (stdin) |
Email draft from neomutt | HTML file + mutt_cmd | ,m macro, nms Ctrl+R |
--action clean |
— | Removes temp files | Send hook cleanup |
Mailcap entry — renders HTML emails inline in the neomutt pager:
text/html; muttlook --action tui %s; nametemplate=%s.html; copiousoutput;
nm-html-extract extracts the HTML part via notmuch and pipes to muttlook:
fzf --preview → nm-html-extract → notmuch show --part=N → muttlook --action tui <file>
Ctrl+O → muttlook --action view (browser)
Ctrl+R → muttlook --action draft (reply via neomutt)
Ctrl+F → notmuch tag (GTD: archive/action/waiting/defer/done)
Reply to Outlook email with markdown:
macro compose ,m "<first-entry>\
<pipe-entry>notmuch new 2>/dev/null; muttlook --action draft<enter>\
<enter-command>set compose_confirm_detach_first=no<enter>\
<enter-command>source ~/.cache/muttlook/mutt_cmd<enter>\
<enter-command>set compose_confirm_detach_first=yes<enter>" "reply with md→html"
View HTML in browser with inline images:
macro pager,attach ,w "<pipe-message>muttlook --action view<enter>" "view HTML in browser"
# Summary
- Action item 1
- Action item 2
| Name | Task |
|------|------|
| Alice | Review |
> [!note] Reminder
> Meeting moved to Friday
- [x] Done
- [ ] Pending
- [/] In progress
- [-] Cancelled
- [>] Deferred
~~cancelled item~~
Term
: Definition6. Neovim Telescope live search (nvim-mail)
Search, preview, reply, and triage mail directly from Neovim via telescope:
-- <leader>sm opens live notmuch search (via nm-livesearch)
require('telescope').load_extension('nvim_mail')| Key | Action | Muttlook role |
|---|---|---|
Ctrl+d/u |
Scroll preview | --action tui renders preview |
Ctrl+o |
View in browser | --action view (CID images resolved) |
Ctrl+r |
Reply in nvim buffer | Marker added for --action draft on send |
Ctrl+l |
Full styled preview split | nm-html-extract → --action tui |
Ctrl+t |
GTD tag | — |
Enter |
Open in neomutt | — |
Preview uses nm-html-extract → muttlook --action tui for styled rendering. Reply creates a draft with muttlook marker — on send (,m), --action draft joins the thread with full HTML history.
Input HTML
→ Charset auto-detect (from <meta> tag)
→ Outlook MsoNormal paragraph merge
→ html2text --colour (Rust binary, 120 cols)
→ ANSI color remap:
bold yellow (38;5;11) → purple (35)
# headers → bold blue (1;34) / cyan (1;36)
forwarded headers (From/Sent/To/Cc) → dim gray (90)
Original/Forwarded separators → dim gray (90)
→ Teams/Zoom boilerplate strip
→ Blank line collapse
→ stdout
Colors use standard ANSI 16 codes, mapped through kitty's Tokyo Night Storm theme.
mail-parser-replyseparates your reply from quoted content- Obsidian callouts (
> [!type]) → styled HTML divs - Obsidian checkboxes (
[/]→ ◐,[-]→ ―,[>]→ ▷) - Markdown → HTML via
pymdown-extensions(tables, tasklist, tilde, fenced_code, def_list) - Reply detection:
In-Reply-To→References(last entry) → new message fallback - Original message fetched via
notmuch search --output=files - Inline images → CID attachments (multipart/related)
- HTML reply embedded into original email's HTML structure
mutt_cmdgenerated for neomutt to attach HTML as MIME alternative
uv tool install -e . --force # Install editable
ruff check src/ # Lint
ruff format src/ # Format
uv run --with pytest pytest tests/ -v # Test (27 tests)- mu4e-mimelook — Original inspiration
- Konfekt/mutt-trim — Original Perl trimming logic
- html2text — Rust crate for HTML→ANSI terminal rendering
