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.

merge docs: adding Hugo docs with Template hextra

sspaeti 59dda48f a66bbe49

+832 -39
+5 -3
.claude/settings.local.json
··· 6 6 "Bash(make build:*)", 7 7 "Bash(make test:*)", 8 8 "WebFetch(domain:www.ssp.sh)", 9 - "Bash(roborev --help:*)", 10 - "WebFetch(domain:www.roborev.io)", 11 - "Bash(roborev check-agents:*)" 9 + "Bash(make docs-build:*)", 10 + "Bash(hugo version:*)", 11 + "Bash(hugo mod graph:*)", 12 + "Bash(make docs-sync:*)", 13 + "WebFetch(domain:raw.githubusercontent.com)" 12 14 ] 13 15 } 14 16 }
+85
.github/workflows/deploy-docs.yaml
··· 1 + # Deploy Hugo documentation site to GitHub Pages 2 + name: Deploy Documentation 3 + 4 + on: 5 + # Runs on pushes targeting the main branch 6 + push: 7 + branches: ["main", "neomd-docs"] 8 + paths: 9 + - 'docs/**' 10 + - '.github/workflows/deploy-docs.yaml' 11 + 12 + # Allows you to run this workflow manually from the Actions tab 13 + workflow_dispatch: 14 + 15 + # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 16 + permissions: 17 + contents: read 18 + pages: write 19 + id-token: write 20 + 21 + # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 22 + # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 23 + concurrency: 24 + group: "pages" 25 + cancel-in-progress: false 26 + 27 + # Default to bash 28 + defaults: 29 + run: 30 + shell: bash 31 + 32 + jobs: 33 + # Build job 34 + build: 35 + runs-on: ubuntu-latest 36 + env: 37 + HUGO_VERSION: 0.156.0 38 + steps: 39 + - name: Checkout 40 + uses: actions/checkout@v4 41 + with: 42 + fetch-depth: 0 # fetch all history for .GitInfo and .Lastmod 43 + submodules: recursive 44 + 45 + - name: Setup Go 46 + uses: actions/setup-go@v5 47 + with: 48 + go-version: '1.24' 49 + 50 + - name: Setup Pages 51 + id: pages 52 + uses: actions/configure-pages@v4 53 + 54 + - name: Setup Hugo 55 + run: | 56 + wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \ 57 + && sudo dpkg -i ${{ runner.temp }}/hugo.deb 58 + 59 + - name: Build with Hugo 60 + env: 61 + # For maximum backward compatibility with Hugo modules 62 + HUGO_ENVIRONMENT: production 63 + HUGO_ENV: production 64 + run: | 65 + cd docs 66 + hugo \ 67 + --gc --minify \ 68 + --baseURL "${{ steps.pages.outputs.base_url }}/" 69 + 70 + - name: Upload artifact 71 + uses: actions/upload-pages-artifact@v3 72 + with: 73 + path: ./docs/public 74 + 75 + # Deployment job 76 + deploy: 77 + environment: 78 + name: github-pages 79 + url: ${{ steps.deployment.outputs.page_url }} 80 + runs-on: ubuntu-latest 81 + needs: build 82 + steps: 83 + - name: Deploy to GitHub Pages 84 + id: deployment 85 + uses: actions/deploy-pages@v4
+21 -1
Makefile
··· 6 6 7 7 .PHONY: build run install clean test test-integration send-test vet fmt tidy release docs help check-go demo demo-reset demo-hp demo-hp-reset benchmark 8 8 9 + 10 + .DEFAULT_GOAL := install 11 + 9 12 ## check-go: verify Go is installed 10 13 check-go: 11 14 @command -v go >/dev/null 2>&1 || { \ ··· 110 113 rm -f $(BINARY) $(BINARY)-android 111 114 112 115 ## release: tag and push a new release (usage: make release VERSION=v0.1.0) 113 - release: docs 116 + release: docs docs-build 114 117 @test -n "$(VERSION)" || (echo "Usage: make release VERSION=v0.1.0" && exit 1) 115 118 git tag -a $(VERSION) -m "Release $(VERSION)" 116 119 git push origin $(VERSION) ··· 119 122 ## docs: regenerate keybindings section in README.md from internal/ui/keys.go 120 123 docs: 121 124 go run ./cmd/docs 125 + @./scripts/sync-readme-to-docs.sh 126 + 127 + ## docs-sync: sync README.md to docs/content/overview.md 128 + docs-sync: 129 + ./scripts/sync-readme-to-docs.sh 130 + 131 + ## docs-serve: serve Hugo docs site locally at http://localhost:1313 132 + docs-serve: 133 + $(MAKE) -C docs serve 134 + 135 + ## docs-build: build Hugo docs site to docs/public/ 136 + docs-build: 137 + $(MAKE) -C docs build 138 + 139 + ## docs-clean: remove generated Hugo files 140 + docs-clean: 141 + $(MAKE) -C docs clean 122 142 123 143 ## help: print this list 124 144 help:
+2 -2
cmd/docs/main.go
··· 1 - // docs regenerates the keybindings section in docs/keybindings.md from the 1 + // docs regenerates the keybindings section in docs/content/docs/keybindings.md from the 2 2 // single source of truth in internal/ui/keys.go. 3 3 // 4 4 // Usage: go run ./cmd/docs (or: make docs) ··· 17 17 ) 18 18 19 19 const ( 20 - readmePath = "docs/keybindings.md" 20 + readmePath = "docs/content/docs/keybindings.md" 21 21 startTag = "<!-- keybindings-start -->" 22 22 endTag = "<!-- keybindings-end -->" 23 23 )
+4
docs/.gitignore
··· 1 + # Hugo build artifacts 2 + public/ 3 + resources/ 4 + .hugo_build.lock
+22
docs/Makefile
··· 1 + .PHONY: serve build clean help 2 + 3 + .DEFAULT_GOAL := serve 4 + 5 + ## serve: serve Hugo docs site locally at http://localhost:1313 6 + serve: 7 + @echo "Starting Hugo development server..." 8 + hugo server -D --port 1311 9 + 10 + ## build: build Hugo docs site to public/ 11 + build: 12 + @echo "Building Hugo docs site..." 13 + hugo --gc --minify 14 + 15 + ## clean: remove generated files 16 + clean: 17 + @echo "Cleaning generated files..." 18 + rm -rf public resources .hugo_build.lock 19 + 20 + ## help: print this list 21 + help: 22 + @grep -E '^## ' Makefile | sed 's/^## //'
+7 -6
docs/android.md docs/content/docs/configurations/android.md
··· 1 - # Android (Termux) 1 + --- 2 + title: Android (Termux) 3 + weight: 3 4 + --- 2 5 3 6 Neomd runs on Android via [Termux](https://f-droid.org/en/packages/com.termux/). Build natively on the phone — cross-compiled binaries have DNS issues on Android. 4 7 5 - 6 - 7 8 ## Install 8 9 9 10 ```sh 10 11 pkg install golang git 11 - git clone https://github.com/sspaeti/neomd 12 + git clone https://github.com/ssp-data/neomd 12 13 cd neomd 13 14 go build -o neomd ./cmd/neomd 14 15 ./neomd ··· 37 38 ## Images 38 39 39 40 Overview email - feed: 40 - ![neomd](../images/android-overview.png) 41 + ![neomd](/images/android-overview.png) 41 42 42 43 Reading an email: 43 - ![neomd](../images/android-reading.png) 44 + ![neomd](/images/android-reading.png) 44 45 45 46 46 47 ## Home Screen Shortcuts
+14 -10
docs/configuration.md docs/content/docs/configuration.md
··· 1 - # Configuration Reference 1 + --- 2 + title: Configuration Reference 3 + weight: 1 4 + --- 2 5 3 6 On first run, neomd creates `~/.config/neomd/config.toml` with placeholders. 4 7 ··· 64 67 # work = "Work" # optional custom folder; add "work" to tab_order to show as a tab (gb to go, Mb to move -b for business as w was taken) 65 68 # tab_order controls the left-to-right tab sequence; omit to use the built-in default order. e.g.: 66 69 # tab_order = ["inbox", "to_screen", "feed", "papertrail", "waiting", "someday", "scheduled", "sent", "archive", "screened_out", "drafts", "trash"] 67 - # Gmail uses different folder names — see docs/gmail.md for the correct mapping. 70 + # Gmail uses different folder names — see docs/content/gmail.md for the correct mapping. 68 71 69 72 [ui] 70 73 theme = "dark" # dark | light | auto ··· 82 85 ``` 83 86 84 87 85 - > [!NOTE] 86 - > **Gmail** uses different IMAP folder names (`[Gmail]/Sent Mail`, `[Gmail]/Trash`, etc.). See [Gmail Configuration](gmail.md) for the correct mapping. 88 + {{< callout type="info" >}} 89 + **Gmail** uses different IMAP folder names (`[Gmail]/Sent Mail`, `[Gmail]/Trash`, etc.). See [Gmail Configuration](configurations/gmail) for the correct mapping. 90 + {{< /callout >}} 87 91 88 92 Use an app-specific password (Gmail, Fastmail, Hostpoint, etc.) rather than your main account password. 89 93 90 94 `inbox_count` is a fetch cap for normal folder loads and startup auto-screening. If you want to re-screen the entire Inbox on the IMAP server, use `:screen-all` from inside neomd; that scans every Inbox email, not just the loaded subset, and can take a while on large mailboxes. 91 95 92 - ### Environment Variables 96 + ## Environment Variables 93 97 94 98 The `password` and `user` fields support environment variable expansion. If the entire value is a single env var reference, neomd resolves it at startup: 95 99 ··· 102 106 103 107 Credentials are stored only in `~/.config/neomd/config.toml` (mode 0600) and never written elsewhere; all IMAP connections use TLS (port 993) or STARTTLS (port 143). 104 108 105 - ### TLS and STARTTLS Configuration 109 + ## TLS and STARTTLS Configuration 106 110 107 111 Neomd automatically determines the correct encryption method based on the port and the optional `starttls` config field: 108 112 ··· 146 150 starttls = true # Forces STARTTLS instead of TLS 147 151 ``` 148 152 149 - See `docs/proton-bridge.md` for complete Proton Mail Bridge setup instructions. 153 + See [Proton Bridge Setup](configurations/proton-bridge) for complete Proton Mail Bridge setup instructions. 150 154 151 155 For localhost/self-signed bridges such as Proton Mail Bridge, neomd first tries 152 156 normal certificate verification. If that fails with an unknown-authority error ··· 154 158 localhost-only fallback so existing Bridge setups keep working. If you want 155 159 strict verification, export the Bridge certificate and set `tls_cert_file`. 156 160 157 - ### Sent and Drafts Storage 161 + ## Sent and Drafts Storage 158 162 159 163 When multiple accounts or `[[senders]]` aliases are configured, SMTP delivery 160 164 always uses the selected sending identity's account. ··· 223 227 #### This is how it looks 224 228 225 229 The sent e-mail with above HTML signature looks like this: 226 - ![image](../images/html-signature.png) 230 + ![HTML signature example](/images/html-signature.png) 227 231 228 232 In the email as text: 229 233 ```markdown ··· 233 237 here's my new HTML signature below. 234 238 BR Simon 235 239 236 - -- 240 + -- 237 241 [html-signature] 238 242 ``` 239 243
+105
docs/content/_index.md
··· 1 + --- 2 + title: neomd 3 + layout: hextra-home 4 + toc: false 5 + --- 6 + 7 + <div class="hx-mt-6 hx-mb-6"> 8 + {{< hextra/hero-headline >}} 9 + A minimal terminal email client&nbsp;<br class="sm:hx-block hx-hidden" />for people who read & write in Markdown 10 + {{< /hextra/hero-headline >}} 11 + </div> 12 + 13 + <div class="hx-mb-12"> 14 + {{< hextra/hero-subtitle >}} 15 + Compose in Neovim, navigate with Vim motions, screen emails like HEY,&nbsp;<br class="sm:hx-block hx-hidden" />process your inbox with GTD — all from the terminal 16 + {{< /hextra/hero-subtitle >}} 17 + </div> 18 + 19 + <div class="hx-mb-6"> 20 + {{< hextra/hero-button text="Overview and Philosophy" link="docs/overview" >}} 21 + </div> 22 + 23 + <div class="hx-mt-6"></div> 24 + 25 + <div class="hx-mt-12 hx-mb-8"> 26 + <h2 class="hx-text-4xl hx-font-bold hx-tracking-tight hx-text-gray-900 dark:hx-text-gray-50">What Makes neomd Different?</h2> 27 + </div> 28 + 29 + {{< hextra/feature-grid >}} 30 + {{< hextra/feature-card 31 + title="HEY-Style Screener" 32 + subtitle="Unknown senders wait in ToScreen until you approve (I), block (O), or categorize them. You choose who reaches your inbox — bye-bye spam." 33 + class="aspect-auto md:aspect-[1.1/1] max-md:min-h-[340px]" 34 + style="background: radial-gradient(ellipse at 50% 80%,rgba(1,97,254,0.15),hsla(0,0%,100%,0));" 35 + >}} 36 + {{< hextra/feature-card 37 + title="GTD Workflow" 38 + subtitle="Process your inbox only once. Move emails to Waiting, Someday, Scheduled, or Archive with single keystrokes. Includes Feed and PaperTrail for newsletters and receipts." 39 + class="aspect-auto md:aspect-[1.1/1] max-lg:min-h-[340px]" 40 + style="background: radial-gradient(ellipse at 50% 80%,rgba(194,97,254,0.15),hsla(0,0%,100%,0));" 41 + >}} 42 + {{< hextra/feature-card 43 + title="Superhuman Speed" 44 + subtitle="Folder switches in ~33ms (on fast IMAP providers like Hostpoint). Every action is instant — no loading spinners, no delays. Navigate with Vim motions." 45 + class="aspect-auto md:aspect-[1.1/1] max-md:min-h-[340px]" 46 + style="background: radial-gradient(ellipse at 50% 80%,rgba(142,53,74,0.15),hsla(0,0%,100%,0));" 47 + >}} 48 + {{< hextra/feature-card 49 + title="Neovim Integration" 50 + subtitle="Compose in $EDITOR (nvim), send as Markdown → HTML multipart. Pre-send review prevents accidental sends. Auto-backup drafts to ~/.cache." 51 + class="aspect-auto md:aspect-[1.1/1] max-md:min-h-[340px]" 52 + style="background: radial-gradient(ellipse at 50% 80%,rgba(12,53,74,0.15),hsla(0,0%,100%,0));" 53 + >}} 54 + {{< hextra/feature-card 55 + title="Direct IMAP/SMTP" 56 + subtitle="No local sync daemon. Uses RFC 6851 MOVE for instant operations. Works on any device with your mailbox always in sync." 57 + class="aspect-auto md:aspect-[1.1/1] max-lg:min-h-[340px]" 58 + style="background: radial-gradient(ellipse at 50% 80%,rgba(221,210,59,0.15),hsla(0,0%,100%,0));" 59 + >}} 60 + {{< hextra/feature-card 61 + title="Keyboard-First" 62 + subtitle="Vim motions everywhere. j/k navigation, gg/G jumps, / search, numbered links [1]-[0], multi-select with m, undo with u." 63 + class="aspect-auto md:aspect-[1.1/1] max-md:min-h-[340px]" 64 + style="background: radial-gradient(ellipse at 50% 80%,rgba(59,130,246,0.15),hsla(0,0%,100%,0));" 65 + >}} 66 + {{< /hextra/feature-grid >}} 67 + 68 + <br> 69 + 70 + <div class="hx-mt-12 hx-mb-8"> 71 + <h2 class="hx-text-4xl hx-font-bold hx-tracking-tight hx-text-gray-900 dark:hx-text-gray-50">Video Demo</h2> 72 + </div> 73 + 74 + YouTube rundown of most features: 75 + [![neomd demo](https://img.youtube.com/vi/lpmHqIrCC-w/maxresdefault.jpg)](https://youtu.be/8aKkldYLWV8) 76 + 77 + 78 + <div class="hx-mt-12 hx-mb-8"> 79 + <h2 class="hx-text-4xl hx-font-bold hx-tracking-tight hx-text-gray-900 dark:hx-text-gray-50">Documentation</h2> 80 + </div> 81 + 82 + {{< cards cols="3" >}} 83 + {{< card link="docs/overview" title="Overview & Philosophy" subtitle="Full feature list, installation (binary, AUR, source), philosophy, benchmarks, and inspiration" >}} 84 + {{< card link="docs/configuration" title="Configuration Reference" subtitle="Full config with multiple accounts, OAuth2, signatures, and UI options" >}} 85 + {{< card link="docs/keybindings" title="Keybindings" subtitle="Complete keyboard shortcuts reference (auto-generated from source)" >}} 86 + {{< card link="docs/screener" title="Screener Workflow" subtitle="How to classify emails, bulk operations, and screener lists" >}} 87 + {{< card link="docs/reading" title="Reading Emails" subtitle="Navigation, images, links, attachments, threading" >}} 88 + {{< card link="docs/sending" title="Sending Emails" subtitle="Compose, attachments, CC/BCC, drafts, HTML signatures" >}} 89 + {{< /cards >}} 90 + 91 + <br> 92 + 93 + <div class="hx-mb-6"> 94 + {{< hextra/hero-button text="Getting Started: Install" link="docs/overview#install" >}} 95 + </div> 96 + 97 + 98 + <div class="hx-mt-12 hx-mb-8"> 99 + <h2 class="hx-text-4xl hx-font-bold hx-tracking-tight hx-text-gray-900 dark:hx-text-gray-50">Links</h2> 100 + </div> 101 + 102 + - [GitHub Repository](https://github.com/ssp-data/neomd) 103 + - [Changelog](https://github.com/ssp-data/neomd/blob/main/CHANGELOG.md) 104 + - [Roadmap](https://www.ssp.sh/brain/neomd#roadmap) 105 + - [Security Policy](https://github.com/ssp-data/neomd/blob/main/SECURITY.md)
+5
docs/content/docs/_index.md
··· 1 + --- 2 + title: Documentation 3 + --- 4 + 5 + Welcome to the neomd documentation.
+8
docs/content/docs/configurations/_index.md
··· 1 + --- 2 + title: Platform Guides 3 + weight: 50 4 + sidebar: 5 + open: false 6 + --- 7 + 8 + Provider-specific configuration guides for Gmail, Proton Mail, and Android/Termux.
+331
docs/content/docs/overview.md
··· 1 + --- 2 + title: Overview & Philosophy 3 + weight: -10 4 + --- 5 + 6 + A minimal terminal email client for people who write in Markdown and live in Neovim. 7 + 8 + [Neomd](https://www.ssp.sh/brain/neomd/) is my way of implementing an email TUI based on my experience with Neomutt, focusing on [Neovim](https://www.ssp.sh/brain/neovim) (input) and reading/writing in [Markdown](https://www.ssp.sh/brain/markdown) and navigating with [Vim Motions](https://www.ssp.sh/brain/vim-language-and-motions) with the GTD workflow and [HEY-Screener](https://www.hey.com/features/the-screener/). 9 + 10 + 11 + {{< callout type="warning" >}} 12 + neomd moves, deletes, and modifies emails directly on your IMAP server. These operations affect your mailbox across all devices. **Back up important emails before first use.** neomd is experimental software and can't take responsibility for lost or misplaced emails. Consider testing with a secondary email account first. 13 + {{< /callout >}} 14 + 15 + 16 + ## The philosophy behind Neomd: What's unique? 17 + 18 + The key here is **speed** in which you can **navigate, read, and process** your email. Everything is just a shortcut away, and instantly (ms not seconds). It's similar to the foundations that Superhuman was built on: it runs on Gmail and makes it fast with vim commands. 19 + 20 + With the **HEY-Screener**, you get only emails in your inbox that you _screened in_, no spam or sales pitch before you added them. Or don't like them, just screen them out, and they get automatically moved to the "ScreenedOut" folder. 21 + 22 + With the [GTD approach](https://www.ssp.sh/brain/getting-things-done-gtd), using folders such as next (inbox), waiting, someday, scheduled, or archive, you can move them with one shortcut. This allows you quickly to move emails you need to wait for, or deal with later, in the right category. **Processing your email only once**. 23 + 24 + With the additional **Feed** and **Papertrail**, two additional features from HEY, you can read newsletters (just hit F) on them automatically in their separate tab, or move all your receipts into the Papertrail. Once you mark them as feed or papertrail, they will moved there automatically going forward. So you decide whether to read emails or news by jumping to different tabs. 25 + 26 + 27 + {{< callout type="info" >}} 28 + 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. 29 + {{< /callout >}} 30 + 31 + 32 + ## Screenshots 33 + 34 + ### Overview: List-View 35 + 36 + Feed view with all Newsletters - also workflow with differnt tabs and unread counter only for certain tabs (not all): 37 + ![neomd](/images/overview-email-feed.png) 38 + 39 + ### Reading Panel 40 + 41 + Reading an email with Markdown 💙: 42 + 43 + ![neomd](/images/reading-email.png) 44 + 45 + ### Sent emails 46 + This is the markdown sent: 47 + 48 + ```markdown 49 + # [neomd: to: email@domain.com] 50 + # [neomd: subject: this is an email from neomd!] 51 + 52 + This email is from Neomd. Great I can add links such as [this](https://ssp.sh) with plain Markdown. 53 + 54 + E.g. **bold** or _italic_. 55 + 56 + ## Does headers work too? 57 + 58 + this is a text before a h3. 59 + 60 + ### H3 header 61 + 62 + how does that look in an email? 63 + Best regards 64 + ``` 65 + 66 + *Compose emails in your editor, read them rendered with [glamour](https://github.com/charmbracelet/glamour), and manage your inbox with a [HEY-style screener](https://www.hey.com/features/the-screener/) — all from the terminal.* 67 + 68 + 69 + Which looks like this: 70 + 71 + ![neomd](/images/sent-email.png) 72 + 73 + Or in Gmail: 74 + ![neomd](/images/gmail.png) 75 + 76 + 77 + ### Video 78 + 79 + YouTube rundown of most features: 80 + [![neomd demo](https://img.youtube.com/vi/lpmHqIrCC-w/maxresdefault.jpg)](https://youtu.be/8aKkldYLWV8) 81 + *(shorter but limited showcase [part 1 video](https://youtu.be/lpmHqIrCC-w))* 82 + 83 + 84 + ## Features 85 + 86 + - **Write in Markdown, send beautifully** — compose in `$EDITOR` (defaults to `nvim`), send as `multipart/alternative`: raw Markdown as plain text + goldmark-rendered HTML so recipients get clickable links, bold, headers, inline code, and code blocks 87 + - **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 88 + - **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 (including inline images) and `1`–`9` downloads and opens them 89 + - **Link opener** — links in emails are numbered `[1]`-`[0]` in the reader header; press `space+digit` to open in `$BROWSER` 90 + - **CC, BCC, Reply-all** — optional Cc/Bcc fields (toggle with `ctrl+b`); `R` in the reader replies to sender + all CC recipients 91 + - **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) 92 + - **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 93 + - **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 94 + - **Undo** — `u` reverses the last move or delete (`x`, `A`, `M*`) using the UIDPLUS destination UID 95 + - **Search** — `/` filters loaded emails in-memory; `space /` or `:search` runs IMAP SEARCH across all folders (only fetching header capped at 100 per folder) with results in a temporary "Search" tab; supports `from:`, `subject:`, `to:` prefixes 96 + - **Address autocomplete** — To/Cc/Bcc fields autocomplete from screener lists; navigate with `ctrl+n`/`ctrl+p`, accept with `tab` 97 + - **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 98 + - **Threaded inbox** — related emails are grouped together in the inbox list with a vertical connector line (`│`/`╰`), Twitter-style; threads are 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 99 + - **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 100 + - **Glamour reading** — incoming emails rendered as styled Markdown in the terminal 101 + - **HEY-style screener** — unknown senders land in `ToScreen`; press `I/O/F/P` to approve, block, mark as Feed, or mark as PaperTrail; reuses your existing `screened_in.txt` lists from neomutt 102 + - **Folder tabs** — Inbox, ToScreen, Feed, PaperTrail, Archive, Waiting, Someday, Scheduled, Sent, Trash, ScreenedOut 103 + - **Multi-select** — `m` marks emails, then batch-delete, move, or screen them all at once 104 + - **Auto-screen on load** — screener runs automatically every time the Inbox loads (startup, `R`); keeps your inbox clean without pressing `S` (configurable, on by default) 105 + - **Background sync** — while neomd is open, inbox is fetched and screened every 5 minutes in the background; interval configurable, set to `0` to disable 106 + - **Kanagawa theme** — colors from the [kanagawa.nvim](https://github.com/rebelot/kanagawa.nvim) palette 107 + - **IMAP + SMTP** — direct connection via RFC 6851 MOVE, no local sync daemon required and keeps it in sync if you use it on mobile or different device 108 + 109 + ## Install 110 + 111 + **Prerequisites:** [Go 1.22+](https://go.dev/doc/install) and `make`. 112 + 113 + {{< callout type="info" >}} 114 + **Optional attachment helpers:** 115 + - `yazi` enables the built-in file picker used by pre-send `a` 116 + - custom Neovim integration in `custom.lua` enables inline `<leader>a` attachment insertion inside `neomd-*.md` buffers 117 + - without these, neomd still works; the inline Neovim attachment workflow just won't be available 118 + {{< /callout >}} 119 + 120 + 121 + ```sh 122 + git clone https://github.com/ssp-data/neomd 123 + cd neomd 124 + make install # installs to ~/.local/bin/neomd 125 + ``` 126 + 127 + Or just build locally: 128 + 129 + ```sh 130 + make build 131 + ./neomd 132 + ``` 133 + 134 + Or if on Arch Linux (AUR), you can use my [neomd-bin](https://aur.archlinux.org/packages/neomd-bin) via: 135 + ```sh 136 + yay -S neomd-bin 137 + ``` 138 + 139 + 140 + On first run, neomd: 141 + 1. Creates `~/.config/neomd/config.toml` with placeholders — fill in your IMAP/SMTP credentials 142 + - Important: Make sure that the Capitalization and naming of folder in `config.toml` is accroding to webmail IMAP, e.g. [Gmails](configurations/gmail) uses `sent = "[Gmail]/Sent Mail"` and not `sent` etc. 143 + 2. Creates `~/.config/neomd/lists/` for screener allowlists (or uses your custom paths from config) 144 + 3. Creates any missing IMAP folders (ToScreen, Feed, PaperTrail, etc.) automatically 145 + 146 + 147 + Neomd also runs on Android (more for fun) — see [configurations/android](configurations/android). 148 + 149 + ## Configuration 150 + 151 + On first run, neomd creates `~/.config/neomd/config.toml` with placeholders: 152 + 153 + ```toml 154 + [[accounts]] 155 + name = "Personal" 156 + imap = "imap.example.com:993" # :993 = TLS, :143 = STARTTLS 157 + smtp = "smtp.example.com:587" 158 + user = "me@example.com" 159 + password = "app-password" 160 + from = "Me <me@example.com>" 161 + starttls = false 162 + tls_cert_file = "" # optional PEM cert/CA for self-signed local bridges 163 + 164 + # Root-level settings 165 + store_sent_drafts_in_sending_account = false # default: Sent/Drafts use first account; true = follow sending account 166 + 167 + [screener] 168 + screened_in = "~/.config/neomd/lists/screened_in.txt" 169 + screened_out = "~/.config/neomd/lists/screened_out.txt" 170 + feed = "~/.config/neomd/lists/feed.txt" 171 + papertrail = "~/.config/neomd/lists/papertrail.txt" 172 + spam = "~/.config/neomd/lists/spam.txt" 173 + ``` 174 + 175 + Use an app-specific password (Gmail, Fastmail, Hostpoint, etc.) rather than your main account password. The `password` and `user` fields support environment variable expansion (`$VAR` or `${VAR}`) so you can avoid storing secrets in the config file. 176 + 177 + For the full configuration reference including multiple accounts, OAuth2 authentication, `[[senders]]` aliases, folder customization, signatures, and UI options, see [configuration](configuration). 178 + 179 + **Provider-specific guides:** 180 + - Gmail: [configurations/gmail](configurations/gmail) — folder name mapping and OAuth2 setup 181 + - Proton Mail Bridge: [configurations/proton-bridge](configurations/proton-bridge) — non-standard port configuration 182 + 183 + ### Onboarding 184 + 185 + On first launch, **auto-screening is paused** because your screener lists are empty — neomd won't move anything until you've classified your first sender. Your Inbox loads normally so you can explore. 186 + 187 + By default, neomd loads and auto-screens only the newest `200` Inbox emails (`[ui].inbox_count`). This keeps startup predictable. If you want to re-screen the entire Inbox on the IMAP server, run `:screen-all` inside neomd; that scans every Inbox email, not just the loaded subset, and can take a while on large mailboxes. 188 + 189 + **Getting started with the screener:** 190 + 191 + 1. From your Inbox, pick an email and press `I` (screen **in**) to approve the sender, or `O` (screen **out**) to block them. This creates your first screener list entry. 192 + 2. Once you've classified at least one sender, auto-screening activates on every Inbox load — new emails from known senders are sorted automatically. 193 + 3. Unknown senders land in the `ToScreen` tab. Jump there with `gk` (or `Tab`, use `L` or click the tab) and classify them: 194 + - `I` screen **in** — sender stays in Inbox forever 195 + - `O` screen **out** — sender never reaches Inbox again 196 + - `F` **feed** — newsletters go to the Feed tab 197 + - `P` **papertrail** — receipts go to the PaperTrail tab 198 + 4. Use `m` to mark multiple emails, then `I` to batch-approve them all at once. From the `ToScreen` folder, approving/blocking a single unmarked message now applies to all currently queued mail from that sender. 199 + 200 + **The best part:** all classifications are saved permanently in your screener lists (`screened_in.txt`, `screened_out.txt`, etc.). An email address screened in will automatically go to your Inbox, and any email screened out will never be in your Inbox again. 201 + 202 + You choose who can land in your Inbox. Bye-bye spam. This is the beauty of [HEY-Screener](https://www.hey.com/features/the-screener/), and neomd implements the same concept. 203 + 204 + {{< callout type="info" >}} 205 + To disable auto-screening entirely, set `auto_screen_on_load = false` in `[ui]` config. Run `:debug` inside neomd if something isn't working. 206 + {{< /callout >}} 207 + 208 + 209 + {{< callout type="warning" >}} 210 + `:screen-all` operates on the full Inbox mailbox on the server, not just the emails currently loaded in the UI. Use it when you intentionally want a mailbox-wide reclassification pass. 211 + {{< /callout >}} 212 + 213 + 214 + ### Screener Workflow 215 + 216 + Find full Screener Workflow at [screener](screener), classification tables, and bulk re-classification instructions. 217 + ### Keybindings 218 + 219 + Press `?` inside neomd to open the interactive help overlay. Start typing to filter shortcuts. 220 + 221 + See the [full keybindings reference](keybindings) (auto-generated from [`internal/ui/keys.go`](internal/ui/keys.go) via `make docs`). 222 + 223 + ### How Sending Works 224 + 225 + Compose in Markdown, send as `multipart/alternative` (plain text + HTML). Attachments, CC/BCC, multiple From addresses, drafts, and pre-send review are all supported. 226 + 227 + Discarding unsent mail now asks for confirmation in compose/pre-send, and `:recover` reopens the latest backup if you want to resume after an abort. 228 + 229 + - See [sending](sending) for details on MIME structure, attachments, pre-send review, and drafts. 230 + - See [reading](reading) for the reader: images, inline links, attachments, and navigation. 231 + 232 + ### Dev: Makefile Commands 233 + 234 + ``` 235 + make build compile ./neomd 236 + make run build and run 237 + make install install to ~/.local/bin 238 + make test run tests 239 + make vet go vet 240 + make fmt gofmt -w . 241 + make tidy go mod tidy 242 + make clean remove compiled binary 243 + make help print this list 244 + ``` 245 + 246 + ## Stack 247 + 248 + - [Bubble Tea](https://github.com/charmbracelet/bubbletea) — TUI framework 249 + - [Bubbles](https://github.com/charmbracelet/bubbles) — list, viewport, textinput components 250 + - [Glamour](https://github.com/charmbracelet/glamour) — Markdown → terminal rendering 251 + - [Lipgloss](https://github.com/charmbracelet/lipgloss) — styling 252 + - [go-imap/v2](https://github.com/emersion/go-imap) — IMAP client (RFC 6851 MOVE) 253 + - [go-message](https://github.com/emersion/go-message) — MIME parsing 254 + - [goldmark](https://github.com/yuin/goldmark) — Markdown → HTML for sending 255 + - [BurntSushi/toml](https://github.com/BurntSushi/toml) — config parsing 256 + 257 + ## Changelog 258 + 259 + See [CHANGELOG.md](CHANGELOG.md) for what's new. 260 + 261 + ## Benchmark 262 + 263 + neomd's responsiveness depends entirely on your IMAP server. Every folder switch, email open, and move requires IMAP round-trips (SELECT + UID SEARCH + FETCH). Here are real measurements from the same machine, same network: 264 + 265 + **Hostpoint** (dedicated email provider) — folder switch: **~33ms total** 266 + | Operation | Time | 267 + |-----------|------| 268 + | SELECT | 12ms | 269 + | UID SEARCH | 10ms | 270 + | FETCH (200 emails) | 76ms | 271 + | MOVE (1 email) | 46ms | 272 + 273 + **Gmail** — folder switch: **~570ms total** (17x slower than Hostpoint) 274 + | Operation | Time | 275 + |-----------|------| 276 + | SELECT | 200ms | 277 + | UID SEARCH | 180ms | 278 + | FETCH (2 emails) | 190ms | 279 + | MOVE (1 email) | 339ms | 280 + 281 + **Outlook/Office365** (with OAuth2 authentication and different network - not really comparable, but gives a indication) — folder switch: **~269ms total** (8x slower than Hostpoint) 282 + | Operation | Time | 283 + |-----------|------| 284 + | SELECT | 45ms | 285 + | UID SEARCH | 22ms | 286 + | FETCH (10 emails) | 180ms | 287 + | MOVE (1 email) | 21ms | 288 + 289 + Interestingly, Gmail benchmarks fast on a **fresh single connection** (`scripts/imap-benchmark.sh` shows ~70ms total, same as Hostpoint). But on a **sustained session** with sequential commands — which is how neomd actually uses IMAP — Gmail adds ~180ms latency per command. This is likely Gmail's internal label-to-folder translation and session management overhead. The result: every action in neomd feels much slower on Gmail, while Hostpoint stays instant. 290 + 291 + 292 + {{< callout type="info" >}} 293 + **Gmail is not recommended.** If you're on Gmail, consider a dedicated email provider (Hostpoint, Fastmail, HEY, Migadu, etc.) for the best neomd experience. Or use Gmail just for fun :). See [configurations/gmail](configurations/gmail) for Gmail-specific folder configuration. 294 + {{< /callout >}} 295 + 296 + 297 + **Test your own provider:** 298 + ```bash 299 + # With password 300 + IMAP_HOST=imap.example.com IMAP_USER=me@example.com IMAP_PASS=secret ./scripts/imap-benchmark.sh 301 + 302 + # With OAuth2 (reads token from neomd config) 303 + CONFIG=~/.config/neomd/config.toml IMAP_USER=me@gmail.com ./scripts/imap-benchmark.sh 304 + ``` 305 + 306 + ## Security 307 + 308 + See [SECURITY.md](SECURITY.md) for how credentials, screener lists, temp files, and network connections are handled — with links to the relevant source files. 309 + 310 + ## Inspirations 311 + 312 + - [Neomutt](https://neomutt.org) — the gold standard terminal email client; neomd reuses its screener list format and borrows keybindings (though most are [custom made](https://github.com/sspaeti/dotfiles/blob/master/mutt/.config/mutt/muttrc) and what I use). I implemented the [HEY screener for Neomutt](https://www.ssp.sh/brain/hey-screener-in-neomutt), see note for more information. 313 + - [HEY](https://www.hey.com/features/the-screener/) — the Screener concept: unknown senders wait for a decision before reaching your inbox 314 + - [hey-cli](https://github.com/basecamp/hey-cli) — a Go CLI for HEY; provided the bubbletea patterns used here 315 + - [newsboat](https://newsboat.org) — RSS reader whose `O` open-in-browser binding and vim navigation feel inspired neomd's reader view 316 + - [emailmd.dev](https://www.emailmd.dev) — the idea that email should be written in Markdown when seen on [HN](https://news.ycombinator.com/item?id=47505144) 317 + - [charmbracelet/pop](https://github.com/charmbracelet/pop) — minimal Go email sender from Charm 318 + - [charmbracelet/glamour](https://github.com/charmbracelet/glamour) — Markdown rendering in the terminal 319 + - [kanagawa.nvim](https://github.com/rebelot/kanagawa.nvim) — the color palette used for the inbox 320 + - [msgvault](https://github.com/wesm/msgvault) — Go IMAP archiver; the IMAP client code in neomd is adapted from it 321 + 322 + ## Disclaimer 323 + 324 + This TUI is mostly [vibe-coded](https://www.ssp.sh/brain/vibe-coding) in the sense that all code is written with Claude Code, but guided by very detailed instructions to make the workflow as I use it and like it to be. 325 + 326 + I used my experience with Neomutt, TUIs, and the GTD workflow for handling emails with HEY Screener, and added some (hopefully) _taste_ using my favorite tools and aesthetics. Find the full history at [Twitter](https://xcancel.com/sspaeti/status/2036539855182627169#m) - inspired by seeing [Email.md](https://www.emailmd.dev/) on HackerNews. 327 + 328 + If you [rather read the prompt](https://www.ssp.sh/brain/id-rather-read-the-prompt), check out my [initial prompt](docs/initial-prompt/prompt.md) and its generated [plan](docs/initial-prompt/prompt-plan.md) - which I have iterated and added features by the 100s since then. 329 + ## Roadmap 330 + 331 + See at my second brain at [Roadmap](https://www.ssp.sh/brain/neomd#roadmap).
+5 -2
docs/gmail.md docs/content/docs/configurations/gmail.md
··· 1 - # Gmail Configuration 1 + --- 2 + title: Gmail Configuration 3 + weight: 1 4 + --- 2 5 3 6 Gmail uses special IMAP folder names prefixed with `[Gmail]/`. neomd works with Gmail, but requires adjusted folder mappings and comes with performance caveats. 4 7 ··· 47 50 48 51 Gmail's IMAP is significantly slower than dedicated email providers. On a sustained session, each IMAP command takes ~180ms on Gmail vs ~12ms on providers like Hostpoint or Fastmail. This means every folder switch, email open, and move feels noticeably slower (~570ms vs ~33ms per folder switch). 49 52 50 - See the [Benchmark section](../README.md#benchmark) in the README for detailed measurements. 53 + See the [Benchmark section](https://github.com/ssp-data/neomd#benchmark) in the README for detailed measurements. 51 54 52 55 For the best neomd experience, consider a dedicated email provider. If you still want to use Gmail, it works — just expect ~1 second per action instead of instant. 53 56
+5
docs/go.mod
··· 1 + module github.com/ssp-data/neomd/docs 2 + 3 + go 1.24 4 + 5 + require github.com/imfing/hextra v0.9.3 // indirect
+2
docs/go.sum
··· 1 + github.com/imfing/hextra v0.9.3 h1:p4vDm2TSgt3RpJdJm2mqkpoJCH2S08wzySyyYodtgCc= 2 + github.com/imfing/hextra v0.9.3/go.mod h1:cEfel3lU/bSx7lTE/+uuR4GJaphyOyiwNR3PTqFTXpI=
+75
docs/hugo.yaml
··· 1 + # Hugo configuration file 2 + title: neomd Documentation 3 + 4 + # Base URL will be set by GitHub Actions 5 + baseURL: "https://ssp-data.github.io/neomd/" 6 + 7 + # Disable RSS generation to avoid template issues 8 + disableKinds: ["RSS"] 9 + 10 + # import hextra as module 11 + module: 12 + imports: 13 + - path: github.com/imfing/hextra 14 + 15 + markup: 16 + # allow raw html 17 + goldmark: 18 + renderer: 19 + unsafe: true 20 + parser: 21 + attribute: 22 + block: true 23 + autoHeadingID: true 24 + autoHeadingIDType: github 25 + 26 + # enable hextra syntax highlight 27 + highlight: 28 + noClasses: false 29 + 30 + tableOfContents: 31 + startLevel: 2 32 + endLevel: 6 33 + ordered: false 34 + 35 + menu: 36 + main: 37 + - name: Documentation 38 + pageRef: /docs 39 + weight: 1 40 + - name: Search 41 + weight: 2 42 + params: 43 + type: search 44 + - name: GitHub 45 + weight: 3 46 + url: "https://github.com/ssp-data/neomd" 47 + params: 48 + icon: github 49 + 50 + sidebar: 51 + - name: More 52 + params: 53 + type: separator 54 + weight: 100 55 + - name: Changelog 56 + url: "https://github.com/ssp-data/neomd/blob/main/CHANGELOG.md" 57 + weight: 101 58 + - name: Roadmap 59 + url: "https://www.ssp.sh/brain/neomd#roadmap" 60 + weight: 102 61 + 62 + params: 63 + description: "A fast, keyboard-first TUI email client with smart screening" 64 + 65 + navbar: 66 + displayTitle: true 67 + displayLogo: false 68 + 69 + footer: 70 + displayCopyright: true 71 + displayPoweredBy: true 72 + 73 + editURL: 74 + enable: true 75 + base: "https://github.com/ssp-data/neomd/edit/main/docs/content/docs"
+8 -3
docs/keybindings.md docs/content/docs/keybindings.md
··· 1 - # Keybindings 1 + --- 2 + title: Keybindings 3 + weight: 2 4 + --- 2 5 3 6 Press `?` inside neomd to open the interactive help overlay. Start typing to filter shortcuts. 4 7 5 - > The tables below are generated from [`internal/ui/keys.go`](../internal/ui/keys.go). 6 - > To update both the help overlay and this document at once, edit that file and run `make docs`. 8 + {{< callout type="info" >}} 9 + The tables below are generated from [`internal/ui/keys.go`](https://github.com/ssp-data/neomd/blob/main/internal/ui/keys.go). 10 + To update both the help overlay and this document at once, edit that file and run `make docs`. 11 + {{< /callout >}} 7 12 8 13 <!-- keybindings-start --> 9 14
+28
docs/layouts/shortcodes/readme-section.html
··· 1 + {{- $file := .Get "file" | default "../../README.md" -}} 2 + {{- $start := .Get "start" -}} 3 + {{- $end := .Get "end" -}} 4 + {{- $content := readFile $file -}} 5 + 6 + {{- if and $start $end -}} 7 + {{- $lines := split $content "\n" -}} 8 + {{- $inSection := false -}} 9 + {{- $output := slice -}} 10 + 11 + {{- range $lines -}} 12 + {{- if strings.Contains . $start -}} 13 + {{- $inSection = true -}} 14 + {{- end -}} 15 + 16 + {{- if $inSection -}} 17 + {{- $output = $output | append . -}} 18 + {{- end -}} 19 + 20 + {{- if strings.Contains . $end -}} 21 + {{- $inSection = false -}} 22 + {{- end -}} 23 + {{- end -}} 24 + 25 + {{- $output | delimit "\n" | markdownify -}} 26 + {{- else -}} 27 + {{- $content | markdownify -}} 28 + {{- end -}}
+4 -1
docs/proton-bridge.md docs/content/docs/configurations/proton-bridge.md
··· 1 - # Configuring neomd with Proton Mail Bridge 1 + --- 2 + title: Proton Mail Bridge 3 + weight: 2 4 + --- 2 5 3 6 Proton Mail Bridge allows you to use neomd with ProtonMail accounts by running a local IMAP/SMTP bridge. 4 7
+5 -2
docs/reading.md docs/content/docs/reading.md
··· 1 - # Reading Emails 1 + --- 2 + title: Reading Emails 3 + weight: 4 4 + --- 2 5 3 6 Emails are rendered as styled Markdown in the terminal using [glamour](https://github.com/charmbracelet/glamour). The reader supports vim-style navigation. 4 7 ··· 76 79 - Threads are sorted by their most recent email, so active conversations float to the top 77 80 78 81 Or as image: 79 - ![neomd](../images/reader-threaded.png) 82 + ![neomd](/images/reader-threaded.png) 80 83 81 84 82 85 ## Replying, Forwarding, and Drafts
+5 -2
docs/screener.md docs/content/docs/screener.md
··· 1 - # Screener Workflow 1 + --- 2 + title: Screener Workflow 3 + weight: 3 4 + --- 2 5 3 6 The screener classifies senders into four buckets using plain-text allowlists. Unknown senders land in `ToScreen` until you make a decision. 4 7 ··· 55 58 56 59 Emails are only auto-screened while they are in the **Inbox**. Once moved to ToScreen (or any other folder), they are not re-classified automatically. This keeps the logic simple and predictable. 57 60 58 - If emails end up in ToScreen incorrectly (e.g. screened by another device like [Termux with Android](android.md) with incomplete lists), use `:reset-toscreen` to move them back to Inbox where auto-screen will re-classify them. 61 + If emails end up in ToScreen incorrectly (e.g. screened by another device like [Termux with Android](configurations/android) with incomplete lists), use `:reset-toscreen` to move them back to Inbox where auto-screen will re-classify them. 59 62 60 63 ## Colon commands 61 64
+11 -7
docs/sending.md docs/content/docs/sending.md
··· 1 - # How Sending Works 1 + --- 2 + title: Sending Emails 3 + weight: 5 4 + --- 2 5 3 6 neomd sends every email as `multipart/alternative`: 4 7 ··· 89 92 **From within neovim** — press `<leader>a` in any `neomd-*.md` buffer to open yazi in a floating terminal. Selected files are inserted at the cursor as visible `[attach] /path/to/file` lines. 90 93 91 94 92 - > [!NOTE] 93 - > **Requires** [custom.lua](https://github.com/sspaeti/dotfiles/blob/master/nvim/.config/nvim/lua/sspaeti/custom.lua) added to your neovim config, and [yazi](https://github.com/sxyazi/yazi) installed. 95 + {{< callout type="info" >}} 96 + **Requires** [custom.lua](https://github.com/sspaeti/dotfiles/blob/master/nvim/.config/nvim/lua/sspaeti/custom.lua) added to your neovim config, and [yazi](https://github.com/sxyazi/yazi) installed. 97 + {{< /callout >}} 94 98 95 99 neomd strips `[attach]` lines before sending: 96 100 - **Image files** (`.png`, `.jpg`, `.gif`, `.webp`, `.svg`) → embedded inline in the HTML body; recipients see the image at that position ··· 104 108 [attach] /home/you/docs/report.pdf 105 109 ``` 106 110 107 - ![neomd](../images/attachments-example.webp) 111 + ![neomd](/images/attachments-example.webp) 108 112 109 113 ## Pre-send Review 110 114 111 115 After saving and closing the editor, neomd shows a review screen before sending — add or remove attachments, save to Drafts, or re-open the editor without sending accidentally. 112 116 113 - ![neomd](../images/presend-navigation.png) 117 + ![neomd](/images/presend-navigation.png) 114 118 115 119 | Key | Action | 116 120 |-----|--------| ··· 175 179 - Test your HTML signature by sending to yourself first 176 180 - The `--` separator is added automatically before the text signature 177 181 178 - For full HTML signature configuration examples, see [configuration.md](configuration.md#html-signatures). 182 + For full HTML signature configuration examples, see [Configuration Reference](configuration#html-signatures). 179 183 180 - For reading emails — images, links, attachments, and navigation — see [reading.md](reading.md). 184 + For reading emails — images, links, attachments, and navigation — see [Reading Emails](reading).
docs/static/images/android-overview.png

This is a binary file and will not be displayed.

docs/static/images/android-reading.png

This is a binary file and will not be displayed.

docs/static/images/attachments-example.webp

This is a binary file and will not be displayed.

docs/static/images/gmail.png

This is a binary file and will not be displayed.

docs/static/images/html-signature.png

This is a binary file and will not be displayed.

docs/static/images/overview-email-feed.png

This is a binary file and will not be displayed.

docs/static/images/presend-navigation.png

This is a binary file and will not be displayed.

docs/static/images/reader-threaded.png

This is a binary file and will not be displayed.

docs/static/images/reading-email.png

This is a binary file and will not be displayed.

docs/static/images/sent-email.png

This is a binary file and will not be displayed.

+75
scripts/sync-readme-to-docs.sh
··· 1 + #!/usr/bin/env bash 2 + # Sync README.md to docs/content/_index.md to avoid duplication 3 + # Usage: ./scripts/sync-readme-to-docs.sh 4 + 5 + set -euo pipefail 6 + 7 + REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" 8 + README="$REPO_ROOT/README.md" 9 + DOCS_OVERVIEW="$REPO_ROOT/docs/content/docs/overview.md" 10 + 11 + if [[ ! -f "$README" ]]; then 12 + echo "Error: README.md not found at $README" 13 + exit 1 14 + fi 15 + 16 + # Extract content from README (skip the first H1 title line) 17 + README_CONTENT=$(tail -n +3 "$README") 18 + 19 + # Create overview.md with Hugo frontmatter 20 + cat > "$DOCS_OVERVIEW" <<'FRONTMATTER' 21 + --- 22 + title: Overview & Philosophy 23 + weight: 0 24 + --- 25 + 26 + FRONTMATTER 27 + 28 + # Process README content and convert GitHub-style callouts to Hugo callouts 29 + # Use awk for proper multi-line callout handling 30 + echo "$README_CONTENT" | awk ' 31 + BEGIN { in_callout = 0; callout_type = ""; callout_content = "" } 32 + /^> \[!WARNING\]/ { in_callout = 1; callout_type = "warning"; next } 33 + /^> \[!NOTE\]/ { in_callout = 1; callout_type = "info"; next } 34 + /^> \[!TIP\]/ { in_callout = 1; callout_type = "info"; next } 35 + /^> / { 36 + if (in_callout) { 37 + line = substr($0, 3) # Remove "> " prefix 38 + if (callout_content != "") callout_content = callout_content "\n" 39 + callout_content = callout_content line 40 + next 41 + } 42 + } 43 + { 44 + if (in_callout) { 45 + print "{{< callout type=\"" callout_type "\" >}}" 46 + print callout_content 47 + print "{{< /callout >}}" 48 + print "" 49 + in_callout = 0 50 + callout_type = "" 51 + callout_content = "" 52 + } 53 + print 54 + } 55 + END { 56 + if (in_callout) { 57 + print "{{< callout type=\"" callout_type "\" >}}" 58 + print callout_content 59 + print "{{< /callout >}}" 60 + } 61 + } 62 + ' | sed \ 63 + -e 's|docs/gmail\.md|configurations/gmail|g' \ 64 + -e 's|docs/proton-bridge\.md|configurations/proton-bridge|g' \ 65 + -e 's|docs/android\.md|configurations/android|g' \ 66 + -e 's|docs/configuration\.md|configuration|g' \ 67 + -e 's|docs/keybindings\.md|keybindings|g' \ 68 + -e 's|docs/screener\.md|screener|g' \ 69 + -e 's|docs/sending\.md|sending|g' \ 70 + -e 's|docs/reading\.md|reading|g' \ 71 + -e 's|images/|/images/|g' \ 72 + >> "$DOCS_OVERVIEW" 73 + 74 + echo "✅ Synced README.md → docs/content/docs/overview.md" 75 + echo " Next: Run 'make docs-build' to regenerate the site"