Read/write mail offline in your favorite text editor. Notmuch interface for neovim written in Lua
๐จ Notmuch.nvim
A powerful and flexible mail reader interface for NeoVim. This plugin bridges your email and text editing experiences directly within NeoVim by interfacing with the Notmuch mail indexer.
Introduction
Notmuch.nvim is a NeoVim plugin that serves as a front-end for the Notmuch mail indexer, enabling users to read, compose, and manage their emails from within NeoVim. It facilitates a streamlined workflow for handling emails using the familiar Vim interface and motions.
Feature Overview
- ๐ง Email Browsing: Navigate emails with Vim-like movements.
- ๐ Search Your Email: Leverage
notmuchto search your email interactively. - ๐ Thread Viewing: Messages are loaded with folding and threading intact.
- ๐ Attachment Management: View, open and save attachments easily.
- ๐ Inline HTML Rendering: Render HTML email bodies as text via
w3m. - โฌ๏ธ Offline Mail Sync: Supports
mbsyncfor efficient sync processes, with buffer, background, and interactive terminal modes. - ๐ Async Search: Large mailboxes with thousands of email? No problem.
- ๐ท๏ธ Tag Management: Conveniently add, remove, or toggle email tags.
- ๐ป Pure Lua: Fully implemented in Lua for performance and maintainability.
- ๐ญ (WIP) ~~Telescope.nvim Integration: Search interactively, extract URL's, jump
Requirements
- NeoVim: Version 0.10 or later is
vim.system(), vim.b buffer variables, and other modern APIs).
- Notmuch: Ensure Notmuch and libnotmuch library
- w3m (optional): Required for inline HTML
renderhtmlbody = true
- (WIP) ~~Telescope.nvim: File
Installation
You can install Notmuch.nvim using your favorite NeoVim plugin manager.
Using lazy.nvim:
{
'yousefakbar/notmuch.nvim',
config = function()
-- Configuration goes here
local opts = {}
require('notmuch').setup(opts)
end,
}
Using vim.pack:
If you are using nvim v0.12, or above, you can install using the builtin package manager:
vim.pack.add({
'https://github.com/yousefakbar/notmuch.nvim',
})
-- Or to pin to a specific tag/version:
vim.pack.add({ { src = 'https://github.com/yousefakbar/notmuch.nvim', version = 'v0.3.0', -- Or git commit, etc. }, })
Manual Installation:
Clone the repository and add the directory to yourruntimepath:
git clone https://github.com/yousefakbar/notmuch.nvim.git
Usage
Here are the core commands within Notmuch.nvim:
:Notmuch: Lists available tags in your Notmuch database in a buffer.
-- Define a keymap to run :Notmuch and launch the plugin landing page
vim.keymap.set("n", "<leader>n", "<CMD>Notmuch<CR>")
:NmSearch <query>: Executes an asynchronous search based on provided
" Loads the threads in your inbox received today
:NmSearch tag:inbox and date:today
:Inbox [email]: Quick access to your inbox. Optionally filter by
" Open all inbox messages
:Inbox
" Open inbox for a specific account :Inbox work@example.com
Configuration Options
You can configure several global options to tailor the plugin's behavior:
| Option | Description | Default | | :----------------- | :-----------------------------------------------------------------------------: | :------------------------------ | | notmuchdbpath | Directory containing the .notmuch/ dir | From notmuch config | | maildirsynccmd | Bash command to run for syncing maildir | mbsync -a | | sync.sync_mode | Sync display mode: "buffer", "background", or "terminal" (PTY with stdin) | buffer | | queries | Saved/pinned queries shown at top of :Notmuch dashboard; hidden when empty | {} | | keymaps | Configure any (WIP) command's keymap | See config.lua[1] | | open_handler | Callback function for opening attachments | Runs OS-aware open[2] | | viewhandler | Callback function for converting attachments to text to view in floating window | See defaultview_handler()[2] | | renderhtmlbody | Render HTML email bodies inline using w3m (requires w3m installed) | false | | threadviewmode | Thread view mode: "threaded", "newest-first", or "oldest-first" | "threaded" | | suppressdeprecationwarning | Suppress the warning shown when using deprecated notmuch API (< 0.32) | false |
[1]: https://github.com/yousefakbar/notmuch.nvim/blob/main/lua/notmuch/config.lua [2]: https://github.com/yousefakbar/notmuch.nvim/blob/main/lua/notmuch/handlers.lua
Example configuration in plugin manager (lazy.nvim):
{
"yousefakbar/notmuch.nvim",
opts = {
notmuchdbpath = "/home/xxx/Documents/Mail",
maildirsynccmd = "mbsync personal",
sync = {
sync_mode = "buffer" -- OR "background" OR "terminal"
},
keymaps = {
sendmail = "<C-g><C-g>",
},
renderhtmlbody = true, -- Render HTML emails inline (requires w3m)
queries = {
{ name = "๐ค Sent today", query = "tag:sent and date:today" },
{ name = "โ ๏ธ IMPORTANT", query = "tag:flagged or tag:pr or tag:urgent" },
{ name = "โ Overdue (+3d)", query = "tag:inbox and date:..3d" },
},
threadviewmode = "threaded", -- OR "newest-first" OR "oldest-first"
},
},
Customizing Attachment Handlers
The plugin provides two handlers for working with attachments:
Open Handler: Opens attachments externally with your system's default application. The default handler automatically detects your OS and uses open (macOS), xdg-open (Linux), or start (Windows).
View Handler: Converts attachments to text for display in a floating window within Neovim. The default handler supports HTML, PDF, images, Office documents, Markdown, archives, and plain text files. It tries multiple CLI tools for each format and falls back gracefully if tools aren't available.
To customize either handler, pass a function to setup():
require('notmuch').setup({
-- Custom open handler
open_handler = function(attachment)
-- attachment.path contains the full file path
vim.fn.system({ 'my-custom-opener', attachment.path })
end,
-- Custom view handler view_handler = function(attachment) -- Must return a string to display in the floating window local path = attachment.path if path:match('%.pdf$') then return vim.fn.system({ 'pdftotext', '-layout', path, '-' }) end return vim.fn.system({ 'cat', path }) end, })
The default handlers are defined in lua/notmuch/handlers.lua and handle many common formats out of the box. Only override them if you need specific behavior.
Statusline Integration
When viewing a thread, the plugin exposes buffer-local variables that can be used for statusline integration or other extensibility purposes:
| Variable | Description | | :------- | :---------- | | vim.b.notmuch_thread | Thread metadata (ID, subject, tags, authors, message count) | | vim.b.notmuch_messages | Array of all messages with line positions and metadata | | vim.b.notmuch_current | Cursor-tracked current message (updates on CursorMoved) | | vim.b.notmuch_status | Pre-formatted statusline string (e.g., "2/5 John Doe ๐1") |
Example statusline integration with lualine:
require('lualine').setup({
sections = {
lualine_c = {
{
function() return vim.b.notmuch_status or '' end,
cond = function() return vim.bo.filetype == 'mail' end,
},
},
},
})
License
This project is licensed under the MIT License, granting you the freedom to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies. The MIT License's full text can be found in the LICENSE section of the project's documentation.
For more details on usage and advanced configuration options, please refer to the in-depth plugin help within NeoVim: :help notmuch.