A one-binary, self-hosted mailing list manager. A modern, self-contained alternative to GNU Mailman: manage mailing lists, subscriptions, archives, and moderation from a passwordless web UI and a CLI, delivered as a single static binary with an embedded frontend and SQLite storage.
- One binary, no services. Go daemon + embedded SvelteKit web UI + SQLite. Drop it on a VPS or run the container; no separate database, cache, or frontend server.
- No passwords anywhere. Sign-in is passwordless: one-time magic links emailed to the subscriber, backed by short-lived sessions.
- MTA-native. Receives mail over LMTP (Postfix and exim validated) or a pipe-mode socket; delivers outbound through a persistent, retrying queue.
- Full lifecycle. Double opt-in, four-state subscriptions, digests, VERP bounce tracking with auto-disable, held-message moderation, an immutable audit trail, CSV member import/export, and attachment policies.
Mailing lists
- Virtual domains hosting multiple lists; discussion and newsletter list types
- Email-driven workflows:
subscribe,confirm,post,unsubscribe, bounce handling, and-requestcommands (which,set digest, …) — all by writing tolistname+role@domain - Double opt-in subscription confirmation; open / moderated / closed subscription policies
- Per-list settings: moderation, subject prefix, footer, digest frequency, reply-to mode, max message size, attachment policy, archive retention, and configurable welcome/goodbye/notice emails
Moderation & roles
- Held-message moderation (approve / reject / discard) from email, CLI, and web
- Roles: Owner, Moderator, Designated Sender (newsletter allowlist), and instance-wide Administrator — the web console surfaces what you can do
- Immutable audit trail of every privileged action, surfaced on the web and CLI
Delivery
- Persistent outbound queue with exponential backoff; posts bounce to the sender at max retries
- Daily or weekly digests (multipart/digest), compiled from the archive
- VERP envelope senders attribute bounces to the right subscription; auto-disable after a configurable threshold, with owner notification
Web UI
- Public: list index and info with subscribe forms
- Member self-service: manage subscriptions, delivery preference (regular / digest / nomail), re-enable, unsubscribe
- Members-only archives: threaded browsing, full-text search (SQLite FTS5), MIME-aware rendering with sanitized HTML and attachment downloads
- Per-list admin console (settings, members, roles, moderation, allowlist, bounces, audit) and a server-admin area (domains, lists, administrators)
Operations
- CLI parity for administration:
xlistman domain|list|owner|subscriber|… - YAML config with environment overrides and
${ENV_VAR}secret expansion systemdunit and a multi-stagescratch-based Docker image included
Captured from a live instance seeded with demo data — reproduce them with
scripts/screenshot-seed.sh. Click a thumbnail to open it full size.
![]() Public list index | ![]() Member self-service | ![]() Members-only archives | ![]() Moderation queue | ![]() Server administration |
docker run -d --name xlistman \
-p 8080:8080 -p 8024:8024 \
-v xlistman-data:/data \
-e XLISTMAN_WEB_BASE_URL=http://localhost:8080 \
ghcr.io/barats/xlistman:0.2.0The image ships a working default config (/etc/xlistman/config.yaml); any
value can be overridden with XLISTMAN_* environment variables (e.g. point
XLISTMAN_SMTP_HOST / XLISTMAN_SMTP_PORT at your relay). See
Configuration.
Prerequisites: Go 1.25+, and Node.js + pnpm for the embedded web UI.
cd web && pnpm install && pnpm build # build the SvelteKit frontendcd .. && go build -o xlistman .# then the Go binary (UI embedded)
./xlistman config init # generate ./xlistman.yaml, then edit it
./xlistman serveThe web UI is generated and not committed, so
go install github.com/barats/xlistman@latest(and a plaingo build) produces a binary without the web UI — CLI and mail handling only. For the full product, use the Docker image (above) or download a release binary from the GitHub Releases page, which ships the frontend embedded (goreleaser builds it as part of the release).
# create a domain, then a list with its first owner
xlistman domain add example.com "Example domain"
xlistman list create dev@example.com --type discussion --owner you@example.com
xlistman serve # start the daemon (HTTP :8080, LMTP :8024, pipe socket)Open http://localhost:8080, request a login link, and you're in. To receive
mail, wire your MTA to deliver the list domain over LMTP (see below).
xListman integrates with an existing mail server rather than replacing it. The flow: your MTA accepts mail for the list domain and hands it to xListman over LMTP (or the pipe-mode Unix socket); xListman delivers outbound mail back through your relay.
- Postfix:
virtual_mailbox_domains = lists.example.com+virtual_transport = lmtp:[127.0.0.1]:8024 - exim: a
manualrouterouter sending the list domain to ansmtptransport withprotocol = lmtpon127.0.0.1:8024
Both configurations are exercised end-to-end against live local instances,
including the VERP bounce reverse leg. The reproducible harnesses live in
validate/.
Configuration is YAML, loaded from xlistman.yaml (or XLISTMAN_CONFIG),
overlaid with XLISTMAN_* environment variables, and expanded for ${ENV_VAR}
secrets. A minimal runnable config ships as
config.default.yaml; generate a fully commented one
with xlistman config init.
| Area | Key settings |
|---|---|
| HTTP / LMTP / socket | http.listen, lmtp.listen, socket.path |
| Storage | database.path |
| Outbound | smtp.host, smtp.port, smtp.username/password, smtp.mode (smtp|sink), smtp.sink_dir, smtp.tls (none|starttls|starttls-required|implicit), smtp.tls_insecure_skip_verify |
| Web | web.base_url (public origin used in emails), web.site_name (instance name in titles and the UI) |
| Protection | rate_limits.* (subscribe / magic-link / posts per hour) |
| Delivery | queue.max_retries |
See internal/config/config.go for the full list
of environment variable names.
CONTEXT.md— the domain model and glossary (Subscriber, Subscription, List Role, Held Message, …)docs/PLAN.md— build history and roadmapdocs/adr/— 26 architecture decision recordsvalidate/README.md— MTA validation harnessesSECURITY.md— security policy and reporting
Active, pre-1.0. Versioned with semantic versioning; 0.x indicates the
storage/config surface may still evolve. See the Next section in
docs/PLAN.md for the roadmap.
MIT © 2026 Barat Semet




