A minimal email TUI where you read with Markdown and write in Neovim.
neomd.ssp.sh/docs
email
markdown
neovim
tui
1BINARY := neomd
2CMD := ./cmd/neomd
3INSTALL := $(HOME)/.local/bin
4VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
5LDFLAGS := -ldflags "-X main.version=$(VERSION)"
6
7.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
8
9
10.DEFAULT_GOAL := install
11
12## check-go: verify Go is installed
13check-go:
14 @command -v go >/dev/null 2>&1 || { \
15 echo ""; \
16 echo " Error: Go is not installed."; \
17 echo ""; \
18 echo " Install Go 1.22+ from https://go.dev/doc/install"; \
19 echo ""; \
20 echo " Quick install (Linux):"; \
21 echo " curl -LO https://go.dev/dl/go1.24.2.linux-amd64.tar.gz"; \
22 echo " sudo tar -C /usr/local -xzf go1.24.2.linux-amd64.tar.gz"; \
23 echo ' echo "export PATH=$$PATH:/usr/local/go/bin" >> ~/.bashrc'; \
24 echo " source ~/.bashrc"; \
25 echo ""; \
26 exit 1; \
27 }
28
29## build: compile ./neomd (version from git tag)
30build: check-go docs
31 go build $(LDFLAGS) -o $(BINARY) $(CMD)
32 CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -o neomd-freebsd ./cmd/neomd # Build static binary for FreeBSD
33## run: build and run
34run: build
35 ./$(BINARY) $(ARGS)
36
37## install: install to ~/.local/bin
38install: docs build
39 install -Dm755 $(BINARY) $(INSTALL)/$(BINARY)
40 @echo "Installed to $(INSTALL)/$(BINARY)"
41
42## daemon: run in headless daemon mode
43daemon: build
44 ./$(BINARY) --headless
45
46## demo: run neomd with demo account (~/.config/neomd-demo/config.toml)
47demo: build
48 ./$(BINARY) -config $(HOME)/.config/neomd-demo/config.toml
49
50## demo-reset: reset demo account to first-run state (welcome screen + empty screener lists)
51demo-reset:
52 ./scripts/reset-demo.sh $(HOME)/.config/neomd-demo
53
54## demo-hp: run neomd with Hostpoint demo account (fast)
55demo-hp: build
56 ./$(BINARY) -config $(HOME)/.config/neomd-demo-hostpoint/config.toml
57
58## demo-hp-reset: reset Hostpoint demo to first-run state
59demo-hp-reset:
60 ./scripts/reset-demo.sh $(HOME)/.config/neomd-demo-hostpoint
61
62## benchmark: benchmark IMAP latency for Hostpoint and Gmail
63benchmark:
64 @echo "=== Hostpoint ==="
65 @IMAP_HOST=imap.mail.hostpoint.ch IMAP_USER=simu@sspaeti.com IMAP_PASS=$$IMAP_PASS_SIMU ./scripts/imap-benchmark.sh
66 @echo ""
67 @echo "=== Gmail ==="
68 @IMAP_HOST=imap.gmail.com IMAP_USER=neomd.demo@gmail.com IMAP_PASS=$$IMAP_APPPASS_GMAIL_NEOMD ./scripts/imap-benchmark.sh
69
70## test: run all unit tests (fast, no network)
71test:
72 go test ./...
73
74## test-integration: run integration tests against real IMAP/SMTP (sends emails to demo account)
75test-integration:
76 NEOMD_TEST_IMAP_HOST=imap.mail.hostpoint.ch \
77 NEOMD_TEST_SMTP_HOST=asmtp.mail.hostpoint.ch \
78 NEOMD_TEST_USER=neomd.demo@ssp.sh \
79 NEOMD_TEST_PASS=$$IMAP_PASS_NEOMD_DEMO \
80 NEOMD_TEST_FROM="Neomd Demo <neomd.demo@ssp.sh>" \
81 go test ./internal/ -run TestIntegration -v -count=1 -timeout 120s
82
83## send-test: send a test email to sspaeti@hey.com (override: make send-test TO=other@example.com)
84send-test:
85 go run ./cmd/sendtest $(TO)
86
87## vet: run go vet
88vet:
89 go vet ./...
90
91## fmt: format all Go source files
92fmt:
93 gofmt -w .
94
95## tidy: tidy go.mod and go.sum
96tidy:
97 go mod tidy
98
99## android: cross-compile for Android ARM64 (run in Termux)
100android: check-go docs
101 CGO_ENABLED=0 GOOS=android GOARCH=arm64 go build $(LDFLAGS) -o $(BINARY)-android $(CMD)
102 @echo ""
103 @echo " Built $(BINARY)-android (ARM64)"
104 @echo ""
105 @echo " Transfer to your Android device:"
106 @echo " adb push $(BINARY)-android /sdcard/Download/"
107 @echo ""
108 @echo " Then in Termux:"
109 @echo " cp /sdcard/Download/$(BINARY)-android ~/$(BINARY)"
110 @echo " chmod +x ~/$(BINARY)"
111 @echo " ~/$(BINARY)"
112 @echo ""
113
114## clean: remove compiled binary
115clean:
116 rm -f $(BINARY) $(BINARY)-android
117
118## release: tag and push a new release (usage: make release VERSION=v0.1.0)
119release: docs docs-build
120 @test -n "$(VERSION)" || (echo "Usage: make release VERSION=v0.1.0" && exit 1)
121 git tag -a $(VERSION) -m "Release $(VERSION)"
122 git push origin $(VERSION)
123 @echo "Tagged $(VERSION) — GitHub Actions will build and publish the release."
124
125## docs: regenerate keybindings section in README.md from internal/ui/keys.go
126docs:
127 go run ./cmd/docs
128 @./scripts/sync-readme-to-docs.sh
129
130## docs-sync: sync README.md to docs/content/overview.md
131docs-sync:
132 ./scripts/sync-readme-to-docs.sh
133
134## docs-serve: serve Hugo docs site locally at http://localhost:1313
135docs-serve:
136 $(MAKE) -C docs serve
137
138## docs-build: build Hugo docs site to docs/public/
139docs-build:
140 $(MAKE) -C docs build
141
142## docs-clean: remove generated Hugo files
143docs-clean:
144 $(MAKE) -C docs clean
145
146## sync-headless: deploy FreeBSD binary to ti server and restart daemon
147sync-headless: build
148 @echo "Copying binary and Makefile to ti..."
149 scp neomd-freebsd ti:~/.local/bin/neomd
150 scp scripts/headless-server/Makefile ti:~/Makefile
151 @echo "Restarting daemon..."
152 ssh ti "pkill neomd || true; sleep 2; mkdir -p ~/.local/share/neomd; nohup ~/.local/bin/neomd --headless >> ~/.local/share/neomd/daemon.log 2>&1 &"
153 @echo "Waiting for daemon to start..."
154 @sleep 2
155 @echo "Checking status..."
156 @ssh ti "ps aux | grep '[n]eomd' || echo 'ERROR: neomd is not running'"
157 @echo ""
158 @echo "Checking logs for errors..."
159 @ssh ti "tail -20 ~/.local/share/neomd/daemon.log"
160
161## help: print this list
162help:
163 @grep -E '^## ' Makefile | sed 's/^## //'