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邮件服务邮件列表Postfix2026/10/26

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-processmlmmj-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 / -omoderator 名单 / 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 任务自动清理(第八节任务清单原文),无需人工干预。

十三、参考链接