You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CHANGELOG.md
+6Lines changed: 6 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,5 +1,11 @@
1
1
# Changelog
2
2
3
+
# 2026-05-06
4
+
-**Theming with 6 built-in palettes** — pick via `[ui].theme = "kanagawa"` (default, byte-for-byte identical to pre-theme state), `kanagawa-paper`, `kanagawa-light` (only light theme — Lotus palette on a paperwhite `#F2EFE9` background), `rose-pine`, `gruvbox`, or `osaka-jade`. Optional top-level `[theme]` block overrides individual colour slots (`primary`, `unread`, `error`, `bg`, …) on top of any built-in. `ApplyTheme` runs in `ui.New()` before any rendering, so theme switching needs only a config edit + restart. Regression test verifies the kanagawa default does not drift; theme tests cover override merge, unknown-name fallback, and round-trip uniqueness
5
+
-**AI handoff key in pre-send (`i`)** — new `[ai].command` config (default `claude`) wires any LLM CLI (`claude`, `codex`, `aichat`, `sgpt`, …) to the pre-send `i` key. neomd writes the current draft to `/tmp/neomd/neomd-ai-*.md` with the standard `# [neomd: ...]` headers, spawns `<command> [args...] <path>`, and re-reads the file on exit so header and body edits round-trip back into the draft (same parser as the regular editor flow). Quit the AI tool to return to neomd's pre-send screen. Pre-send footer surfaces the active command (`i AI (claude, quit to return)`). `nvim` is intentionally not a useful choice here — the compose buffer is already in nvim before pre-send, so spawning nvim on `i` would just re-edit. Set `command = ""` to disable the binding
6
+
- **iCalendar RSVP + local calendar handoff** — emails with a `text/calendar` part or `.ics` attachment now show a `📅` summary card in the reader header (`event title · date · location`). Leader chord `<space> v {a|d|t|o}` sends an RFC 5546/6047 (iMIP) accept/decline/tentative reply, or opens the `.ics` in `[calendar].open_command` (default `xdg-open`, set to `morgen`/`khal`/`/usr/bin/gnome-calendar` to force a specific app). MIME envelope: `multipart/mixed > [multipart/alternative > (text/plain + text/calendar;method=REPLY)] + .ics attachment` with `Subject: Accepted: <event>` (matches Gmail's native button format) and bracketed RFC 5322 `In-Reply-To` headers. RSVPs save to the `Sent` folder and mark the original invite `\Answered`. **Reliability note:** Outlook 365 / Exchange / Apple iCloud / CalDAV servers auto-process iMIP REPLIES server-side; Gmail has deprioritized iMIP processing in 2026, so Gmail organizers may need to manually note your reply (or just use Gmail's native Yes/No button for Gmail-originated invites). New self-contained `internal/calendar/` package on top of `arran4/golang-ical`; new `internal/smtp/rsvp.go` for the iMIP MIME envelope. Only the first `VEVENT` is processed; recurring rules, counter-proposals, and cancellations are out of scope
7
+
-**OS keyring credential storage** ([#5](https://github.com/ssp-data/neomd/pull/5), thanks [@notthatjesus](https://github.com/notthatjesus)) — set `password = "keyring"` in any `[[accounts]]` block to fetch the IMAP/SMTP password from the OS keyring (macOS Keychain, Linux Secret Service via gnome-keyring/kwallet, Windows Credential Manager) at startup. OAuth2 tokens also persist in the keyring with explicit file fallback for headless/SSH systems where no keyring service is available. Sentinel resolution runs inside `config.Load()` so every consumer — IMAP at boot, SMTP at send, `[[senders]]` aliases that reference an account — sees the resolved password automatically without per-call lookups. New `internal/keyring/` package with mock-backed tests; storage keys are `neomd/account/<name>/{password|oauth2}`
8
+
3
9
# 2026-05-04
4
10
-**Fix: send from `imap_disabled = true` account no longer panics** — sending an email from an account configured as send-only (typically Gmail with `imap_disabled = true`) crashed the TUI with `nil pointer dereference` in `tokenSourceFor` because the helper called `.TokenSource()` on the intentionally-nil IMAP client. The same nil-deref existed in `imapCli`, `imapCliForAccount`, and `primaryIMAPClient` — any code path that resolved an IMAP client for a send-only account would crash. All four helpers now skip nil entries (and fall back to the first non-nil client where appropriate); `sendEmailCmd` also guards `cli.SaveSent` and `replyCli.MarkAnswered` so a fully send-only configuration silently skips the Sent-folder copy instead of panicking. Four regression tests added in `internal/ui/imap_client_helpers_test.go`
5
11
-**Fix: notify state key keeps IMAP folder name (preserves baselines across upgrades)** — yesterday's label-normalisation fix accidentally changed the persisted state key from `Personal|INBOX` to `Personal|Inbox`, which would have re-baselined every existing user on upgrade and silently swallowed one round of notifications. `MaybeNotify` now takes both the IMAP name (used for the state key) and the UI label (used only for the allowlist comparison) so existing `notify_state.json` files keep working untouched. Regression test added
-`internal/oauth2/` — OAuth2 flow for Gmail/Office365
75
+
-`internal/calendar/` — iCalendar (.ics) parsing + iMIP RSVP reply construction (`arran4/golang-ical`); used by reader card and `<space> v {a|d|t}` chord
75
76
-`internal/integration_test.go` — integration tests (live IMAP/SMTP); lives at package level, not in a sub-package
76
77
77
78
**Spy pixel detection** (`internal/imap/tracker_list.go` + `client.go`): Two-layer approach — (1) curated denylist of 150+ tracking services in `KnownTrackers` with `IdentifyTracker()` for attribution ("Mailchimp", "HubSpot"); (2) generic 1×1 pixel heuristic via `detectSpyPixels()` on raw HTML. Results flow through `SpyPixelInfo` struct returned by `FetchBody()` and `ScanSpyPixels()`. Cached to `~/.cache/neomd/spy_pixels` (format: `+key` for spy, `-key` for scanned clean).
Copy file name to clipboardExpand all lines: README.md
+4-1Lines changed: 4 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -158,6 +158,7 @@ Keep your inbox clean without effort.
158
158
### Composing & Sending
159
159
160
160
-**Pre-send review** — after closing the editor, review To/Subject/body before sending; attach files, save to Drafts, or re-open the editor — no accidental sends [→](https://neomd.ssp.sh/docs/sending/#pre-send-review)
161
+
-**AI handoff (`i` in pre-send)** — `[ai].command` (default `claude`) wires any LLM CLI (claude, codex, aichat, …) to the pre-send `i` key; neomd writes the draft to a temp markdown, spawns the command, and re-reads on exit when you quit the tool — header and body edits round-trip back. No in-app AI dependency [→](https://neomd.ssp.sh/docs/configuration/#ai-handoff-pre-send-i-key)
161
162
-**Attachments** — attach files from the pre-send screen via yazi (`a`); images appear inline in the email body, other files as attachments; also attach from within Neovim via `<leader>a`; the reader lists all attachments and `1`–`9` downloads and opens them [→](https://neomd.ssp.sh/docs/sending/#attachments)
162
163
-**Emoji reactions** — press `ctrl+e` from inbox or reader to react with emoji (👍 ❤️ 😂 🎉 🙏 💯 👀 ✅); instant send with proper threading and quoted message history, no editor needed [→](https://neomd.ssp.sh/docs/sending/#emoji-reactions)
163
164
-**Multi-select** — `m` marks emails, then batch-delete, move, or screen them all at once [→](https://neomd.ssp.sh/docs/keybindings/#multi-select--undo)
@@ -166,6 +167,7 @@ Keep your inbox clean without effort.
166
167
### Reading
167
168
168
169
-**Threaded inbox** — related emails grouped together with a vertical connector line (`│`/`╰`), Twitter-style; threads detected via `In-Reply-To`/`Message-ID` headers with a subject+participant fallback; newest reply on top, root at bottom; `·` reply indicator shows which emails you've answered [→](https://neomd.ssp.sh/docs/reading/#threaded-inbox)
170
+
-**iCalendar RSVP** — meeting invites (`text/calendar` / `.ics`) show a `📅` card in the reader; leader chord `<space> v {a|d|t}` sends an RFC 5546/6047 (iMIP) accept/decline/tentative reply; `<space> v o` hands the `.ics` off to your local calendar app via `[calendar].open_command` (default `xdg-open`, set to `morgen`, `khal`, etc.) [→](https://neomd.ssp.sh/docs/configuration/#calendar-invites-icalendar--imip)
169
171
-**Conversation view** — `T` or `:thread` shows the full conversation across folders (Inbox, Sent, Archive, etc.) in a temporary tab with `[Folder]` prefix; see your replies alongside received emails [→](https://neomd.ssp.sh/docs/reading/#conversation-view)
170
172
-**Link opener** — links in emails are numbered `[1]`–`[0]` in the reader header; press `space+digit` to open in `$BROWSER`[→](https://neomd.ssp.sh/docs/reading/#links)
171
173
-**Everything view** — `ge` or `:everything` shows the 50 most recent emails across all folders; find emails that were screened out, moved to spam, or otherwise hard to locate [→](https://neomd.ssp.sh/docs/keybindings/#folders)
@@ -179,14 +181,15 @@ Keep your inbox clean without effort.
179
181
-**CC, BCC, Reply-all** — optional Cc/Bcc fields (toggle with `ctrl+b`); `R` in the reader replies to sender + all CC recipients [→](https://neomd.ssp.sh/docs/sending/#cc-bcc-reply-all-and-forward)
180
182
-**Drafts** — `d` in pre-send saves to Drafts (IMAP APPEND); `E` in the reader re-opens a draft as an editable compose; compose sessions are auto-backed up to `~/.cache/neomd/drafts/` so you never lose an unsent email (`:recover` to reopen) [→](https://neomd.ssp.sh/docs/sending/#drafts)
181
183
-**Multiple From addresses** — define SMTP-only `[[senders]]` aliases (e.g. `s@ssp.sh` through an existing account); cycle with `ctrl+f` in compose and pre-send; sent copies always land in the Sent folder [→](https://neomd.ssp.sh/docs/sending/#multiple-from-addresses)
184
+
-**OS keyring credentials** — set `password = "keyring"` to fetch the IMAP/SMTP password from your OS keyring (macOS Keychain, Linux Secret Service, Windows Credential Manager); OAuth2 tokens also stored in keyring with file fallback for headless/SSH; resolution happens at config load so `[[senders]]` aliases inherit the resolved password automatically [→](https://neomd.ssp.sh/docs/configuration/#storing-passwords-in-the-os-keyring)
182
185
-**HTML signatures** — configure separate text and HTML signatures; text signature appears in editor and plain text part, HTML signature in HTML part only; use `[html-signature]` placeholder to control inclusion per-email [→](https://neomd.ssp.sh/docs/configuration/#html-signatures)
183
186
-**Address autocomplete** — To/Cc/Bcc fields autocomplete from screener lists; navigate with `ctrl+n`/`ctrl+p`, accept with `tab`
184
187
185
188
### Under the Hood
186
189
187
190
-**IMAP + SMTP** — direct connection via RFC 6851 MOVE, no local sync daemon required; stays in sync if you use it on mobile or different device [→](https://neomd.ssp.sh/docs/configuration/)
188
191
-**RFC 5322 compliant email delivery** — Message-IDs use sender's domain, proper MIME multipart/alternative structure (text/plain before text/html), quoted-printable encoding, and all required headers; ensures deliverability across all providers, spam filter compatibility, and correct email threading [→](https://neomd.ssp.sh/docs/configuration/email-standards/)
189
-
-**Kanagawa theme** — colors from the [kanagawa.nvim](https://github.com/rebelot/kanagawa.nvim) palette
192
+
-**Themes** — six built-in palettes (`kanagawa` default, `kanagawa-paper`, `kanagawa-light` for daylight terminals, `rose-pine`, `gruvbox`, `osaka-jade`); pick via `[ui].theme = "..."` and override individual colour slots in an optional `[theme]` block [→](https://neomd.ssp.sh/docs/configuration/#theming)
190
193
191
194
> [!NOTE]
192
195
> neomd's **speed** depends entirely on your IMAP provider. On Hostpoint (the provider I use), a folder switch takes **~33ms** which feels instant. On Gmail, the same operation takes **~570ms** which is noticeably slow. See [Benchmark](#benchmark) for full details and how to test your provider.
Copy file name to clipboardExpand all lines: docs/content/docs/_index.md
+4-1Lines changed: 4 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -161,6 +161,7 @@ Keep your inbox clean without effort.
161
161
### Composing & Sending
162
162
163
163
-**Pre-send review** — after closing the editor, review To/Subject/body before sending; attach files, save to Drafts, or re-open the editor — no accidental sends [→](https://neomd.ssp.sh/docs/sending/#pre-send-review)
164
+
-**AI handoff (`i` in pre-send)** — `[ai].command` (default `claude`) wires any LLM CLI (claude, codex, aichat, …) to the pre-send `i` key; neomd writes the draft to a temp markdown, spawns the command, and re-reads on exit when you quit the tool — header and body edits round-trip back. No in-app AI dependency [→](https://neomd.ssp.sh/docs/configuration/#ai-handoff-pre-send-i-key)
164
165
-**Attachments** — attach files from the pre-send screen via yazi (`a`); images appear inline in the email body, other files as attachments; also attach from within Neovim via `<leader>a`; the reader lists all attachments and `1`–`9` downloads and opens them [→](https://neomd.ssp.sh/docs/sending/#attachments)
165
166
-**Emoji reactions** — press `ctrl+e` from inbox or reader to react with emoji (👍 ❤️ 😂 🎉 🙏 💯 👀 ✅); instant send with proper threading and quoted message history, no editor needed [→](https://neomd.ssp.sh/docs/sending/#emoji-reactions)
166
167
-**Multi-select** — `m` marks emails, then batch-delete, move, or screen them all at once [→](https://neomd.ssp.sh/docs/keybindings/#multi-select--undo)
@@ -169,6 +170,7 @@ Keep your inbox clean without effort.
169
170
### Reading
170
171
171
172
-**Threaded inbox** — related emails grouped together with a vertical connector line (`│`/`╰`), Twitter-style; threads detected via `In-Reply-To`/`Message-ID` headers with a subject+participant fallback; newest reply on top, root at bottom; `·` reply indicator shows which emails you've answered [→](https://neomd.ssp.sh/docs/reading/#threaded-inbox)
173
+
-**iCalendar RSVP** — meeting invites (`text/calendar` / `.ics`) show a `📅` card in the reader; leader chord `<space> v {a|d|t}` sends an RFC 5546/6047 (iMIP) accept/decline/tentative reply; `<space> v o` hands the `.ics` off to your local calendar app via `[calendar].open_command` (default `xdg-open`, set to `morgen`, `khal`, etc.) [→](https://neomd.ssp.sh/docs/configuration/#calendar-invites-icalendar--imip)
172
174
-**Conversation view** — `T` or `:thread` shows the full conversation across folders (Inbox, Sent, Archive, etc.) in a temporary tab with `[Folder]` prefix; see your replies alongside received emails [→](https://neomd.ssp.sh/docs/reading/#conversation-view)
173
175
-**Link opener** — links in emails are numbered `[1]`–`[0]` in the reader header; press `space+digit` to open in `$BROWSER`[→](https://neomd.ssp.sh/docs/reading/#links)
174
176
-**Everything view** — `ge` or `:everything` shows the 50 most recent emails across all folders; find emails that were screened out, moved to spam, or otherwise hard to locate [→](https://neomd.ssp.sh/docs/keybindings/#folders)
@@ -182,14 +184,15 @@ Keep your inbox clean without effort.
182
184
-**CC, BCC, Reply-all** — optional Cc/Bcc fields (toggle with `ctrl+b`); `R` in the reader replies to sender + all CC recipients [→](https://neomd.ssp.sh/docs/sending/#cc-bcc-reply-all-and-forward)
183
185
-**Drafts** — `d` in pre-send saves to Drafts (IMAP APPEND); `E` in the reader re-opens a draft as an editable compose; compose sessions are auto-backed up to `~/.cache/neomd/drafts/` so you never lose an unsent email (`:recover` to reopen) [→](https://neomd.ssp.sh/docs/sending/#drafts)
184
186
-**Multiple From addresses** — define SMTP-only `[[senders]]` aliases (e.g. `s@ssp.sh` through an existing account); cycle with `ctrl+f` in compose and pre-send; sent copies always land in the Sent folder [→](https://neomd.ssp.sh/docs/sending/#multiple-from-addresses)
187
+
-**OS keyring credentials** — set `password = "keyring"` to fetch the IMAP/SMTP password from your OS keyring (macOS Keychain, Linux Secret Service, Windows Credential Manager); OAuth2 tokens also stored in keyring with file fallback for headless/SSH; resolution happens at config load so `[[senders]]` aliases inherit the resolved password automatically [→](https://neomd.ssp.sh/docs/configuration/#storing-passwords-in-the-os-keyring)
185
188
-**HTML signatures** — configure separate text and HTML signatures; text signature appears in editor and plain text part, HTML signature in HTML part only; use `[html-signature]` placeholder to control inclusion per-email [→](https://neomd.ssp.sh/docs/configuration/#html-signatures)
186
189
-**Address autocomplete** — To/Cc/Bcc fields autocomplete from screener lists; navigate with `ctrl+n`/`ctrl+p`, accept with `tab`
187
190
188
191
### Under the Hood
189
192
190
193
-**IMAP + SMTP** — direct connection via RFC 6851 MOVE, no local sync daemon required; stays in sync if you use it on mobile or different device [→](https://neomd.ssp.sh/docs/configuration/)
191
194
-**RFC 5322 compliant email delivery** — Message-IDs use sender's domain, proper MIME multipart/alternative structure (text/plain before text/html), quoted-printable encoding, and all required headers; ensures deliverability across all providers, spam filter compatibility, and correct email threading [→](https://neomd.ssp.sh/docs/configuration/email-standards/)
192
-
-**Kanagawa theme** — colors from the [kanagawa.nvim](https://github.com/rebelot/kanagawa.nvim) palette
195
+
-**Themes** — six built-in palettes (`kanagawa` default, `kanagawa-paper`, `kanagawa-light` for daylight terminals, `rose-pine`, `gruvbox`, `osaka-jade`); pick via `[ui].theme = "..."` and override individual colour slots in an optional `[theme]` block [→](https://neomd.ssp.sh/docs/configuration/#theming)
193
196
194
197
{{< callout type="info" >}}
195
198
neomd's **speed** depends entirely on your IMAP provider. On Hostpoint (the provider I use), a folder switch takes **~33ms** which feels instant. On Gmail, the same operation takes **~570ms** which is noticeably slow. See [Benchmark](#benchmark) for full details and how to test your provider.
0 commit comments