A minimal email TUI where you read with Markdown and write in Neovim. neomd.ssp.sh/docs
email markdown neovim tui
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

fixed refresh to reload emails in inbox

sspaeti fcfd1904 3f5b98be

+12
+1
CHANGELOG.md
··· 3 3 # 2026-04-13 4 4 - **Emoji reactions (`ctrl+e`)** — fast, keyboard-driven emoji reactions from inbox or reader; press `ctrl+e` to open emoji picker overlay, select with `1`-`8` for instant send or navigate with `j`/`k` and press `enter`; sends minimal reaction email (emoji + italic footer + quoted original message) with proper threading headers; available reactions: 👍 ❤️ 😂 🎉 🙏 💯 👀 ✅; original email marked with `\Answered` flag; reaction saved to Sent folder; auto-selects From address matching recipient (same logic as regular replies) 5 5 - **Email threading headers** — all replies (regular `r`/`R` and emoji reactions `ctrl+e`) now include proper `In-Reply-To` and `References` headers for conversation threading; ensures replies appear correctly grouped in Gmail, Outlook, and Apple Mail conversation views; `References` header extracted from IMAP message body and preserved in reply chain 6 + - **Fix: refresh not showing new emails immediately** — pressing `R` now correctly displays new emails on first refresh; previously the IMAP client cached the selected mailbox state, so the first `R` would skip re-SELECT and use stale UID SEARCH results (showing the old unread count but no new messages in the list); required a second `R` or tab switch to see new emails; now forces a fresh SELECT to ensure mailbox state is current 6 7 7 8 8 9 # 2026-04-10
+9
internal/imap/client.go
··· 196 196 } 197 197 } 198 198 199 + // ResetMailboxSelection clears the cached selected mailbox, forcing the next 200 + // FetchHeaders or similar operation to re-SELECT the mailbox and fetch fresh state. 201 + // This is useful when refreshing to ensure new messages are visible. 202 + func (c *Client) ResetMailboxSelection() { 203 + c.mu.Lock() 204 + defer c.mu.Unlock() 205 + c.selectedMailbox = "" 206 + } 207 + 199 208 // TokenSource returns the OAuth2 token source for this client, or nil for 200 209 // password-authenticated accounts. 201 210 func (c *Client) TokenSource() func() (string, error) { return c.cfg.TokenSource }
+2
internal/ui/model.go
··· 2424 2424 return m, nil 2425 2425 2426 2426 case "R": 2427 + m.imapCli().ResetMailboxSelection() // force fresh SELECT to see new messages 2427 2428 m.loading = true 2428 2429 return m, tea.Batch(m.spinner.Tick, m.fetchFolderCmd(m.activeFolder())) 2429 2430 ··· 2790 2791 return m.launchReplyCmd() 2791 2792 } 2792 2793 case "R": 2794 + m.imapCli().ResetMailboxSelection() // force fresh SELECT to see new messages 2793 2795 m.loading = true 2794 2796 return m, tea.Batch(m.spinner.Tick, m.fetchFolderCmd(m.activeFolder())) 2795 2797 case "ctrl+r":