···11# Binary (root-level compiled binary only, not cmd/neomd/ source dir)
22/neomd
33+/neomd-freebsd
34/neomd-android
4556# Go build cache
+5
CHANGELOG.md
···11# Changelog
2233+# 2026-04-18
44+- **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 every `bg_sync_interval` minutes (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 with `log/slog`; use case: run daemon on NAS with `bg_sync_interval = 5`, disable background sync on laptop/Android with `bg_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 in `docs/configurations/headless.md` with Syncthing setup guide, systemd service example, and multi-device workflow; `make daemon` target for testing
55+- **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
66+- **Cross-compile FreeBSD binary on build** — `make build` now automatically creates `neomd-freebsd` static binary alongside Linux binary; FreeBSD binary can be copied to FreeBSD/OpenBSD servers without needing Go installed; `make sync-headless` target copies to remote server via scp
77+38# 2026-04-17
49- **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
510- **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
+8-2
Makefile
···44VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
55LDFLAGS := -ldflags "-X main.version=$(VERSION)"
6677-.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
77+.PHONY: build run install daemon clean test test-integration send-test vet fmt tidy release docs help check-go demo demo-reset demo-hp demo-hp-reset benchmark
88991010.DEFAULT_GOAL := install
···2929## build: compile ./neomd (version from git tag)
3030build: check-go docs
3131 go build $(LDFLAGS) -o $(BINARY) $(CMD)
3232-3232+ CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -o neomd-freebsd ./cmd/neomd # Build static binary for FreeBSD
3333## run: build and run
3434run: build
3535 ./$(BINARY) $(ARGS)
···3939 install -Dm755 $(BINARY) $(INSTALL)/$(BINARY)
4040 @echo "Installed to $(INSTALL)/$(BINARY)"
41414242+## daemon: run in headless daemon mode
4343+daemon: build
4444+ ./$(BINARY) --headless
42454346## demo: run neomd with demo account (~/.config/neomd-demo/config.toml)
4447demo: build
···139142## docs-clean: remove generated Hugo files
140143docs-clean:
141144 $(MAKE) -C docs clean
145145+146146+sync-headless:
147147+ scp neomd-freebsd ti:~/neomd
142148143149## help: print this list
144150help:
+1
README.md
···159159- **Multi-select** — `m` marks emails, then batch-delete, move, or screen them all at once [[more](https://ssp-data.github.io/neomd/docs/keybindings/#multi-select--undo)]
160160- **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) [[more](https://ssp-data.github.io/neomd/docs/screener/#auto-screen-and-background-sync)]
161161- **Background sync** — while neomd is open, inbox is fetched and screened every 5 minutes in the background; interval configurable, set to `0` to disable [[more](https://ssp-data.github.io/neomd/docs/screener/#auto-screen-and-background-sync)]
162162+- **Headless daemon mode** — run `neomd --headless` on a server to continuously screen emails in the background without the TUI; watches screener list files for changes via Syncthing; emails are auto-screened every `bg_sync_interval` minutes so mobile apps see correctly filtered IMAP folders; perfect for running on a NAS while using the TUI on laptop/Android [[more](https://ssp-data.github.io/neomd/docs/configurations/headless/)]
162163- **Kanagawa theme** — colors from the [kanagawa.nvim](https://github.com/rebelot/kanagawa.nvim) palette
163164- **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 [[more](https://ssp-data.github.io/neomd/docs/configuration/)]
164165
+23-10
cmd/neomd/main.go
···11111212 tea "github.com/charmbracelet/bubbletea"
1313 "github.com/sspaeti/neomd/internal/config"
1414+ "github.com/sspaeti/neomd/internal/daemon"
1415 goIMAP "github.com/sspaeti/neomd/internal/imap"
1516 "github.com/sspaeti/neomd/internal/oauth2"
1617 "github.com/sspaeti/neomd/internal/screener"
···2324func main() {
2425 cfgPath := flag.String("config", "", "path to config.toml (default: ~/.config/neomd/config.toml)")
2526 showVersion := flag.Bool("version", false, "print version and exit")
2727+ headless := flag.Bool("headless", false, "run in headless daemon mode (no TUI)")
2628 flag.Parse()
27292830 if *showVersion {
···119121 os.Exit(1)
120122 }
121123122122- ui.Version = version
123123- model := ui.New(cfg, imapClients, sc)
124124+ // Fork: run either headless daemon or TUI
125125+ if *headless {
126126+ // Headless daemon mode: run background screening loop
127127+ d := daemon.New(*cfg, imapClients[0], sc)
128128+ if err := d.Run(ctx); err != nil {
129129+ fmt.Fprintf(os.Stderr, "neomd: daemon error: %v\n", err)
130130+ os.Exit(1)
131131+ }
132132+ } else {
133133+ // TUI mode: run interactive interface
134134+ ui.Version = version
135135+ model := ui.New(cfg, imapClients, sc)
124136125125- p := tea.NewProgram(
126126- model,
127127- tea.WithAltScreen(),
128128- tea.WithMouseCellMotion(),
129129- )
130130- if _, err := p.Run(); err != nil {
131131- fmt.Fprintf(os.Stderr, "neomd: %v\n", err)
132132- os.Exit(1)
137137+ p := tea.NewProgram(
138138+ model,
139139+ tea.WithAltScreen(),
140140+ tea.WithMouseCellMotion(),
141141+ )
142142+ if _, err := p.Run(); err != nil {
143143+ fmt.Fprintf(os.Stderr, "neomd: %v\n", err)
144144+ os.Exit(1)
145145+ }
133146 }
134147}
135148
+30-28
docs/content/docs/_index.md
···8282*all colored boxes represent neomd folders*
83838484**Key principles:**
8585-- **Screener first**: Unknown senders never clutter your Inbox — they wait in ToScreen for classification
8686-- **One-time decision**: Once you classify a sender (`I/O/F/P`), all future emails from them are automatically routed
8787-- **GTD processing**: Emails in Inbox are processed once — if < 2 min, do it or keep it in inbox as doing *Next* otherwise move to Waiting, Someday, or Scheduled
8888-- **Minimal filing**: Only Archive when done; no complex folder hierarchies — use search to find old emails
8989-- **Separate contexts**: Feed for newsletters (read when you want), PaperTrail for receipts (search when needed)
8585+- **Screener first**: Unknown senders never clutter your Inbox — they wait in ToScreen for classification [[more](https://ssp-data.github.io/neomd/docs/screener/)]
8686+- **One-time decision**: Once you classify a sender (`I/O/F/P`), all future emails from them are automatically routed [[more](https://ssp-data.github.io/neomd/docs/screener/#how-classification-works)]
8787+- **GTD processing**: Emails in Inbox are processed once — if < 2 min, do it or keep it in inbox as doing *Next* otherwise move to Waiting, Someday, or Scheduled
8888+- **Minimal filing**: Only Archive when done; no complex folder hierarchies — use search to find old emails
8989+- **Separate contexts**: Feed for newsletters (read when you want), PaperTrail for receipts (search when needed)
9090+- See full features list below.
909191929293## Screenshots
···143144144145## Features
145146146146-- **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
147147-- **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
148148-- **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
149149-- **Link opener** — links in emails are numbered `[1]`-`[0]` in the reader header; press `space+digit` to open in `$BROWSER`
150150-- **CC, BCC, Reply-all** — optional Cc/Bcc fields (toggle with `ctrl+b`); `R` in the reader replies to sender + all CC recipients
151151-- **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; reactions appear in conversation threads with neomd branding
152152-- **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
153153-- **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)
154154-- **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
155155-- **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
156156-- **Undo** — `u` reverses the last move or delete (`x`, `A`, `M*`) using the UIDPLUS destination UID
157157-- **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
158158-- **Address autocomplete** — To/Cc/Bcc fields autocomplete from screener lists; navigate with `ctrl+n`/`ctrl+p`, accept with `tab`
159159-- **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
160160-- **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
161161-- **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
162162-- **Glamour reading** — incoming emails rendered as styled Markdown in the terminal
163163-- **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
164164-- **Folder tabs** — Inbox, ToScreen, Feed, PaperTrail, Archive, Waiting, Someday, Scheduled, Sent, Trash, ScreenedOut
165165-- **Multi-select** — `m` marks emails, then batch-delete, move, or screen them all at once
166166-- **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)
167167-- **Background sync** — while neomd is open, inbox is fetched and screened every 5 minutes in the background; interval configurable, set to `0` to disable
147147+- **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 [[more](https://ssp-data.github.io/neomd/docs/sending/)]
148148+- **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 [[more](https://ssp-data.github.io/neomd/docs/sending/#pre-send-review)]
149149+- **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 [[more](https://ssp-data.github.io/neomd/docs/sending/#attachments)]
150150+- **Link opener** — links in emails are numbered `[1]`-`[0]` in the reader header; press `space+digit` to open in `$BROWSER` [[more](https://ssp-data.github.io/neomd/docs/reading/#links)]
151151+- **CC, BCC, Reply-all** — optional Cc/Bcc fields (toggle with `ctrl+b`); `R` in the reader replies to sender + all CC recipients [[more](https://ssp-data.github.io/neomd/docs/sending/#cc-bcc-reply-all-and-forward)]
152152+- **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) [[more](https://ssp-data.github.io/neomd/docs/sending/#drafts)]
153153+- **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 [[more](https://ssp-data.github.io/neomd/docs/configuration/#html-signatures)]
154154+- **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 [[more](https://ssp-data.github.io/neomd/docs/sending/#multiple-from-addresses)]
155155+- **Undo** — `u` reverses the last move or delete (`x`, `A`, `M*`) using the UIDPLUS destination UID [[more](https://ssp-data.github.io/neomd/docs/keybindings/#multi-select--undo)]
156156+- **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 [[more](https://ssp-data.github.io/neomd/docs/keybindings/#leader-key-mappings-space-prefix)]
157157+- **Address autocomplete** — To/Cc/Bcc fields autocomplete from screener lists; navigate with `ctrl+n`/`ctrl+p`, accept with `tab`
158158+- **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 [[more](https://ssp-data.github.io/neomd/docs/keybindings/#folders)]
159159+- **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 [[more](https://ssp-data.github.io/neomd/docs/reading/#threaded-inbox)]
160160+- **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 [[more](https://ssp-data.github.io/neomd/docs/reading/#conversation-view)]
161161+- **Glamour reading** — incoming emails rendered as styled Markdown in the terminal [[more](https://ssp-data.github.io/neomd/docs/reading/)]
162162+- **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 [[more](https://ssp-data.github.io/neomd/docs/screener/)]
163163+- **Folder tabs** — Inbox, ToScreen, Feed, PaperTrail, Archive, Waiting, Someday, Scheduled, Sent, Trash, ScreenedOut [[more](https://ssp-data.github.io/neomd/docs/keybindings/#folders)]
164164+- **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; reactions appear in conversation threads with neomd branding [[more](https://ssp-data.github.io/neomd/docs/sending/#emoji-reactions)]
165165+- **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 [[more](https://ssp-data.github.io/neomd/docs/sending/#callouts-admonition)]
166166+- **Multi-select** — `m` marks emails, then batch-delete, move, or screen them all at once [[more](https://ssp-data.github.io/neomd/docs/keybindings/#multi-select--undo)]
167167+- **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) [[more](https://ssp-data.github.io/neomd/docs/screener/#auto-screen-and-background-sync)]
168168+- **Background sync** — while neomd is open, inbox is fetched and screened every 5 minutes in the background; interval configurable, set to `0` to disable [[more](https://ssp-data.github.io/neomd/docs/screener/#auto-screen-and-background-sync)]
169169+- **Headless daemon mode** — run `neomd --headless` on a server to continuously screen emails in the background without the TUI; watches screener list files for changes via Syncthing; emails are auto-screened every `bg_sync_interval` minutes so mobile apps see correctly filtered IMAP folders; perfect for running on a NAS while using the TUI on laptop/Android [[more](https://ssp-data.github.io/neomd/docs/configurations/headless/)]
168170- **Kanagawa theme** — colors from the [kanagawa.nvim](https://github.com/rebelot/kanagawa.nvim) palette
169169-- **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
171171+- **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 [[more](https://ssp-data.github.io/neomd/docs/configuration/)]
170172171173## Install
172174
+435
docs/content/docs/configurations/headless.md
···11+---
22+title: Headless Daemon Mode
33+weight: 4
44+---
55+66+Neomd can run in headless daemon mode to continuously screen emails in the background without launching the TUI. This is useful for running neomd on a server (like a NAS) that screens emails automatically, while you use the TUI on your laptop or mobile device.
77+88+## Overview
99+1010+When running in headless mode, neomd:
1111+- **Screens emails automatically** every `bg_sync_interval` minutes
1212+- **Watches screener list files** and reloads when they change (via Syncthing)
1313+- **Runs in the background** as a standard process
1414+- **Logs to stdout** for monitoring
1515+1616+## Quick Start
1717+1818+```sh
1919+# Run in foreground (for testing)
2020+neomd --headless
2121+2222+# Run in background
2323+neomd --headless &
2424+2525+# Run in background with logging
2626+nohup neomd --headless > /var/log/neomd.log 2>&1 &
2727+2828+# Or redirect to a file
2929+neomd --headless >> ~/.local/share/neomd/daemon.log 2>&1 &
3030+```
3131+3232+## Multi-Device Setup with Syncthing
3333+3434+The headless daemon is designed to work with [Syncthing](https://syncthing.net/) to keep screener lists synchronized across multiple devices.
3535+3636+### Architecture
3737+3838+1. **NAS/Server**: Runs `neomd --headless` continuously, screening emails every 5 minutes
3939+2. **Laptop**: Runs TUI with `bg_sync_interval = 0` (disabled), classifies senders manually
4040+3. **Android/Mobile**: Runs TUI with `bg_sync_interval = 0` (disabled), classifies senders manually
4141+4. **Syncthing**: Syncs screener list files across all devices
4242+4343+### Benefits
4444+4545+- **Automatic screening**: Emails are screened on the server even when your laptop/phone is offline
4646+- **Mobile email apps work**: Your phone's native email app sees screened emails in the correct IMAP folders
4747+- **No conflicts**: Only the daemon moves emails; TUI instances only classify senders
4848+- **Instant sync**: Classification decisions propagate to all devices via Syncthing
4949+5050+## Configuration
5151+5252+### Server Config (Daemon)
5353+5454+On your NAS/server, set `bg_sync_interval` to enable periodic screening:
5555+5656+```toml
5757+# ~/.config/neomd/config.toml (server)
5858+[ui]
5959+bg_sync_interval = 5 # Screen inbox every 5 minutes
6060+```
6161+6262+### Laptop/Mobile Config (TUI)
6363+6464+On devices where you run the TUI, **disable background sync** to avoid duplicate moves:
6565+6666+```toml
6767+# ~/.config/neomd/config.toml (laptop/mobile)
6868+[ui]
6969+bg_sync_interval = 0 # Disable background screening (daemon handles it)
7070+```
7171+7272+## Syncthing Setup
7373+7474+### What Gets Synced
7575+7676+**Screener list directory**: `~/.config/neomd/lists/` - you can also sync the entire `neomd` folder, if you don't have passwords stored in there, only ENVs:
7777+- `screened_in.txt`
7878+- `screened_out.txt`
7979+- `feed.txt`
8080+- `papertrail.txt`
8181+- `spam.txt`
8282+8383+### Step-by-Step Setup
8484+8585+#### 1. Install Syncthing
8686+8787+**On Arch Linux / Server:**
8888+```sh
8989+sudo pacman -S syncthing
9090+systemctl --user enable syncthing
9191+systemctl --user start syncthing
9292+```
9393+9494+**On other systems**: See [Syncthing installation docs](https://docs.syncthing.net/intro/getting-started.html)
9595+9696+> **Note**: Use `systemctl --user` instead of adding to window manager autostart scripts. This ensures Syncthing runs on login, works across different environments, and continues running independently of your desktop session.
9797+9898+#### 2. Access Web UI
9999+100100+Open http://localhost:8384 on each device
101101+102102+#### 3. Connect Devices
103103+104104+**On Device A (e.g., your laptop):**
105105+1. Go to **Actions** → **Show ID** to get your Device ID
106106+2. Copy the long alphanumeric Device ID
107107+108108+**On Device B (e.g., your server):**
109109+1. Click **Add Remote Device**
110110+2. Paste Device A's Device ID
111111+3. Name it (e.g., "laptop")
112112+4. Click **Save**
113113+114114+**Back on Device A:**
115115+1. Accept the connection notification
116116+2. Name Device B (e.g., "server")
117117+3. Click **Save**
118118+119119+Repeat for all devices (laptop, server, Android).
120120+121121+#### 4. Create Shared Folder
122122+123123+**On one device (e.g., server):**
124124+1. Click **Add Folder**
125125+2. Set **Folder Label**: `neomd-lists`
126126+3. Set **Folder ID**: `neomd-lists` (same on all devices)
127127+4. Set **Folder Path**: `/home/user/.config/neomd/lists/` (or `~/.config/neomd/` if syncing entire folder)
128128+5. Go to **Sharing** tab → check all other devices
129129+6. Go to **File Versioning** tab:
130130+ - Select **Simple File Versioning**
131131+ - Keep Versions: `5`
132132+7. Click **Save**
133133+134134+**On other devices:**
135135+1. Accept the folder share notification
136136+2. Verify/set the correct path for that device
137137+3. Enable **File Versioning** (same as above)
138138+4. Click **Save**
139139+140140+#### 5. Backup First (Important!)
141141+142142+Before syncing existing data, **backup your lists**:
143143+144144+```sh
145145+cp -r ~/.config/neomd/lists ~/.config/neomd/lists.backup-$(date +%Y%m%d)
146146+```
147147+148148+#### 6. Wait for Initial Sync
149149+150150+Watch the folder status in the web UI. It will show "Syncing" with progress, then "Up to Date" when complete.
151151+152152+### Server Setup (FreeBSD / No GUI)
153153+154154+If running neomd headless on a FreeBSD server without a desktop environment, use SSH port forwarding to access the Syncthing web UI:
155155+156156+#### 1. Start Syncthing on FreeBSD
157157+158158+```sh
159159+# Enable and start as system service
160160+sudo sysrc syncthing_enable="YES"
161161+sudo sysrc syncthing_user="sspaeti"
162162+sudo service syncthing start
163163+164164+# Or run as user service (no sudo)
165165+syncthing &
166166+167167+# Or with nohup for persistent operation
168168+nohup syncthing > ~/syncthing.log 2>&1 &
169169+```
170170+171171+Check it's running:
172172+```sh
173173+ps aux | grep syncthing
174174+```
175175+176176+#### 2. SSH Port Forwarding
177177+178178+From your **local machine** (laptop/desktop with browser), create an SSH tunnel:
179179+180180+```sh
181181+ssh -L 8385:localhost:8384 your-server
182182+```
183183+184184+This forwards `localhost:8385` on your local machine to `localhost:8384` on the server.
185185+186186+Now open in your **local browser**: http://localhost:8385
187187+188188+You'll see the server's Syncthing web UI!
189189+190190+#### 3. Get Server Device ID
191191+192192+In the web UI at http://localhost:8385:
193193+1. Go to **Actions** → **Show ID**
194194+2. Copy the Device ID
195195+196196+#### 4. Connect Your Devices
197197+198198+**On your local machine's Syncthing** (http://localhost:8384):
199199+1. Click **Add Remote Device**
200200+2. Paste the server's Device ID
201201+3. Name it (e.g., "freebsd-server")
202202+4. Click **Save**
203203+204204+**On the server's UI** (http://localhost:8385 via SSH tunnel):
205205+1. Accept the connection notification
206206+2. Name your local device (e.g., "laptop")
207207+3. Click **Save**
208208+209209+#### 5. Share the Folder
210210+211211+**On your local machine** (http://localhost:8384):
212212+1. Find your existing `neomd-lists` folder
213213+2. Click **Edit**
214214+3. Go to **Sharing** tab
215215+4. Check the box next to your server device
216216+5. Click **Save**
217217+218218+**On the server** (http://localhost:8385):
219219+1. Accept the folder share notification
220220+2. Set **Folder Path**: `/home/user/.config/neomd/lists/` (or `~/.config/neomd/` if syncing entire folder)
221221+3. Go to **File Versioning** tab:
222222+ - Select **Simple File Versioning**
223223+ - Keep Versions: `5`
224224+4. Click **Save**
225225+226226+#### 6. Handle Existing Files
227227+228228+Before syncing, **backup the server's existing lists**:
229229+230230+```sh
231231+# On server
232232+cp -r ~/.config/neomd/lists ~/.config/neomd/lists.backup-$(date +%Y%m%d)
233233+```
234234+235235+Syncthing will merge files from both sides. To **start fresh from your local machine's data**:
236236+237237+```sh
238238+# On server - remove existing files (after backup!)
239239+rm -rf ~/.config/neomd/lists/*
240240+```
241241+242242+#### 7. Close SSH Tunnel
243243+244244+Once setup is complete, you can close the SSH tunnel (Ctrl+C in the SSH session). Devices will continue syncing in the background.
245245+246246+For future configuration changes, create the SSH tunnel again when needed:
247247+```sh
248248+ssh -L 8385:localhost:8384 your-server
249249+```
250250+251251+### Verify Sync is Working
252252+253253+```sh
254254+# Check files exist
255255+ls -la ~/.config/neomd/lists/
256256+257257+# Watch real-time sync in logs
258258+journalctl --user -u syncthing -f
259259+```
260260+261261+The daemon watches for file changes and reloads screener lists automatically when Syncthing updates them.
262262+263263+### Conflict Handling
264264+265265+- **File-level conflicts**: Syncthing creates `.sync-conflict-*` files if two devices modify the same file simultaneously
266266+- **Email-level**: IMAP is the source of truth; no local email state to conflict
267267+- **Screener lists**: Append-only operations are safe; duplicates are harmless (normalized automatically)
268268+269269+Check for conflicts periodically:
270270+```sh
271271+find ~/.config/neomd/lists -name "*.sync-conflict-*"
272272+```
273273+274274+## Systemd Service (Optional)
275275+276276+For servers with systemd, you can create a service unit for automatic startup and logging:
277277+278278+```ini
279279+# /etc/systemd/user/neomd.service
280280+[Unit]
281281+Description=Neomd Headless Email Screener
282282+After=network.target
283283+284284+[Service]
285285+Type=simple
286286+ExecStart=%h/.local/bin/neomd --headless
287287+Restart=on-failure
288288+RestartSec=10
289289+StandardOutput=journal
290290+StandardError=journal
291291+292292+[Install]
293293+WantedBy=default.target
294294+```
295295+296296+Enable and start the service:
297297+298298+```sh
299299+# Install neomd to ~/.local/bin
300300+make install
301301+302302+# Reload systemd
303303+systemctl --user daemon-reload
304304+305305+# Enable auto-start on login
306306+systemctl --user enable neomd
307307+308308+# Start now
309309+systemctl --user start neomd
310310+311311+# Check status
312312+systemctl --user status neomd
313313+314314+# View logs
315315+journalctl --user -u neomd -f
316316+```
317317+318318+## Monitoring
319319+320320+### View Logs
321321+322322+If running with `nohup` or redirected output:
323323+324324+```sh
325325+tail -f /var/log/neomd.log
326326+```
327327+328328+If running as systemd service:
329329+330330+```sh
331331+journalctl --user -u neomd -f
332332+```
333333+334334+### Log Format
335335+336336+The daemon logs structured output with timestamps:
337337+338338+```
339339+time=2025-04-18T10:00:00Z level=INFO msg="neomd daemon starting" version=headless
340340+time=2025-04-18T10:00:00Z level=INFO msg="screening interval configured" minutes=5
341341+time=2025-04-18T10:00:00Z level=INFO msg="watching directory for changes" dir=/home/user/.config/neomd/lists
342342+time=2025-04-18T10:00:00Z level=INFO msg="daemon running" interval=5m0s
343343+time=2025-04-18T10:00:05Z level=INFO msg="running initial screening"
344344+time=2025-04-18T10:00:05Z level=INFO msg="fetched inbox emails" count=42
345345+time=2025-04-18T10:00:05Z level=INFO msg="emails to screen" count=12
346346+time=2025-04-18T10:00:05Z level=INFO msg="screened email" index=1 total=12 from="newsletter@example.com" subject="Weekly Update" dst=Feed
347347+...
348348+time=2025-04-18T10:00:06Z level=INFO msg="screening complete" moved=12 total=12
349349+```
350350+351351+### Graceful Shutdown
352352+353353+Send SIGTERM or SIGINT to stop the daemon:
354354+355355+```sh
356356+# If running in foreground
357357+Ctrl+C
358358+359359+# If running in background
360360+kill <pid>
361361+362362+# With systemd
363363+systemctl --user stop neomd
364364+```
365365+366366+The daemon will finish the current screening operation before exiting.
367367+368368+## Troubleshooting
369369+370370+### Daemon exits immediately
371371+372372+Check that `bg_sync_interval` is set to a value > 0:
373373+374374+```sh
375375+grep bg_sync_interval ~/.config/neomd/config.toml
376376+```
377377+378378+### Screener lists not reloading
379379+380380+Check file watcher logs:
381381+382382+```sh
383383+tail -f /var/log/neomd.log | grep "watching directory"
384384+```
385385+386386+Verify Syncthing is running and syncing:
387387+388388+```sh
389389+# Check Syncthing web UI (usually http://localhost:8384)
390390+```
391391+392392+### Emails not being screened
393393+394394+1. Check daemon is running: `ps aux | grep neomd`
395395+2. Check IMAP connection in logs
396396+3. Verify screener list files exist and contain email addresses
397397+4. Check folder configuration in config.toml
398398+399399+### Duplicate screening
400400+401401+If emails are being moved twice (once by daemon, once by TUI):
402402+403403+- Set `bg_sync_interval = 0` on TUI devices
404404+- Only run one daemon instance per account
405405+406406+## Android Termux Example
407407+408408+>[!NOTE]
409409+> See Android Termux Setup at [Android Docs](android.md)
410410+411411+On Android, you can run the daemon in a Termux session:
412412+413413+```sh
414414+# Install Termux:Boot from F-Droid to auto-start on device boot
415415+pkg install termux-boot
416416+417417+# Create boot script
418418+mkdir -p ~/.termux/boot
419419+cat > ~/.termux/boot/neomd-daemon.sh <<'EOF'
420420+#!/data/data/com.termux/files/usr/bin/bash
421421+cd ~/neomd
422422+nohup ./neomd --headless >> ~/neomd-daemon.log 2>&1 &
423423+EOF
424424+chmod +x ~/.termux/boot/neomd-daemon.sh
425425+426426+# Reboot device to auto-start daemon
427427+```
428428+429429+## Notes
430430+431431+- The daemon only **reads** screener list files and **moves** emails via IMAP
432432+- All sender classification (adding to lists) happens in the TUI
433433+- File watching requires the screener list directory to exist
434434+- The daemon uses the first configured account from `config.toml`
435435+- IMAP connection is kept alive and automatically reconnects on failures