A minimal email TUI where you read with Markdown and write in Neovim.
neomd.ssp.sh/docs
email
markdown
neovim
tui
Changelog#
2026-04-30#
- Send-only accounts (
imap_disabled = true) — accounts can be marked as send-only by settingimap_disabled = true; neomd skips IMAP connection, folder fetching, and screening for that account; the account remains available as a From address viactrl+fin compose/pre-send;ctrl+aaccount cycling skips disabled accounts; useful for adding Gmail or other providers purely for sending without fetching thousands of emails;:debugshows "(imap disabled)" label
2026-04-28#
- Spy pixel blocking — neomd automatically detects and blocks tracking pixels in emails using a two-layer approach (same as HEY): (1) a curated denylist of 150+ tracking services sourced from Simplify (BSD-3-Clause), LeaveMeAlone (CC-BY 3.0), and DHH's original HEY list (MIT) — matches are attributed by service name (e.g. "Mailchimp", "HubSpot", "SendGrid"); (2) a generic 1×1 pixel heuristic (empty alt + tiny dimensions or CSS hiding) catches custom/branded tracking domains not on the list;
°indicator in the inbox list for emails with tracking pixels; reader header shows° N spy pixel(s) blocked (ServiceName)with tracker attribution; senders cannot tell if you read their email in the TUI since glamour never fetches remote resources - Spy pixel scan (
<space>S/:scan-spy-pixels) — scan all emails in the current folder for tracking pixels in the background; fetches full UID list from IMAP server, skips already-scanned emails, uses IMAP PEEK (won't mark as read); results cached in~/.cache/neomd/spy_pixelsand persist across restarts; both positive and negative scan results are cached so repeat scans are instant - URL scheme whitelist — email links opened via
space+digitare now validated; onlyhttp://,https://, andmailto:schemes are allowed;javascript:,data:, and other dangerous schemes are blocked with an error in the status bar - Dangerous attachment warning — two layers of protection: (1) files with executable extensions (
.sh,.exe,.desktop,.bat,.py,.jar, etc.) are saved but not auto-opened; (2) magic-byte verification using Go'snet/http.DetectContentType()catches disguised files (e.g. a script renamed to.pngis detected astext/plainand blocked); status bar warns about dangerous or suspicious file types - Browser view sanitization — pressing
Oto open email in browser now injects a Content-Security-Policy that blocks JavaScript, iframes, and embedded objects (script-src 'none'; frame-src 'none'; object-src 'none') while allowing remote images - Reader space chord hints — pressing
spacein the reader now shows all available actions (1-0 links,d download .eml,l11-99 links 11+) instead of only link info;space+dfor EML download now works even when no links are present - Colored attachments in reader — attachment filenames in the reader header are now rendered in waveAqua2 color instead of dim gray for better visibility
- Panic recovery — all background goroutines (mark-as-read, spy pixel cache, temp file cleanup) are now wrapped with
safeGo()which recovers panics instead of crashing the TUI; panics are logged to~/.cache/neomd/crash.logwith timestamp and full stack trace for post-mortem debugging - IMAP connection health check — after 2+ minutes of inactivity (e.g. laptop suspend/resume), neomd probes the connection with IMAP NOOP before the next operation; if the connection is dead, it automatically reconnects — no more manual
Rrefresh needed after sleep - IMAP retry for read-only operations — read-only IMAP commands (FETCH, SEARCH, STATUS) automatically retry once after reconnecting on network error; mutating operations (MOVE, APPEND, STORE) are NOT retried to prevent duplicate emails or replayed mutations
- MIME charset/encoding fallback — emails with unknown charsets (ISO-8859-15, Windows-1256) or unknown transfer encodings no longer fail; neomd continues with raw bytes instead of crashing, matching aerc's graceful degradation pattern
- Config validation — config is now validated on load: IMAP/SMTP addresses checked for valid
host:portformat with port range 1-65535, required fields enforced, UI values checked for non-negative ranges; clear error messages instead of silent failures - Integration tests for security features — new
TestIntegration_SecurityFeatures(disguised attachment + callout) andTestIntegration_BrowserSanitization(CSP script/iframe blocking) send real test emails for live inspection
2026-04-27#
- Mailto handler (
--mailto/ positional URI) — neomd can now be used as the system defaultmailto:handler; clicking amailto:link in any browser opens a foot terminal with neomd in compose mode, pre-filled with To, CC, BCC, Subject, and Body from the URI; supports bothneomd --mailto "mailto:user@example.com?subject=Hello"andneomd "mailto:..."(positional, for.desktopintegration); registered viaxdg-mimewith aneomd-mailto.desktopfile; after sending or cancelling, neomd continues as normal
2026-04-24#
- Disable threading in Sent folder — the Sent tab now shows each email individually without thread grouping, ordered by date; threading remains active in all other folders; useful because Sent emails are your own outgoing messages and don't benefit from conversation grouping
2026-04-23#
- Download raw email source (
space+din reader) — saves the full raw MIME source as.emlto~/Downloads/with filenameneomd-YYYYMMDD-<subject>.eml; useful for archiving, debugging email headers, or importing into other clients; status bar shows download progress and completion; filenames deduplicated automatically - Listmonk newsletter integration — send newsletters to subscribers by composing an email to a virtual trigger address (e.g.
listmonk@ssp.sh); neomd intercepts the send and creates a scheduled campaign in Listmonk via its REST API instead of delivering via SMTP; configure multiple trigger addresses in[[listmonk.triggers]]to target different subscriber lists (newsletter, book, all); pre-send screen shows "Newsletter via Listmonk" with target list IDs and schedule delay; campaigns are created as draft then set toscheduledstatus with configurable delay (default 30 minutes); authentication via HTTP Basic Auth with environment variable expansion for API token; new self-containedinternal/listmonk/package with full test coverage (httptest mocks); documented indocs/integrations/listmonk.md
2026-04-21#
- RFC 5322 compliant Message-ID — Message-IDs now use the sender's domain instead of hardcoded
@neomd; ensures proper email threading, spam filter compatibility, and domain reputation consistency; usesnet/mail.ParseAddress()for robust RFC 5322 address parsing; validates From address before sending and rejects invalid addresses that would result in@localhostMessage-IDs; added comprehensive test coverage for BuildMessage, BuildDraftMessage, and BuildReactionMessage paths; documented email standards compliance indocs/email-standards.md - Fix: From validation allows local-only addresses —
extractDomain()now returns(domain, ok bool)to distinguish between parsing failures (invalid address) and validuser@localhostaddresses; validation only rejects unparseable addresses, not legitimate local mail system configurations; prevents regression that would have blocked valid RFC 5322 addresses - Fix: test nil dereference guard —
TestBuildMessage_InvalidFromnow usest.Fatalf()when validation incorrectly succeeds, preventing nil pointer panic onerr.Error()if test regresses - Fix: potential memory leak in background sync — fixed infinite loop that occurred when IMAP errors (e.g., after suspend/resume) triggered immediate retry instead of waiting for next scheduled interval;
bgFetchInboxCmd()now returns nil on error instead ofbgSyncTickMsg{}, preventing tight loop that consumes large amounts of RAM; addedbgSyncInProgressflag that covers the entire fetch-and-screen cycle (kept set untilbgScreenDoneMsg), preventing concurrent background syncs from piling up during slow network or long screening operations - Fix: reply-all excludes all own addresses —
ctrl+rreply-all now excludes both IMAP login addresses (account.User) and send-as addresses (account.From,sender.From) from the CC field; fixes edge cases whereuser != from(e.g., login asuser123@provider.combut send assimon@domain.com) would still leak the login address into CC; previously only excludedFromaddresses. Added test suite added covering single/multi-account, sender aliases, case sensitivity, and named addresses
2026-04-18#
- Headless daemon mode (
--headless) — run neomd as a background daemon on a server (NAS, VPS, always-on device) to continuously screen emails without launching the TUI; daemon fetches inbox and auto-screens everybg_sync_intervalminutes (configurable in config); watches screener list files (~/.config/neomd/lists/*.txt) and reloads when they change (designed for Syncthing multi-device sync); graceful shutdown on SIGTERM/SIGINT; structured logging to stdout withlog/slog; use case: run daemon on NAS withbg_sync_interval = 5, disable background sync on laptop/Android withbg_sync_interval = 0, classify senders in TUI, Syncthing syncs lists to NAS, daemon auto-screens incoming emails so mobile IMAP apps see correctly filtered folders; perfect for using neomd's screener with native mobile email clients; daemon only reads screener lists and moves emails (never modifies lists), all sender classification happens in TUI; includes comprehensive tests for classification logic and daemon lifecycle; documented indocs/configurations/headless.mdwith Syncthing setup guide, systemd service example, and multi-device workflow;make daemontarget for testing - Fix: headless screening paused when lists empty — daemon now checks
screener.IsEmpty()and skips screening when all lists are empty, mirroring TUI behavior; prevents sweeping entire inbox to ToScreen on first run or before Syncthing completes initial sync; logs "screening paused: screener lists are empty (classify your first sender to activate)" until first classification exists; regression test added - Fix: cross-list cleanup on reclassification — all 5 screener classification functions (
Approve,Block,MarkSpam,MarkFeed,MarkPaperTrail) now remove email addresses from ALL conflicting lists before adding to the target list; fixes bug where reclassifying a sender (e.g., Feed → ScreenedOut) would leave the address in bothfeed.txtandscreened_out.txt, causing duplicates in screener state and sync conflicts across devices; previously onlyApprove,Block, andMarkSpamhad partial cleanup logic, whileMarkFeedandMarkPaperTrailonly appended without removing; reclassification is now atomic using snapshot/restore: captures screener state before starting, checks all file operation errors (no longer silently ignored), and restores the snapshot on any failure to prevent partial writes or lost classifications; comprehensive regression test added covering Feed→ScreenedOut, PaperTrail→Feed, full reclassification chain (Inbox→Feed→PaperTrail→ScreenedOut→Spam→Inbox), and persistence after reload; all cleanup operations verified both in-memory and on-disk - Cross-compile FreeBSD binary on build —
make buildnow automatically createsneomd-freebsdstatic binary alongside Linux binary; FreeBSD binary can be copied to FreeBSD/OpenBSD servers without needing Go installed;make sync-headlesstarget copies to remote server via scp
2026-04-17#
- GitHub/Obsidian-style callouts in emails — compose emails with callout syntax
> [!note],> [!tip],> [!warning]for styled alert boxes in HTML emails; rendered with colored left borders, subtle backgrounds, and emoji icons using Kanagawa theme colors (crystalBlue, springGreen, carpYellow, oniViolet, autumnRed); compact spacing with emoji and title matching body text size (15px) for minimal visual intrusion; supports custom titles (> [!note] Custom Title), multiple paragraphs, and nested callouts; always expanded (no collapsible behavior), no JavaScript required; works in both syntaxes:> [!note](with space) or>[!note](without space); plain text emails format callouts as emoji text without blockquote markers (readable in neomd reader and plain text clients); uses local fork of goldmark-obsidian-callout with email-optimized rendering; same syntax used in neomd's README now works in your composed emails - Timer-based mark-as-read — emails are no longer marked as read immediately when opened; instead, a configurable timer (default 7 seconds) starts when you enter the reader; if you stay for the full duration, the email is marked as
\Seen; if you exit early (quick peek), it stays unread; prevents accidental marking when browsing through emails mark_as_read_after_secsconfig — new[ui]option to control mark-as-read delay in seconds (default 7); set to0for immediate marking (old behavior); set to any value to customize the delay- Fix: local UI state sync on mark-as-read — inbox list now updates immediately when an email is marked as read, either via timer or manual toggle (
n); previously the server was updated but the local UI showed stale unread indicators until manual refresh
2026-04-16#
Bmove to Work/business — pressBto move marked or cursor email(s) to Work folder (similar toAfor Archive); quick single-key action without screener list updates; shows friendly error if Work folder not configured; useful for rapid GTD-style email processing; complements existinggb(go to Work) andMb(move to Work) shortcuts- Redesigned welcome screen — new two-column layout with ASCII art logo, philosophy/getting started guide on the left, and essential shortcuts organized by category on the right; wider box (100 chars) with cleaner spacing; maintains kanagawa color scheme; more scannable and visually appealing for new users
- ASCII logo in help overlay — pressing
?now shows the neomd ASCII art logo overlaid on the top-right corner of the help screen; shortcuts start immediately at the top without vertical space taken by the logo; logo only appears when scrolled to the top space+wwelcome shortcut — pressspacethenwto reopen the welcome screen anytime; useful for reviewing keybindings and getting started guide; documented in help overlay and keybindings referenceNjump to next unread — pressNto jump to the next unread email in the current folder; wraps around to the beginning if no unread found after cursor; displays status message if no unread emails existztoggle unread-only view — presszto filter the inbox to show only unread emails (mnemonic: "zero in on unread"); presszagain to show all emails; works alongside text filter (/) and can be cleared withesc; status bar indicates current view mode- Fix: help menu search — all keys (including
j,k,d,u) are now available for typing when searching in help overlay (?then/); scroll keys only work when not in search mode
2026-04-15#
- Scheduled folder keybindings — added
gc(go to Scheduled, mnemonic: "calendar") andMc(move to Scheduled) shortcuts; Scheduled folder now accessible via dedicated keybindings alongside existing tab navigation ([]HL,space+1-9); help overlay and generated keybindings documentation updated
2026-04-14#
- Extended link support (99 links) — link opener now supports up to 99 links per email (previously limited to 10);
space+1-0opens links 1-10,space+l11-99opens links 11-99 using intuitive numeric shortcuts (e.g.space+l26for link [26]); status line provides progressive feedback during multi-key input; footer help and?overlay updated - Fix: link extraction with brackets in text — markdown link regex now correctly matches links with brackets inside the link text (e.g.
[[Watch the studio tour here]](url)); changed from[^\]]+(anything except]) to non-greedy.+?to handle nested brackets; fixes newsletter links from Beehiiv and similar services
2026-04-13#
- Emoji reactions (
ctrl+e) — fast, keyboard-driven emoji reactions from inbox or reader; pressctrl+eto open emoji picker overlay, select with1-8for instant send or navigate withj/kand pressenter; sends minimal reaction email (emoji + italic footer + quoted original message) with proper threading headers; available reactions: 👍 ❤️ 😂 🎉 🙏 💯 👀 ✅; original email marked with\Answeredflag; reaction saved to Sent folder; auto-selects From address matching recipient (same logic as regular replies) - Email threading headers — all replies (regular
r/Rand emoji reactionsctrl+e) now include properIn-Reply-ToandReferencesheaders for conversation threading; ensures replies appear correctly grouped in Gmail, Outlook, and Apple Mail conversation views;Referencesheader extracted from IMAP message body and preserved in reply chain - Fix: refresh not showing new emails immediately — pressing
Rnow correctly displays new emails on first refresh; previously the IMAP client cached the selected mailbox state, so the firstRwould skip re-SELECT and use stale UID SEARCH results (showing the old unread count but no new messages in the list); required a secondRor tab switch to see new emails; now forces a fresh SELECT to ensure mailbox state is current; also fixed background sync path to prevent stale cache; added regression test (internal/imap/client_test.go:410)
2026-04-10#
- HTML signature support — new
[ui.signature_block]config with separatetextandhtmlfields for dual-format signatures; text signature appears in the editor and text/plain MIME part, HTML signature appends to the text/html part only; use[html-signature]placeholder in text signature to control HTML signature inclusion per-email (visible in preview, deletable before sending); backward compatible with legacysignaturefield - Fix: draft formatting corruption — drafts are now stored as plain text only instead of multipart/alternative to prevent HTML→markdown conversion artifacts; fixes line break addition, pipe escaping (
|→\|), and italic style changes (*→_) when reopening saved drafts - Sent/Drafts primary-account default restored — in multi-account setups, Sent and Drafts now default back to the first configured IMAP account while SMTP still uses the selected sending identity; added
store_sent_drafts_in_sending_account = truefor users who want Sent/Drafts to follow the sending account instead - Proton Mail Bridge compatibility — documented that Proton Mail works with neomd only via Proton Mail Bridge (paid Proton feature), added optional
tls_cert_filesupport for trusting Bridge’s exported self-signed certificate, and added a narrow localhost-only TLS retry fallback for Bridge connections on127.0.0.1/localhost; normal remote IMAP/SMTP providers keep their existing strict certificate verification behavior - Issue #6 verification pass — reviewed the user report against the current code and specifically verified that startup auto-screening does not route Inbox mail to Trash in the current implementation, while manual
ToScreenscreening remains message-by-message by design - Fix: Drafts/Spam reload off-tab folder mismatch — reloading while viewing an off-tab folder now reloads that actual mailbox instead of the currently selected tab's folder; fixes the confusing case where Drafts could show Inbox content after pressing
R - Fix: committed
/filter now clears withesc— pressingescnow reliably clears the in-memory inbox filter even after the filter was already applied - Help overlay improvements —
?help is now scrollable withj/k, arrow keys, andd/u; search begins only after pressing/, so opening help no longer immediately behaves like a search prompt - Attachment workflow guidance — startup/welcome messaging now warns when the optional inline Neovim attachment integration is unavailable; README install docs now list
yaziand the externalcustom.luaintegration as optional requirements for<leader>a, while clarifying that pre-sendastill works independently - UX hints — inbox footer now exposes
, sort; pre-send footer clarifiessas spell-check-and-edit versus plaineedit; compose/pre-sendctrl+fnow shows a message when only one From identity is configured - Fix: sender-level screening from
ToScreen— approving/blocking/feed/papertrail/spam on a single unmarked message inToScreennow expands to all currently queued mail from that sender, matching the intended HEY-style workflow - Safety guard: screener destinations may not point to Trash — screening now refuses to run if
ToScreen,ScreenedOut,Feed,PaperTrail, orSpamare configured to the same IMAP folder as Trash - Inbox paging clarity — the inbox header now shows the current fetch limit (
loaded/limit) andd/upage movement directly, so the “only 50 emails” behavior is visible without guessing - Discard confirmation for unsent mail —
escin compose andesc/xin pre-send now ask for confirmation before dropping the message; recovery hints still point to:recover - Default Inbox load raised to 200 — new configs now use
inbox_count = 200; README, config docs, and welcome text now clarify that normal loads/auto-screening only process that loaded Inbox slice, while:screen-allscans the full Inbox on the IMAP server - Compose/draft round-trip preservation — editor/pre-send/draft/recover flows now preserve
Bccand selectedFrom; continuing a draft also restores its attachments back into the compose session - Correct IMAP account for Sent/Drafts — sent copies and saved drafts now use the IMAP account that matches the selected sending identity /
[[senders]]alias instead of always using the currently active inbox account - Draft MIME keeps
Bcc— Drafts saved via IMAP now retain theBccheader so reopening a draft does not silently lose hidden recipients - Search/Everything/Thread subjects no longer mutate — folder prefixes are now display-only in list rendering, so reply/forward/thread logic keeps using the real RFC subject
- Screener rollback safety — screener actions now snapshot list state and roll back both list files and already-moved emails if a later move fails, keeping mailbox state and screener files consistent
:searchhelp text fixed — the command description now correctly says it searches across configured folders, not just the current folder
Roborev#
- Security: path traversal vulnerability fixed — inline image handling (
Obrowser preview) now sanitizesContentIDandFilenamefrom email MIME headers to prevent attackers from writing files outside/tmp/neomd/via maliciouscid:references (e.g.../../etc/cron.d/evil); all attachment paths now usefilepath.Base()and verify the result stays under temp directory before writing - Fix: conversation view navigation — pressing
T(thread view) now correctly shows error messages and empty-result warnings;imapSearchResultsflag is cleared immediately so the status bar appears instead of the search bar; added general Esc handler foroffTabFolderviews that preserves search context: pressing Esc from thread view returns to IMAP search results if that's where you came from (checked viaimapSearchText), otherwise returns to active folder;imapSearchTextis cleared when navigating away from search via tab, clicks, or go-to commands (gi/ga/etc) to prevent stale search context from affecting unrelated views; search retry errors are now visible becauseimapSearchResultsis only set by the handler on success - Fix: Work folder move guard —
Mb(move to Work) is now disabled when the Work folder is not configured, preventing moves to an empty folder name; previously caused silent failures - Fix: Work folder keybindings in help —
gb(go to Work) andMb(move to Work) now appear in the?help overlay and generated keybindings documentation, marked as "(if configured)" to indicate they're optional - Test coverage: expandEnv edge cases — added unit tests for environment variable expansion covering unset variables (silently return empty), bare
$alone, empty${}, whitespace trimming, and variables with text suffixes/prefixes; documents current behavior for config password/user fields - Fix: welcome message formatting — onboarding screen instruction now reads clearly ("Go to Inbox tab; once screener is active, use ToScreen") instead of the previous formatting regression ("ToScreentab")
2026-04-09#
- Fix: non-standard IMAP/SMTP ports — neomd now correctly handles non-standard ports (e.g., Proton Mail Bridge on
127.0.0.1:1143and127.0.0.1:1025); previously hardcoded port-based logic ignored the user'sstarttlsconfig and refused unencrypted connections to any port other than 993/143 (IMAP) or 465/587 (SMTP); new behavior: user's explicitstarttls = truealways forces STARTTLS, standard ports use their defaults (993→TLS, 143→STARTTLS, 465→TLS, 587→STARTTLS), non-standard ports default to TLS for security (user must setstarttls = trueif their provider uses STARTTLS on a custom port); fixes "refusing unencrypted connection to 127.0.0.1:1143" error reported by Proton Bridge users; comprehensive test coverage added for all port/config combinations
2026-04-08#
- Fix: pre-send
elosing email body — pressingein the pre-send review to re-edit now correctly reopens the editor with the existing body; previously it opened a blank compose with only the signature, silently discarding the email content (including reply history) - Draft backups — every compose session is automatically backed up to
~/.cache/neomd/drafts/before the temp file is deleted; keeps a rolling 20 backups (configurable viadraft_backup_countin[ui], set to-1to disable); no more lost emails after crashes or accidental closes :recover/:reccommand — reopens the most recent draft backup as a compose session; To/Cc/Bcc/Subject are parsed from the backup and pre-filled automatically- Screener docs: "screening happens once" — documented that auto-screening only runs on the Inbox folder; emails moved to ToScreen by another device are not re-classified; use
:reset-toscreento move them back for re-screening - Test suite — 147 unit tests across 8 packages covering screener classification, MIME message building, editor parsing, config loading, IMAP search, OAuth2 token handling, rendering, and security invariants (file permissions, BCC privacy, credential leak prevention); CI workflow runs
go test+go veton every PR - Integration tests (
make test-integration) — end-to-end tests against a real IMAP/SMTP server: send plain email and verify From/To/Subject/HTML body round-trip, CC header, file attachment content, non-ASCII subject encoding (umlauts + emoji), IMAP search withfrom:/subject:prefixes, move + undo, inline images, signature HTML rendering, SaveSent IMAP APPEND, comma-separated multiple recipients, and reply-all with 3 distinct addresses; all test emails cleaned up automatically; skipped without credentials somake teststays fast and offline - Fix: multiple To recipients —
Send()now correctly splits comma-separated To addresses into individual SMTP RCPT TO commands; previously the entire"a@x.com, b@x.com"string was passed as a single address, causing delivery failures - Fix: To/CC display — reader and inbox now show all To and CC addresses, not just the first;
FetchHeadersByUID(used by search/everything) now also populates To and CC fields - Reply-all rebind to
ctrl+r—R(Shift+R) is now consistently reload/refresh in all views; reply-all moved toctrl+rwhich works from both inbox list and reader (previouslyRconflicted between reload in inbox and reply-all in reader) - Default signature for new users — new installs get
*sent from [neomd](https://neomd.ssp.sh)*as the default signature - Reply indicator (
·) — emails you've replied to show a·dot in the inbox list between the flag and thread columns; uses the standard IMAP\Answeredflag so it works across clients (reply from webmail → neomd shows it) \Answeredflag on reply — after sending a reply, the original email is automatically marked as\Answeredon the IMAP server- Conversation thread view (
T/:thread) — pressTfrom inbox list or reader to see the full conversation across folders (Inbox, Sent, Archive, Waiting, Work, etc.); searches by normalized subject + participant overlap; displays in a temporary "Thread" tab with[Folder]prefix and│/╰threading connectors; esc returns to previous view - Custom folder support (
work) — optionalwork = "Work"in[folders]config; add"work"totab_orderto show as a tab;gbto go,Mbto move; auto-created on first run if configured; included in Everything, Search, and conversation views - Inline images in browser preview — pressing
Oto open an email in the browser now shows inline images from other senders;cid:references are rewritten to temp files so the browser can display them; previously only your own sent emails rendered images correctly compose_editorconfig option — optionalcompose_editorin[ui]to use a different editor for compose/reply/forward (e.g."nvim --appname nvim-wp"); defaults to$EDITOR/nvim
2026-04-05#
- OAuth2 authentication (#3, thanks @notthatjesus) — accounts can set
auth_type = "oauth2"withoauth2_client_id,oauth2_client_secret,oauth2_issuer_url, andoauth2_scopesinstead of a password; on first launch neomd opens the browser for the authorization code flow, persists the token to~/.config/neomd/tokens/<account>.json, and refreshes it automatically; works with Gmail, Office365, and any OIDC-discoverable provider via XOAUTH2 over IMAP and SMTP; password auth paths unchanged for existing accounts auto_bccconfig — root-levelauto_bcc = "addr@example.com"appends an address to every outgoing email's Bcc so you keep a copy in an external mailbox (e.g. a hey.com archive); visible in the composer and pre-send review (no silent BCC), deduped against any manual Bcc entryshift+tabin compose — navigate back through To/Cc/Bcc/Subject fields (previously could only move forward with tab/enter)- Reader shows local time — email dates in the reader header now convert to your system's local timezone and include the clock time (e.g.
Apr 05, 00:51); previously showed the sender's timezone date without time
2026-04-02#
- Auto From on reply — replying auto-selects the From address that matches the email's To/CC field (e.g. email sent to
simon@domain.comreplies fromsimon@domain.com);rnow works from inbox list view;# [neomd: from: ...]shown in editor;xin pre-send discards the email - Email safety hardening — bulk operations show live progress counter ("Screening: 42/1000…") for batches >10; screener now moves emails before updating list files (no inconsistent state on failure); SaveSent failure shown as warning instead of silently swallowed; batch failures report exact moved/total counts; partial batch undo info preserved on error; undo stack capped at 20
- Screener lists created on startup — all 5 screener
.txtfiles are created as empty files on first run (alongside directories), consistent with IMAP folder creation - Config-isolated cache — demo and production configs use separate cache directories (derived from config dir name), so
make demo-resetnever touches production data - Added benchmark to readme as Gmail was considerly slower than my IMAP provider here from Switzerland.
2026-04-01#
- Threaded inbox — related emails are automatically grouped in the inbox list with a Twitter-style vertical connector line (
│/╰); threads detected viaIn-Reply-To/Message-IDIMAP envelope headers with a reply-prefix subject fallback (only emails withRe:,AW:,Fwd:etc. are grouped by subject — recurring notifications/invoices stay separate); newest reply on top, root at bottom; threads sorted by most recent email so active conversations float to the top - Clickable tabs — folder tabs in the top bar are clickable with the mouse; click any tab to switch folders
- Spell check in pre-send (
s) — opens nvim with spell checking enabled (en_us+de), cursor jumps to the first misspelled word; use]s/[sto navigate errors,z=for suggestions,zgto add to dictionary; corrected body flows back to pre-send :debug/:dbgcommand — writes a diagnostic report covering IMAP connectivity (ping test), account config (emails masked), folder mapping, screener list status, UI config, and current state; opens in the reader and saves to/tmp/neomd/debug.logfor sharing; no sensitive data (passwords, full emails) included- Drafts show recipient — Drafts folder now shows
→ recipientinstead of From (same as Sent tab), since all drafts are from you ctrl+bin pre-send — toggle CC/BCC fields from the pre-send review screen (previously only available during compose)u/Urebind —uis now free for page-up (vim-style half-page scroll);Uis undo last move/delete;ctrl+uclears all marks- Temp files in
/tmp/neomd/— all temp files (compose, preview, spell check) now live in/tmp/neomd/subdirectory for easy recovery after crashes and less clutter - Improved onboarding — auto-screening is now paused when screener lists are empty (first run), preventing all emails from being moved to ToScreen; activates automatically once the user classifies their first sender; welcome screen rewritten with step-by-step getting-started guide explaining the screener workflow, batch operations (
m+I), and config hints (:debug,auto_screen_on_load) ]/[folder navigation — bracket keys now switch to next/previous folder tab (alongsideL/Handtab/shift+tab)
2026-03-31#
- fix showing recipient in SENT tab (instead of from)
- IMAP search across all folders (
space /or:search) — server-side IMAP SEARCH across all configured folders (Inbox, Sent, Archive, Feed, etc.); results displayed in a temporary "Search" tab with[Folder]prefix on each subject; supports query prefixes:from:simon,subject:invoice,to:team@, or plain text to search all three fields; pressescto close results - Filter preserves across actions — the local
/filter no longer clears when pressingn(toggle read),m(mark),U(clear marks), or sorting; filter stays active untilesc - Address autocomplete in compose — To, Cc, and Bcc fields show autocomplete suggestions from screener lists (
screened_in.txt,feed.txt,papertrail.txt); navigate withctrl+n/ctrl+p/arrows, accept withtab; supports multi-address fields (autocomplete applies after the last comma) - Everything view (
geor:everything) — shows the 50 most recent emails across all folders in a temporary "Everything" tab, sorted by date descending; each subject prefixed with[Folder]; useful for finding emails that were screened out or moved to spam - Link opener (
space+1-9in reader) — links are extracted from the email body, numbered[1]-[0]in the header; pressspacethen a digit to open in$BROWSER; up to 10 links per email, deduplicated by URL - Draft signature fix — re-opening a draft (
E) no longer appends a duplicate signature; the draft body already contains it from the first compose - Draft reader footer —
E draftnow appears in the reader footer when viewing an email from the Drafts folder - Android support (
make android) — cross-compile for Android ARM64; runs in Termux; documented indocs/android.mdwith install instructions and useful shortcuts - Docs restructure — detailed documentation moved from README to
docs/folder:docs/keybindings.md(auto-generated),docs/screener.md,docs/sending.md,docs/configuration.md,docs/android.md; README kept concise with links
2026-03-30#
- added preview email in $BROWSER (images rendered, same as recipient sees) with
p - Multiple From addresses / SMTP aliases — add
[[senders]]blocks to config to define extra From identities (e.g.s@ssp.shas an alias through an existing account's SMTP); cycle through all accounts + senders withctrl+fin both compose and pre-send screens; theaccount =field matches by accountname =(not email address) - Sent folder — after sending, neomd APPENDs a copy to the configured Sent IMAP folder with
\Seenflag; the same raw MIME bytes used for SMTP delivery are reused for the APPEND (no double-build) - Attachment column in inbox —
@appears in a dedicated column next to the date when an email has attachments (detected from IMAP BODYSTRUCTURE including inline images) - Attachment downloads in reader — the email header now lists all attachments as
[1] report.pdf [2] photo.png; press1–9to download attachment N to~/Downloads/and open it withxdg-open; filenames are deduplicated automatically - Inline images as downloads — images embedded inline in emails (
Content-Disposition: inline, e.g. PNG screenshots) are now shown alongside regular attachments in the reader header and downloadable with1–9; previously onlyContent-Disposition: attachmentparts were listed - Inline image placeholders in reader body —
<img src="cid:...">tags now show[Image: filename.png]at their position in the body text instead of being silently stripped; uses Content-ID → filename mapping from MIME parts - Undo move / delete —
ureverses the last single or batch move/delete (x,A,M*); uses the UIDPLUS destination UID so undo still works even when the server reassigns UIDs on MOVE; screener actions (I,O,F,P,$) are intentionally excluded because they also modify.txtlist files - Subject (and headers) re-parsed from editor — editing
# [neomd: subject: ...],# [neomd: to: ...], etc. in neovim now correctly updates those fields; previously the values were captured in a closure before the editor opened and changes were silently discarded; all three editor entry points (new compose, reply, continue draft) now calleditor.ParseHeaderson the saved file content ctrl+ffor cycling From — changed fromf(which conflicts with typing in text fields) toctrl+f; works in both the compose form and the pre-send review screen- Forward (
f) — forward an email from the reader or inbox; opens the editor with the original message quoted,Fwd:subject prefix, and emptyTo:field; from inbox the body is fetched automatically before opening the editor - Permanent delete (
X, Trash only) — permanently deletes marked or cursor email(s) from the Trash folder via IMAP STORE\Deleted+ UID EXPUNGE; blocked in other folders with a warning message :empty-trash/:et— permanently delete all emails in Trash with y/n confirmation; works from any folder without navigating to Trash first- First-run welcome popup — on the very first launch, a centered popup shows quick-start keybindings and screener basics; any key dismisses it; marker at
~/.cache/neomd/welcome-shownensures it only appears once - Auto-create IMAP folders on startup —
ensureFoldersCmdruns duringInit()so new users don't need to manually run:create-folders; idempotent for existing users - Auto-create screener list directories — parent directories for screener list paths are created automatically during config load; prevents errors when pressing
I/O/F/Pon a fresh install - Default screener paths — changed from
~/.config/mutt/to~/.config/neomd/lists/for new installs; existing configs with custom paths are unaffected - Go prerequisite check in Makefile —
make build/make installnow prints clear Go installation instructions instead of a cryptic error whengois not found - Pre-send preview (
p) — presspin the pre-send screen to open a browser preview of the composed email; renders through the same goldmark pipeline as sending, with local image paths converted tofile://URLs so inline images from[attach]lines display correctly
2026-03-29#
- CC field — compose and reply forms now include an optional Cc field (Tab/Enter to skip); CC recipients receive the email and appear in the
Cc:header - BCC field — hidden by default; toggle with
ctrl+bin compose; BCC recipients receive the email but are not visible in the message headers (standard BCC privacy) - Reply-all —
Rin the reader replies to the original sender + all CC recipients; your own address is excluded automatically; usesReply-Toheader when present - Pre-send review screen — after closing the editor, neomd shows a summary (To, Subject, body preview) before sending; press
enterto send,ato attach files via yazi (auto-detected, no config needed; override with$NEOMD_FILE_PICKER),Dto remove last attachment,dto save to Drafts,eto re-open the editor,escto cancel; avoids tmux/terminal key-capture issues sinceaneeds no modifier - Save to Drafts —
din the pre-send screen APPENDs the composed message to the configured Drafts IMAP folder with\Draft+\Seenflags; navigate to it withgd - Attachments from neovim —
<leader>ain aneomd-*.mdbuffer opens yazi in a floating terminal; selected files are inserted as[attach] /path/to/filelines (visible in markdown, not hidden HTML comments); neomd strips them before sending and adds them as MIME attachments - Inline code and code blocks —
`inline code`and fenced```blocks are rendered in HTML emails (goldmark CommonMark + GFM; styled with monospace font and light grey background)
2026-03-27#
gdDrafts navigation — jump to Drafts folder withgdeven when it's not in the tab rotation- Off-tab folder indicator — when viewing Spam (
gS) or Drafts (gd), the folder name appears highlighted in the tab bar with a│separator; no regular tab stays falsely active - Security hardening — IMAP refuses unencrypted connections (non-993/143 ports error out instead of
DialInsecure); email-extracted URLs validated tohttp/httpsonly before opening in browser (case-insensitive, RFC 3986);SECURITY.mdadded documenting credential storage, TLS guarantees, screener list handling, and temp file lifecycle with links to source - Spam folder —
$marks a sender as spam (writes tospam.txt, moves to Spam IMAP folder). Separate from ScreenedOut so you never have to look at it again. Navigate withgSor:go-spam— kept out of the tab rotation intentionally - Cross-list cleanup — reclassifying a sender removes them from conflicting lists automatically:
I(approve) removes from screened_out + spam;O(block) removes from screened_in;$(spam) removes from screened_in + screened_out. No manual.txtediting needed :command history —↑/↓cycles through the last 5 distinct commands;→accepts the ghost completion;ctrl+n/ctrl+pcycle forward/backward through completions. Persists across restarts in~/.cache/neomd/cmd_history(outside dotfiles version control)- Leader key —
spaceis the leader;<space>1–<space>9jumps to a folder tab by number - Auto-screen on inbox load — screener applies automatically on every Inbox load (startup,
R). Disable withauto_screen_on_load = falsein[ui] - Background sync — inbox re-fetched and screened every 5 minutes while neomd is open. Configure with
bg_sync_intervalin[ui];0disables it n/mrebind —ntoggles read/unread (wasN);mmarks for batch ops (wasspace)
2026-03-25#
- Signature — auto-appended to new compose buffers; configure in
[ui]withsignature - Compose abort — closing the editor with
ZQ/:q!cancels the email; onlyZZ/:wqsends - Browser image workflow —
Oopens email as HTML in$BROWSER;ctrl+oopens the canonical web/newsletter URL (extracted fromList-Postheader);oopens in w3m :create-folders/:cf— creates any missing IMAP folders defined in config (idempotent)