A production-grade replacement for smartlist and Mailman 2 in a procmail-based MTA environment. Ships as five self-contained SBCL binaries (no daemon, no interpreter at runtime). State is plain S-expression files.
Not sure if this replaces your setup? — If you run
procmailas your MDA and currently use smartlist, ezmlm, Mailman 2, or hand-rolledprocmailrcrecipes to manage mailing lists, mlisp replaces all of that. If you run Mailman 3, Sympa, or LISTSERV on a full SMTP stack, mlisp is not a drop-in — it targets the same MTA-pipe niche as smartlist, not the web-admin niche.
v0.8.0 · 381 BATS integration tests · 78 FiveAM unit tests · Actively developed
v0.1 Core delivery, subscribe/unsubscribe, troff templates
v0.2 Compliance (CAN-SPAM, GDPR, CASL, LGPD), audit log
v0.3 MIME, BCC delivery, RFC 2369 headers, bounce management,
Prometheus metrics, Maildir, mlisp-distrib binary
v0.4 SHA-256 contacts, GPG require-signed, procmail integration,
DMARC rewrite, VERP,
LDIF export, double opt-in, mass subscribe, bounce thresholds
v0.5 9 subgroups, attachment policy, subject filtering, message
numbering, rate limiting, embargo, DKIM strip, RFC 8058,
complete manpages, Quicklisp dist
v0.6 Subscriber self-service (info/who/query/set), BITNET-style
archive search, AllFix file distribution, plugin filter pipeline,
Emacs mlisp.el minor mode
v0.7 mlisp-bugs: Debbugs-compatible email bug tracker
v0.8 mlisp-procmail-gen (s-expr DSL for procmailrc), neural.sh
digest-summarization integration, multi-platform release packages
(Linux tarball + pkgsrc, NetBSD pkgsrc, FreeBSD pkg),
automated GitHub Releases, XDG-compliant zero-config init
| Binary | Purpose |
|---|---|
mlisp |
MTA-pipe delivery: distributes mail, handles subscriber commands and bounces |
mlisp-admin |
Operator tool: list management, moderation, export, diagnostics |
mlisp-bugs |
Debbugs-compatible email bug tracker (submit/close/control/append) |
mlisp-distrib |
AllFix-compatible file distribution |
mlisp-procmail-gen |
Compiles s-expr recipe files to procmailrc fragments |
# 1. Build (requires SBCL + Quicklisp; groff for manpages; bats-core to test)
make build-all
# 2. Install
sudo make install PREFIX=/usr/local
# 3. Init (zero-config: writes to ~/.config/mlisp/ by default)
mlisp-admin init
# 4. Create a namespace (one command creates all subgroup lists)
mlisp-admin add-namespace myproject myproject@lists.example.com
# 5. Generate procmailrc recipes
mlisp-procmail-gen --output ~/.procmailrc etc/example-recipes.lisp
# -- or -- for each list individually:
mlisp-admin install-procmail
# 6. Diagnose
mlisp-admin diagnose myproject-discussMLISP_HOME is optional. mlisp-admin init resolves the config directory
in priority order: --home flag > $MLISP_HOME > $XDG_CONFIG_HOME/mlisp/
~/.config/mlisp/>/etc/mlisp/(system-wide fallback for service accounts with no resolvable home directory).
procmail → mlisp <list-id> # delivery: stdin → subscribers
→ mlisp --mode request <list> # subscriber self-service commands
→ mlisp --mode bounce <list> # DSN bounce processing
→ mlisp-bugs --mode submit <pkg> # bug report submission
→ mlisp-bugs --mode control <pkg> # bug state changes
mlisp-admin <subcommand> # operator: config, moderation, export
mlisp-distrib <list-id> <file> # file distribution (AllFix-compatible)
mlisp-procmail-gen <recipe.lisp> # procmailrc DSL compiler
All state lives under $MLISP_HOME (default: ~/.config/mlisp/),
except per-list/per-package Maildir archives (see below):
state/state.sexp list configs + subscriber database
state/audit.sexp append-only event log
state/held/ moderation queue
state/pending/ double opt-in confirmation tokens
state/maildir/ per-list/per-package archive fallback when
$MAILDIR is unset (search/index/get, mlisp-bugs)
state/distrib/ file distribution archives
templates/ welcome / goodbye / help / footer templates
etc/filters/ example pre/post filter scripts
etc/unsubscribe-cgi/ RFC 8058 one-click CGI example
mlisp's internal per-list archive (used by the search/index/get
subscriber commands and by mlisp-bugs) follows the POSIX/
freedesktop.org $MAILDIR convention also honored by smartlist,
procmail, debbugs, and notmuch:
$MAILDIR set: $MAILDIR/lists/<list-id>/ e.g. $MAILDIR/lists/mlisp-discuss/
$MAILDIR unset: $MLISP_HOME/state/maildir/<list-id>/
Set $MAILDIR to point mlisp's archive at an existing mail spool
shared with other tools (notmuch indexing, mutt, debbugs). With no
$MAILDIR, mlisp works zero-config under $MLISP_HOME.
$MAIL (the mbox-format equivalent) is not used: mlisp only writes
Maildir-format archives.
This is separate from the per-list set-option <list> maildir-path <path> mechanism, which writes an additional explicit-path copy for
external indexing regardless of $MAILDIR.
myproject-discuss subscriber-writable general discussion
myproject-announce owner-post-only notifications
myproject-devel patches, VCS, development traffic
myproject-distrib file/binary distribution (AllFix)
myproject-request admin commands (subscribe/unsubscribe/help/search/…)
myproject-owner off-list owner contact
myproject-security security disclosures (GPG + embargo support)
myproject-commits automated CI/VCS notifications (bot-post-only)
myproject-users end-user support
One mlisp-admin add-namespace myproject myproject@lists.example.com
creates all nine lists. Use --subgroups to create a subset.
Write recipes as s-expressions:
(:recipe :marker "mlisp: myproject-discuss"
:guards ("!^FROM_DAEMON" "!^FROM_MAILER"
"!^Precedence: (bulk|junk|list)")
:match "^^TO_myproject-discuss@lists.example.com"
:pipe "/usr/local/bin/mlisp --home /etc/mlisp myproject-discuss")mlisp-procmail-gen --output ~/.procmailrc recipes.lisp # idempotent append
mlisp-procmail-gen --dry-run recipes.lisp # preview onlymlisp-admin bugs-add-package myproject bugs-submit@lists.example.com
mlisp-admin install-bugs-procmail myproject
# Operator tools
mlisp-admin bugs-list myproject --open --severity critical
mlisp-admin bugs-report myproject
mlisp-admin bugs-show myproject 42Wire format: <pkg>-bugs-submit@, <pkg>-bugs-N@, <pkg>-bugs-N-done@,
<pkg>-bugs-control@. Same as Debbugs.
# Exit codes: 0=pass 1=reject 2=hold 3=discard
mlisp-admin set-option mylist pre-filter /path/to/filter
mlisp-admin set-option mylist post-filter /path/to/filter
# Space-separated chain: first non-zero stops processing
mlisp-admin set-option mylist pre-filter "/etc/mlisp/filters/spam /etc/mlisp/filters/virus"Bundled examples in etc/filters/: SpamAssassin, ClamAV, Gemini
archive, and neural-moderate (#100 use case 3: AI moderation
annotation via neural.sh, advisory X-Mlisp-AI-Triage header, exits
0 -- combine with set-option <list> non-member-action hold for an
actual hold decision).
neural is the vendored vendor/neural.sh submodule's own build
output: a self-contained shell script (bash + curl + jq + jo at
runtime). make build-all runs make -C vendor/neural.sh build and
copies the result to bin/neural alongside the mlisp binaries;
make install installs it to PREFIX/bin/.
# First-time setup: install build/runtime deps (curl, jq, jo, m4 --
# m4 is vendor/neural.sh's own build dependency, used internally)
make deps
# Build everything including neural
make build-all
# Summarize a bug report
OPENAI_API_KEY=sk-... mlisp-admin bugs-report myproject \
--summarize etc/filters/neural-summarizeThe model/endpoint are whatever vendor/neural.sh was built with --
its default is OpenAI text-davinci-003, requiring OPENAI_API_KEY.
See vendor/neural.sh's own docs to change the model/endpoint.
dmarc-rewrite never|auto|always
verp true|false
confirm-subscribe true|false
message-numbering true|false
max-posts-per-day N
attachment-policy allow|strip|reject
subject-allow "pattern"
subject-deny "pattern"
unsubscribe-url https://... (RFC 8058 one-click)
archive-url https://... (List-Archive header)
search-enabled true|false
advertised true|false
maildir-path /path/to/dir (explicit external archive copy for
notmuch/mutt; independent of $MAILDIR -- see
"Maildir archive location" below)
pre-filter /path/to/filter
post-filter /path/to/filter
subscribe [subgroup] info who [list]
unsubscribe query [list] set <list> mail|nomail|digest
help search <kw> index [list]
nomail / resume get <list> N files [list]
confirm <token> diagnose
From smartlist: mlisp-admin add-sub-batch mylist subscribers-file
From Mailman 2:
list_members -o members.txt mylist
mlisp-admin add-sub-batch myproject-discuss members.txtTests are split by role:
- FiveAM (
make test-unit) — primary BDD/unit tests for core library logic (src/*.lisp): message parsing, state machine, compliance rules, MIME handling. 78 specs. These are the source-of-truth for behavioral correctness. - BATS (
make test-bats) — integration tests that exercise the compiled binaries end-to-end via subprocess calls. 381 specs across 19 suites. Catches binary/CLI-level regressions.
make test-unit # FiveAM: 78 unit specs
make test-bats # BATS: 381 integration specs across 19 suitesman mlisp delivery pipeline reference
man mlisp-admin all admin subcommands + set-option keys
man mlisp-intro tutorial + migration from smartlist/Mailman/LISTSERV
man mlisp-distrib file distribution
A narrative HTML overview (entry point + links to the manpages above)
is built from docs/index.lisp via the separate mlisp-docs ASDF
system (kept out of mlisp.asd's dependencies -- see "Requirements"):
sbcl --eval '(ql:quickload :40ants-doc-full)' \
--eval '(asdf:load-system :mlisp-docs)' \
--eval '(docs-builder:build :mlisp-docs)'CI builds and publishes this on every push to develop/main.
- SBCL 2.x with Quicklisp
- groff (for template rendering)
- bats-core (for the integration test suite)
- fiveam via Quicklisp (for the unit test suite)
- POSIX sendmail(8) or compatible (Postfix, Exim, OpenSMTPD)
- procmail (for MTA integration)
- m4, jo, jq, curl (build-time, for
vendor/neural.sh; bash + curl + jq + jo at runtime for theneuralbinary)
Pre-built binaries for each GitHub Release:
| Package | Format |
|---|---|
mlisp-<ver>-linux-x86_64.tar.gz |
Generic Linux tarball |
mlisp-<ver>-linux-x86_64.tgz |
pkgsrc binary package |
mlisp-<ver>-netbsd-x86_64.tgz |
NetBSD pkgsrc binary package |
mlisp-<ver>-freebsd-x86_64.pkg |
FreeBSD native package |
BSD-2-Clause — Da Planet Security / Dwight Spencer