mlmmj 2.2.2 CLI 命令详解
mlmmj 2.2.2 的 mlmmj-* 命令族详解:9 命令角色与邮件流定位、全路径强制 4/9 实测、make-ml/sub/unsub/list/bounce/maintd/receive/process/send 逐命令选项与回显、+command 邮件命令地址族、版本自检陷阱与脚本配方。
mlmmj 2.2.2 CLI 命令详解
目标:把 mlmmj 2.2.2 的 9 个
mlmmj-*命令一次讲清——每个命令的角色、选项全集、真实回显与易踩的坑。全部命令在 Debian 12 (bookworm) + mlmmj 2.2.2(源码编译)+ Postfix 3.7.11 环境实测(测试日期 2026-10-26),回显逐行照录。
一、环境信息
- 系统:Debian 12 (bookworm) x86_64
- mlmmj:2.2.2 源码编译,二进制位于
/usr/local/bin/,man 页位于/usr/local/man/man1/(安装过程见本专栏安装篇) - MTA:Postfix 3.7.11,
recipient_delimiter = +,别名管道已对接列表mlmmj-test - 实验列表:
mlmmj-test@lists.example.com,列表目录/var/spool/mlmmj/mlmmj-test/,属主nobody:nogroup(uid/gid 65534) - 测试角色:
alice@example.com/bob@example.com(订阅者),carol@example.com(订阅/退订实验),dave@example.com(帮助信实验) - 选项基准:随源码包附带的 9 页 man 文档逐一核对,凡与实测出入处均以实测为准(见 3.2 与第 11 节)
二、命令族总览
mlmmj 没有守护进程式的"控制台",管理动作全部由 9 个独立小命令完成。按"谁调用它"分三类:
| 命令 | 角色 | 调用者 |
|---|---|---|
mlmmj-make-ml | 建列表(生成目录树 + 别名条目) | 管理员手工/脚本 |
mlmmj-sub | 订阅 | 管理员/脚本 |
mlmmj-unsub | 退订 | 管理员/脚本 |
mlmmj-list | 查询名单/计数/owner | 管理员/监控脚本 |
mlmmj-bounce | 登记退信 | MTA 侧脚本/手工核对 |
mlmmj-maintd | 维护巡检(退信处理、摘要、队列) | cron 或 systemd |
mlmmj-receive | 收信入口(落件 incoming/) | MTA 别名管道 |
mlmmj-process | 邮件处理(准入/改写/入档) | mlmmj-receive 自动拉起 |
mlmmj-send | 出站分发(经 relayhost) | mlmmj-process 自动调用 |
在邮件流里的位置:
投稿人 → MTA —别名管道→ mlmmj-receive → incoming/ → mlmmj-process ⇄ mlmmj-send → relayhost → 订阅者
(落件) (按 control/ 处理) (VERP 信封)
mlmmj-maintd —旁路巡检→ unsub_bouncers / probe_bouncers / run_digests / clean_* / resend_queue …
管理员 —直接调用→ make-ml / sub / unsub / list / bounce
三、全路径强制规则(9 命令实测)
3.1 规则与实测
部分命令在程序入口就检查 argv[0],拒绝短名调用。对 9 个命令逐一实测,结论如下:
| 强制全路径(4 个) | 短名可用(5 个) |
|---|---|
mlmmj-sub、mlmmj-maintd、mlmmj-receive、mlmmj-process | mlmmj-make-ml、mlmmj-list、mlmmj-unsub、mlmmj-send、mlmmj-bounce |
被拒时的完整回显(以 mlmmj-sub 为例,逐行照录):
$ mlmmj-sub -L /var/spool/mlmmj/mlmmj-test/ -a x@example.com
mlmmj-sub: All mlmmj binaries have to be invoked with full path,
e.g. /usr/local/bin/mlmmj-sub
$ echo $?
1
对照组——不在强制名单里的 mlmmj-list 用短名一切正常:
$ mlmmj-list -L /var/spool/mlmmj/mlmmj-test/
bob@example.com
alice@example.com
carol@example.com
$ echo $?
0
man 页没有记载这条规则的命令清单,网上旧资料常只说"sub 要全路径"——2.2.2 的实际范围是四个命令。写脚本时最稳妥的策略是:一律全路径,不必记忆白名单。
3.2 man 页与实测的一处出入
man mlmmj-make-ml 的 SYNOPSIS 没有列出 -d 参数,但 -h 帮助与实际行为都支持它(用于指定列表域名,见第 5 节实测)。本文档以 -h 输出为准——这也提醒读者:mlmmj 的 man 页个别地方落后于代码,-h 永远是最新的。
四、建列表:mlmmj-make-ml
官方唯一的建列表入口,自动生成 13 个子目录(incoming/queue/archive/text/control/subscribers.d 等)与 control 基础文件。
-h 帮助全文(2.2.2 实测,逐行照录):
mlmmj-make-ml 0.2
/usr/local/bin/mlmmj-make-ml <options>
options:
-L listname: the name of the mailing list
-a: create the needed entries in your /etc/aliases file
-b: add needed entry to /etc/crontab
-c user: user to chown the spool directory to
-d fqdn: fully qualified domain name of the mailing list
-f answerfile: use answerfile to create mailing list non-interactively
-h: display this help text
-n answerfile: Do nothing. Instead, create answer file for later non-interactive use with -f
-s spooldir: mlmmj spool directory
-t lang: list text directory relative to /usr/local/share/mlmmj/text.skel for given language
选项精要:
-a:把别名管道条目直接追加进/etc/aliases(免手工抄写;追加后记得newaliases生效)。-c user:建完直接 chown,一步对齐 MTA 管道执行身份(Postfix 默认 nobody)。-f answerfile+-n:官方非交互路径。-n先生成"答案文件",-f回放它——批量建列表、基础设施即代码场景用这对组合,比< /dev/null更可审计。-t lang:按语言铺 listtexts 模板(相对/usr/local/share/mlmmj/text.skel/)。- 不带参数运行会进入交互问答模式;脚本里要么给全参数重定向
/dev/null,要么用-f。
五、订阅与退订:mlmmj-sub / mlmmj-unsub
5.1 mlmmj-sub 实测
$ /usr/local/bin/mlmmj-sub -L /var/spool/mlmmj/mlmmj-test/ -a bob@example.com
Changing to uid 65534 gid 65534, owner of /var/spool/mlmmj/mlmmj-test/.
$ echo $?
0
Changing to uid 65534 … 是正常输出而非警告:命令自动降权为列表目录属主(nobody)再写 subscribers.d/,保证写权限与 MTA 管道写入身份一致。不想降权时加 -U。
选项全集(man 页核对):
| 选项 | 作用 |
|---|---|
-L path | 列表目录全路径(必填) |
-a email / -a - | 订阅指定地址;- 表示从标准输入逐行读地址 |
-m str | 审核串(配合订阅审核流程) |
-c / -C | 发欢迎信 / 发起邮件确认流程 |
-d / -n | 订阅摘要版 / nomail 版 |
-f | 强制订阅(绕过 closedlist 等限制) |
-r / -R | 把本次操作当作"邮件请求 / 邮件确认"来处理 |
-s | 已订阅时不重复发信 |
-q / -U / -V | 安静模式 / 不降权 / 打印版本 |
5.2 mlmmj-unsub 实测
$ /usr/local/bin/mlmmj-unsub -L /var/spool/mlmmj/mlmmj-test/ -a carol@example.com
Changing to uid 65534 gid 65534, owner of /var/spool/mlmmj/mlmmj-test/.
$ echo $?
0
$ /usr/local/bin/mlmmj-list -L /var/spool/mlmmj/mlmmj-test/
bob@example.com
alice@example.com
选项与 sub 大体对称,差异项:-N 退订普通版(普通版即默认名单)、-c 发告别信、-s 未订阅时也不发信。注意 CLI 退订不受 closedlist 影响——closedlist 关的是"邮件命令退订"这条通道,管理员从命令行操作始终可行。
六、名单查询:mlmmj-list
$ /usr/local/bin/mlmmj-list -L /var/spool/mlmmj/mlmmj-test/
bob@example.com
alice@example.com
$ /usr/local/bin/mlmmj-list -L /var/spool/mlmmj/mlmmj-test/ -c
2
$ /usr/local/bin/mlmmj-list -L /var/spool/mlmmj/mlmmj-test/ -o
postmaster@lists.example.com
$ /usr/local/bin/mlmmj-list -L /var/spool/mlmmj/mlmmj-test/ -V
mlmmj-list version 2.2.1
| 选项 | 作用 |
|---|---|
(默认/-s) | 普通订阅名单 |
-c | 只输出人数(监控脚本取数最方便) |
-d / -n | 摘要版名单 / nomail 版名单 |
-m / -o | moderator 名单 / owner 名单 |
-V | 版本号 |
版本自检陷阱:源码标签是 2.2.2,
-V却自报 2.2.1——源码包内mk/defs.mk的VERSION变量未随 tag 更新(安装篇有取证)。判断真实版本以 tarball 的 tag 为准,不要被-V迷惑。
七、退信登记:mlmmj-bounce
正常情况下退信由 MTA 投递到 列表名+bounces-N-用户=域@ 的 VERP 地址、自动进入登记;mlmmj-bounce 用于手工把"某存档邮件对某地址投递失败"补进登记——典型场景是外部监控发现 SMTP 层拒信、或把历史投递故障补录。
实测(给 bob 登记存档 #3 的退信):
$ /usr/local/bin/mlmmj-bounce -L /var/spool/mlmmj/mlmmj-test/ -a bob=example.com -n 3
$ echo $?
0
$ cat /var/spool/mlmmj/mlmmj-test/bounce/bob=example.com
3:1793006259 # Mon Oct 26 09:17:39 2026
要点:
- 地址形态是
bob=example.com(VERP 编码,=代@),与 bounce/ 目录下的文件名一致。 - 登记文件格式为
存档编号:时间戳 # 人类可读时间,一文件一地址。 - 登记由
mlmmj-maintd消费:probe_bouncers(发探测信)与unsub_bouncers(连续退信超bouncelife(默认 5 天)即除名)两个任务接力。 -d(从 DSN 邮件里解析地址)与-p(立即发探测信)用于自动化接线;-n与-p二选一。
八、维护巡检:mlmmj-maintd
mlmmj 唯一的常驻型组件(但不建议常驻——官方推荐 cron 周期运行)。-F 表示"跑一轮就退出":
$ /usr/local/bin/mlmmj-maintd -F -L /var/spool/mlmmj/mlmmj-test/
$ echo $?
0
$ cat /var/spool/mlmmj/mlmmj-test/mlmmj-maintd.lastrun.log
Starting maintenance run at Mon Oct 26 09:17:39 2026
clean_moderation
clean_discarded
clean_subconf
clean_unsubconf
resend_queue
resend_requeue
clean_nolongerbouncing
unsub_bouncers
probe_bouncers
run_digests
十项任务逐行照录如上,语义对应:清过期审核件与待确认件、重发队列滞留件、清理与除名退信者、发探测信、汇总摘要。每次运行把任务清单记进列表目录的 mlmmj-maintd.lastrun.log——巡检是否在跑、跑了什么,看这一个文件即可。
两种托管方式(安装篇均实测):
cron(官方 README 推荐):
0 */2 * * * /usr/local/bin/mlmmj-maintd -F -d /var/spool/mlmmj
systemd timer/service:
ExecStart=/usr/local/bin/mlmmj-maintd -d /var/spool/mlmmj
-d spooldir 巡检目录下全部列表,-L listdir 只盯一个;多列表部署一律 -d。维护进程应以 root 启动(它会自行降权为各列表目录属主)。
九、收信与处理管线:mlmmj-receive / mlmmj-process
这两个命令是邮件流的中枢,也是四个"强制全路径"里的两个。
9.1 mlmmj-receive:别名管道入口
别名(/etc/aliases)里那行管道就是它:
mlmmj-test: "|/usr/local/bin/mlmmj-receive -L /var/spool/mlmmj/mlmmj-test/"
MTA 每收到一封列表信就执行一次 receive:信件写入列表的 incoming/,随后默认自动拉起 process 处理。选项:-F 不 fork 后台(调试用)、-P 不拉起 process(只落件,配合手工处理排障)、-L 列表目录。端到端实测(含收发双段日志与订阅者落信)见安装篇与部署篇。
9.2 mlmmj-process:单封信处理器
mlmmj-process -L /path/to/list -m /path/to/mail
-m 指明待处理信件文件(通常就是 incoming/ 里的落件),处理动作依序为:control 准入检查(tocc/subonlypost/access/maxmailsize…)→ 头部改写(delheaders/customheaders/prefix)→ footer 追加 → 入档 → 生成收件人清单交给 mlmmj-send。-P 让它不调用 send(只完成处理与入档,出站另走)。手工排障时先 -P 隔离出站,确认 archive/ 与 queue/ 的中间产物,再放开验证 send 段。
9.3 mlmmj-send:出站分发
一般不手工调用。它是 process 的下游:从列表读订阅名单,按 VERP 规则生成信封,投给 control/relayhost(默认 127.0.0.1:25)。选项全集备用:-a 不入档、-D 发完不删信、-F/-R/-T 定制 MAIL FROM/Return-Path/RCPT TO、-o 指定剔除的地址、-r 临时指定 relayhost、-s 指定订阅者文件、-l 传列表控制变量、-m 信件文件。
十、邮件命令地址族(+command 接口)
除 CLI 外,mlmmj 还开放"发邮件即操作"的命令地址——Postfix 的 recipient_delimiter = + 把 mlmmj-test+subscribe@ 的扩展部分转交列表处理。本专栏实测涉及的全家福:
| 命令地址 | 作用 | 验证出处 |
|---|---|---|
列表名+subscribe@ | 邮件订阅 | 配置篇 8.1/8.3 实测 |
列表名+unsubscribe@ | 邮件退订 | help 回信文案 |
列表名+help@ | 取回帮助信 | 本篇 10.1 实测 |
列表名+get-N@ | 取回存档第 N 封 | help 回信文案 |
列表名+owner@ | 写给列表主人(owner 文件收) | 配置篇 8.2 通知实测 |
列表名+list@ | 向 owner 邮寄订阅者名单 | TUNABLES(nolistsubsemail 可关) |
列表名+conf-<哈希>@ | 订阅确认回复 | 确认信流程 |
列表名+subrelease-<哈希>@(及 +digestsubrelease- / +nomailsubrelease-) | 拒信自选项:订阅并释放被拒投稿 | 配置篇第六节拒信实测 |
列表名+bounces-N-用户=域@ | VERP 退信回程地址 | 各篇投递实测 |
10.1 +help 实测:文案随列表状态变化
$ printf "From: dave@example.com\nTo: mlmmj-test+help@lists.example.com\nSubject: help\n\n" | sendmail -f dave@example.com mlmmj-test+help@lists.example.com
$ grep "^Subject" /var/mail/dave | tail -1
Subject: =?utf-8?q?Information_for_mlmmj-test=40lists.example.com?=
回信正文(节选,逐行照录):
Hi, this is the Mlmmj program managing the <mlmmj-test@lists.example.com>
mailing list.
Here is some information about the list.
You can subscribe to the following versions:
- The normal version: Every time a post is sent to the list, subscribers
receive a copy of it. Subscribe by contacting a list administrator.
…
Unsubscribe by emailing <mlmmj-test+unsubscribe@lists.example.com>.
Posts are made by emailing <mlmmj-test@lists.example.com>.
However, only subscribers may post to the list.
注意两处措辞——"Subscribe by contacting a list administrator"与"only subscribers may post":当时实验列表上正开着 closedlistsub 与 subonlypost(配置篇实验遗留)。help 文案不是死模板,而是按 control 当前状态动态生成的。排障时若用户转来的帮助信与预期不符,先核对 control 目录再怀疑程序。
十一、脚本配方:建列表一条龙
把本章命令串成生产可用的最小脚本(与安装篇的集成步骤一致):
#!/bin/bash
# newlist.sh <列表名> <列表域> —— 建列表 + 属主对齐 + 别名生效 + 纳入维护
set -e
LIST="$1"; DOMAIN="$2"; SPOOL=/var/spool/mlmmj
/usr/local/bin/mlmmj-make-ml -L "$LIST" -d "$DOMAIN" -c nobody -a -s "$SPOOL" < /dev/null
chown -R nobody:nogroup "$SPOOL/$LIST"
newaliases
if ! crontab -l 2>/dev/null | grep -q mlmmj-maintd; then
( crontab -l 2>/dev/null; echo "0 */2 * * * /usr/local/bin/mlmmj-maintd -F -d $SPOOL" ) | crontab -
fi
echo "$LIST@$DOMAIN 已就绪(目录:$SPOOL/$LIST)"
要点回顾:-c nobody + chown 双保险对齐管道执行身份;newaliases 让别名即时生效;cron 幂等注入(有 mlmmj-maintd 就不加)。
十二、FAQ
Q1:为什么有的命令短名能跑、有的不能? 2.2.2 实测规则:sub/maintd/receive/process 四个强制全路径(3.1 节回显),其余五个随意。man 页未写明缘由与清单,脚本一律全路径最省心。
Q2:-V 显示的版本和实际不符?
mk/defs.mk 未随 2.2.2 tag 更新,二进制自报 2.2.1。以源码包 tag 为准(第六节实测复现)。
Q3:手工补跑某一封信怎么操作?
mlmmj-process -L <列表> -m <信件文件>;先加 -P 隔离出站观察中间产物,验证无误再去掉 -P 全流程重放。排查"信到底卡在哪"时这招最有效。
Q4:mlmmj-sub 成功但订阅者收不到欢迎信?
欢迎信不是 sub 直接发的——出站统一走 process→send→relayhost 管线,且需要 -c 显式要求;另核对 relayhost 连通性与列表 owner 配置。
Q5:closedlist 把邮件退订也关了,用户找上门怎么办?
管理员用 mlmmj-unsub 从命令行除名(不受 closedlist 影响),或临时移除 control 文件走邮件流程。closedlist 的"关"只作用于邮件命令通道(配置篇 8.3 的静默语义同样适用于退订方向)。
Q6:bounce 文件越攒越多正常吗?
正常。它们是 maintd 探测与除名的输入;地址恢复收信后由 clean_nolongerbouncing 任务自动清理(第八节任务清单原文),无需人工干预。
十三、参考链接
- man 手册:
mlmmj-list(1)、mlmmj-sub(1)、mlmmj-unsub(1)、mlmmj-make-ml(1)、mlmmj-bounce(1)、mlmmj-maintd(1)、mlmmj-receive(1)、mlmmj-process(1)、mlmmj-send(1)(源码包man/目录同名 9 页) - 官方仓库(Codeberg):https://codeberg.org/mlmmj/mlmmj ;官网 https://mlmmj.org
- 本专栏系列:mlmmj 2.2.2 介绍与选型指南(架构与邮件流总览)、Debian 12 安装 mlmmj 2.2.2 指南(安装与 Postfix 集成)、mlmmj 2.2.2 部署指南(服务编排部署)、mlmmj 2.2.2 配置参数详解(本文命令所读写的 control 文件族)