mlmmj 2.2.2 配置参数详解
mlmmj 2.2.2 配置参数详解:control 文件族 56 项全景(四型参数与默认值速查)、prefix/footer/customheaders、subonlypost 自释放、tocc 拦截与放行、nosubconfirm/notifysub/closedlistsub、maxmailsize 七组实测与 listtexts 模板定制。
mlmmj 2.2.2 配置参数详解
目标:讲透 mlmmj 2.2.2 的配置体系——control 文件族全部 56 项的可调参数(类型、语义、默认值),配合七组真实投递实验验证关键开关的实际行为。全文命令与回显均在 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/;安装过程见本专栏安装篇) - MTA:Postfix 3.7.11,
recipient_delimiter = + - 实验列表:
mlmmj-test@lists.example.com,列表目录/var/spool/mlmmj/mlmmj-test/,属主nobody:nogroup - 测试角色:
alice@example.com/bob@example.com(在册订阅者)、carol@example.com(实验中途加入)、dave@example.com(仅用于拒绝实验) - 配置素材基准:随 2.2.2 源码包附带的
TUNABLES文档(本篇 56 项清单以此为基准逐一核对)
二、配置体系总览
mlmmj 的配置思路与多数软件相反:没有中心配置文件、没有配置数据库、没有需要重载的守护进程。一个列表的全部行为都由它的目录树决定,其中可调参数集中在 listdir/control/ 子目录——每个参数就是一个文件。
/var/spool/mlmmj/mlmmj-test/
├── control/ ← 本篇主角:56 个可调参数(文件即配置)
│ ├── listaddress # 列表地址(建列表时生成)
│ ├── prefix # Subject 前缀(管理员手工创建)
│ └── …
├── text/ # listtexts 邮件文本模板(17 项,见第十节)
├── subscribers.d/ # 订阅者名单(一人一文件)
├── archive/ # 存档(顺序编号)
├── incoming/ queue/ … # 队列与工作目录
└── mlmmj-maintd.lastrun.log # 维护进程巡检记录
2.1 四种参数类型
control 文件的类型由值的形态决定,TUNABLES 原文对每项都标注了类型:
| 类型 | 语义 | 建法示例 |
|---|---|---|
| boolean | 文件存在即为真,内容无关 | touch control/subonlypost |
| normal | 第一行作为值,第二行起可写注释 | echo 1024 > control/maxmailsize |
| list | 每行一个条目(多行列表) | printf "a@x.com\nb@y.com\n" > control/owner |
| text | 整个文件内容作为值 | vi control/footer |
2.2 生效时机:写完即生效
mlmmj-process 处理每一封邮件时都重新读取 control 文件——没有缓存、没有 reload 动作。本篇实验全程靠「touch 一个文件 → 下一封信行为改变」验证,未重启任何服务。这个特性让 control 文件族特别适合脚本化管理(git 管理、模板下发、批量巡检都天然可行)。
三、控制文件全景(56 项)
以下按功能域分组列出全部 56 项。类型标注沿用 TUNABLES 原文;「默认」列给出官方文档记载的默认值。
3.1 订阅与名单(9 项)
| 控制文件 | 类型 | 作用与默认 |
|---|---|---|
listaddress | list | 列表地址清单,第一行为主地址(发信时用它)。建列表时自动生成 |
closedlist | boolean | 全关:邮件订阅与退订都禁用 |
closedlistsub | boolean | 只关订阅、允许退订 |
nosubconfirm | boolean | 免确认订阅。官方原文警告"HANDLE WITH CARE",只适合内部可信网络(实测见 7.1) |
submod | list | 订阅审核:文件存在即开启;有地址则审核通知发给这些地址,否则发给 owner |
notifysub | boolean | 有人订阅/退订时通知 owner(实测见 7.2) |
owner | list | 列表主人地址,收 列表名+owner@ 的信与各类管理通知。建列表时默认写 postmaster@ |
autosubscribe | boolean | 审核型列表中,投稿被管理员放行后,发件人自动以 nomail 身份入列 |
nolistsubsemail | boolean | 关闭 列表名+list@(向 owner 邮寄全部订阅者名单)功能 |
3.2 投递准入(19 项)
| 控制文件 | 类型 | 作用与默认 |
|---|---|---|
subonlypost | boolean | 仅订阅者可发信(按 From 头核对)。无 moderator 时拒信附「订阅并释放」自选项;有 moderator 时仅拒(实测见第六节)。与 modonlypost 同存时本项优先 |
modonlypost | boolean | 仅 moderators 名单内的人可发信 |
modnonsubposts | boolean | 无资格者的投稿改为进审核队列而非直接拒。单独使用时隐含 subonlypost |
moderated | boolean | 开启投稿审核,审核者是 control/moderators 内的地址 |
subreleaseall | boolean | 自释放时一并释放该发件人所有待审邮件(适合订阅前一口气发来的补丁系列) |
modreqlife | normal | 待审邮件最长等待秒数,超期丢弃。默认 604800(7 天) |
notifymod | boolean | 投稿进入审核时按信封发件人回执通知 |
ifmodsendonlymodmoderate | boolean | 审核邮件只发给「正在处理的那位」moderator,避免打扰其他 moderator |
tocc | boolean | 放宽:列表地址不必出现在 To:/Cc: 头(默认必须出现,实测见第七节) |
access | list | 正则访问控制,逐头匹配、首条命中即止。语法见源码包 README.access |
send | list | 白名单:这些地址的来信无条件放行(绕过审核与 subonlypost) |
maxmailsize | normal | 单封邮件大小上限(字节)。实测见第八节 |
nomaxmailsizedenymails | boolean | 超限时不发拒信通知 |
mimedeny | list | MIME 类型黑名单(前缀匹配,含嵌套部分);命中时不拒信,而是打上 X-ThisMailContainsUnwantedMimeParts: Y 头。2.x 中 mlmmj-receive 原生处理 |
notoccdenymails | boolean | tocc 违规时不发拒信通知 |
noaccessdenymails | boolean | access 规则拒绝时不发拒信通知 |
nosubonlydenymails | boolean | subonlypost 拒绝时不发拒信通知 |
nomodonlydenymails | boolean | modonlypost 拒绝时不发拒信通知 |
nosubmodmails | boolean | 订阅被审核时不给申请人发通知 |
3.3 邮件内容改写(9 项)
| 控制文件 | 类型 | 作用与默认 |
|---|---|---|
prefix | normal | Subject 前缀(如 [研发组]),已带该前缀的信不会重复加(实测见第五节) |
footer | text | 附加到每封出站信末尾的落款(实测见第五节) |
customheaders | list | 每封出站信注入的自定义头;支持两个特殊变量:$posteraddr$(原发件人地址)、$disarmedfrom$(化名安全化处理后的 From 值) |
delheaders | list | 删除入站信中的指定头;匹配按前缀规则:写 X- 会连带删掉所有 X-Example 类头,写 X-: 只删精确的 X-。From 与 Return-Path: 无条件删除 |
addtohdr | boolean | 出站信加含收件人地址的 To: 头(建议先用 delheaders 删掉原 To:) |
replyto | boolean | 已废弃:改用 customheaders 加 Reply-To: 头;两者同用会产生两个 Reply-To 违反 RFC 5322,被多家大厂拒信 |
listidphrase | normal | List-Id 头里 <list-id> 前的可选短语(RFC 2919);非 ASCII 需按 RFC 2047 编码 |
nolistheaders | boolean | 关闭 RFC 2919/2369 自动头注入(List-Id/List-Post/List-Help/List-Subscribe/List-Unsubscribe/List-Owner/Precedence: list)。2.x 默认注入 |
notmetoo | boolean | 发件人也在订阅名单时,不给发件人本人抄送这封信 |
3.4 存档与历史取回(4 项)
| 控制文件 | 类型 | 作用与默认 |
|---|---|---|
noarchive | boolean | 不入档,处理完直接删除 |
archivepartition | boolean | 存档改用 archive/YYYY/MM/编号 层级布局(默认 archive/编号 平铺);存量平铺档案由 mlmmj-maintd 按文件修改时间自动迁移,读取不受布局影响 |
noget | boolean | 关闭 +get-N 取回历史邮件功能 |
subonlyget | boolean | +get-N 仅限订阅者使用(noget 优先于本项) |
3.5 摘要(digest)与免打扰(5 项)
| 控制文件 | 类型 | 作用与默认 |
|---|---|---|
digestinterval | normal | 距上期摘要多少秒后发下一期。默认 604800(7 天) |
digestmaxmails | normal | 累计多少封就提前触发摘要。默认 50 封 |
nodigesttext | boolean | 摘要邮件不带话题串简述的文本部分 |
nodigestsub | boolean | 拒绝摘要版订阅(并通知申请人) |
nonomailsub | boolean | 拒绝 nomail 版订阅(收信但不收列表信的挂名订阅) |
3.6 退信处理与 VERP(6 项)
| 控制文件 | 类型 | 作用与默认 |
|---|---|---|
bouncelife | normal | 地址连续退信多少秒后除名。默认 432000(5 天) |
verp | normal | VERP 策略:不建此文件=mlmmj 自己逐收件人做 VERP(默认行为,信封发件人形如 列表名+bounces-N-用户=域@);内容含 postfix=让 Postfix 用 XVERP 批量处理;空内容=彻底关闭 VERP |
maxverprecips | normal | 每封交给 SMTP 服务器的最大收件人数(配合 verp 分组)。默认 100 |
staticbounceaddr | normal | 固定退信地址(Return-Path:),等于关掉自动退信处理,交给外部系统 |
xforwardedto | boolean | 出站信注入 X-Forwarded-To: 头(利于 Gmail 等识别转发),与 VERP 互斥 |
dara | boolean | 注入 X-Signed-Recipient: 头(DARA/ARC 重放 resistant 认证),与 VERP 互斥 |
VERP 行为无需任何配置即默认开启——本专栏安装篇的端到端验证中,订阅者收到的信封发件人就是
mlmmj-test+bounces-3-bob=example.com@lists.example.com,收件人地址被编码进退信路径,退信可精确定位到人。
3.7 出站 SMTP 连接(4 项)
| 控制文件 | 类型 | 作用与默认 |
|---|---|---|
relayhost | normal | mlmmj 发信的中继主机,IP 或主机名。默认 127.0.0.1 |
smtpport | normal | 中继主机端口。默认 25 |
smtphelo | normal | SMTP EHLO/HELO 报的主机名。默认用机器名 |
delimiter | normal | 本列表的收件人分隔符。默认 +(官方明确提醒:用 - 大概率出问题,mlmmj 自己把 - 当内部小分隔符用) |
四、默认值速查表
改参数前先记住出厂值(全部来自 2.2.2 源码包 TUNABLES 原文):
| 参数 | 默认值 | 换算 |
|---|---|---|
modreqlife | 604800 秒 | 7 天后丢弃待审邮件 |
digestinterval | 604800 秒 | 7 天发一期摘要 |
digestmaxmails | 50 封 | 攒满 50 封提前触发摘要 |
bouncelife | 432000 秒 | 5 天连续退信即除名 |
maxverprecips | 100 | 每封最大收件人数 |
relayhost | 127.0.0.1 | 发信走本机 MTA |
smtpport | 25 | — |
delimiter | + | 与 Postfix recipient_delimiter 对齐 |
| RFC 2919/2369 头注入 | 开启 | 建 nolistheaders 才关闭 |
| 存档 | 平铺 archive/N | 建 archivepartition 才分层 |
五、实测一:prefix + footer + customheaders 三件套
创建三个内容改写文件并投一封信:
$ cd /var/spool/mlmmj/mlmmj-test/control
$ echo "[研发组]" > prefix
$ printf -- "----\nmlmmj 2.2.2 配置实测 · 研发测试列表\n" > footer
$ printf "Reply-To: mlmmj-test@lists.example.com\nX-Mailinglist: mlmmj-test\n" > customheaders
$ printf "From: carol@example.com\nTo: mlmmj-test@lists.example.com\nSubject: 配置参数验证一\n\n正在验证 prefix/footer/customheaders。\n" | sendmail -f carol@example.com mlmmj-test@lists.example.com
$ ls /var/spool/mlmmj/mlmmj-test/archive/
1 2 3 ← 信已入档(编号 3)
订阅者 bob 收到的关键邮件头与正文尾部:
From mlmmj-test+bounces-3-bob=example.com@lists.example.com Mon Oct 26 09:06:57 2026
Subject: [研发组] 配置参数验证一 ← prefix 生效
Reply-To: mlmmj-test@lists.example.com ← customheaders 生效
X-Mailinglist: mlmmj-test ← customheaders 生效
List-Id: <mlmmj-test.lists.example.com> ← 2.x 默认注入(无需配置)
正在验证 prefix/footer/customheaders。
---- ← footer 生效(自动追加)
mlmmj 2.2.2 配置实测 · 研发测试列表
三个文件一次投递全部生效,且 From 行的 mlmmj-test+bounces-3-bob=example.com@ 显示 VERP 信封同步工作——内容改写与信封改写互不干扰。
六、实测二:subonlypost 与「订阅并释放」自选项
$ touch control/subonlypost
$ printf "From: carol@example.com\nTo: mlmmj-test@lists.example.com\nSubject: 非订阅者投递测试\n\nsubonlypost 验证。\n" | sendmail -f carol@example.com mlmmj-test@lists.example.com
$ ls /var/spool/mlmmj/mlmmj-test/archive/
1 2 3 ← 存档未增长:carol 的信被拒,不入档
carol 收到的拒信(关键行照录):
From mlmmj-test+bounces-help@lists.example.com Mon Oct 26 09:07:49 2026
From: mlmmj-test+owner@lists.example.com
To: carol@example.com
Reply-To: mlmmj-test+subrelease-7dd481640e4f6ce8@lists.example.com
Hi, this is the Mlmmj program managing the <mlmmj-test@lists.example.com>
mailing list.
The message from <carol@example.com> with subject
"????????????????????????" was unable to be delivered to the list because
you are not a list subscriber.
However, this is an open list. If you wish, you can simultaneously
subscribe and release your post by sending a message to
<mlmmj-test+subrelease-7dd481640e4f6ce8@lists.example.com> which can
usually be done simply by replying to this message. The subject and the
body of the message can be anything.
Or you can simultaneously subscribe to the digest version of the list and
release your post by sending a message to
<mlmmj-test+digestsubrelease-7dd481640e4f6ce8@lists.example.com> …
Or you can simultaneously subscribe to the no-mail version of the list and
release your post by sending a message to
<mlmmj-test+nomailsubrelease-7dd481640e4f6ce8@lists.example.com> …
两个值得注意的行为细节:
- 拒信自带三通道自选项:普通订阅释放(
+subrelease-<哈希>)、摘要版释放(+digestsubrelease-)、nomail 版释放(+nomailsubrelease-)。哈希把「这一封待释放的信」绑定在回信地址里,直接回复即完成「订阅 + 放行」两件事。 - 中文主题在拒信里变成问号:原信主题「非订阅者投递测试」在拒信引用处显示为
"????????????????????????"——拒信对原主题的再编码丢失了非 ASCII 字符(英文主题不受影响)。这是 2.2.2 的实际行为,中文列表部署时要在 FAQ 里告诉用户「拒信主题里的问号就是你被拒的那封信」。
若列表配有 control/moderators,subonlypost 的拒信不会给自选项——改为简单拒绝(避免绕过审核);想给非订阅者机会就走 modnonsubposts 进审核队列。
七、实测三:tocc 的「默认拦截」与「开关放行」
默认行为:订阅者 bob 投信,但 To 写的是 alice(列表地址只在信封收件人里):
$ printf "From: bob@example.com\nTo: alice@example.com\nSubject: tocc 验证信\n\n列表地址不在 To/Cc。\n" | sendmail -f bob@example.com mlmmj-test@lists.example.com
$ ls /var/spool/mlmmj/mlmmj-test/archive/
1 2 3 ← 被拒,存档未增长
bob 收到拒信:
The message from <bob@example.com> with subject "tocc ?????????" was unable
to be delivered to the list because the list address was not found in
either the To: or CC: header.
建立 tocc 开关后同样写法再投:
$ touch control/tocc
$ printf "From: bob@example.com\nTo: alice@example.com\nSubject: tocc 放行验证\n\n列表地址不在 To/Cc,但已建 tocc。\n" | sendmail -f bob@example.com mlmmj-test@lists.example.com
$ ls /var/spool/mlmmj/mlmmj-test/archive/
1 2 3 4 ← 放行成功,入档编号 4
这个开关服务于一类真实场景:密送投递(把列表地址放 Bcc)以及从只允许单一 To: 的系统(部分工单网关)转投的自动化邮件。若既想放宽又不想让用户收到「地址不在 To/Cc」的提醒,配合 notoccdenymails。
八、实测四:订阅开关三连(nosubconfirm / notifysub / closedlistsub)
8.1 nosubconfirm:免确认直接入列
$ touch control/nosubconfirm notifysub
$ printf "From: carol@example.com\nTo: mlmmj-test+subscribe@lists.example.com\nSubject: subscribe me\n\n" | sendmail -f carol@example.com mlmmj-test+subscribe@lists.example.com
$ /usr/local/bin/mlmmj-list -L /var/spool/mlmmj/mlmmj-test/
bob@example.com
alice@example.com
carol@example.com ← 一步入列,无确认环节
$ ls -A /var/spool/mlmmj/mlmmj-test/subconf/
(空) ← 没有待确认项产生
对照:正常流程下这封 +subscribe 信只会生成待确认请求,carol 要回信到 +conf-<哈希> 才算订阅成功。nosubconfirm 把这一环整个跳过——官方文档明确警告只该在本地可信列表使用,公网列表开了它等于接受任意伪造发件人的订阅。
8.2 notifysub:owner 收到订阅通知
owner 文件默认指向 postmaster@lists.example.com(本机别名落地到 root 邮箱),通知原文关键行:
From mlmmj-test+bounces-help@lists.example.com Mon Oct 26 09:08:56 2026
Subject: =?utf-8?q?Subscribed_to_mlmmj-test=40lists.example.com:_carol=40example.com?=
From: mlmmj-test+owner@lists.example.com
To: mlmmj-test+owner@lists.example.com
即「Subscribed to mlmmj-test@lists.example.com: carol@example.com」。想收通知的活人,把真实地址写进 control/owner(每行一个)即可。
8.3 closedlistsub:订阅关闭是「静默拒绝」
$ touch control/closedlistsub
$ printf "From: dave@example.com\nTo: mlmmj-test+subscribe@lists.example.com\nSubject: subscribe me too\n\n" | sendmail -f dave@example.com mlmmj-test+subscribe@lists.example.com
$ /usr/local/bin/mlmmj-list -L /var/spool/mlmmj/mlmmj-test/
bob@example.com
alice@example.com
carol@example.com ← dave 未入列
$ ls /var/mail/dave
ls: cannot access '/var/mail/dave': No such file or directory ← 没有任何回信
$ ls -A /var/spool/mlmmj/mlmmj-test/subconf/
(空) ← 也没有待确认项
三个证据拼出完整结论:closedlistsub 下的订阅请求被静默丢弃——名单不变、无拒信、无待确认项。部署封闭列表时要主动在别处(网页、页脚)告知「如何找管理员人工加名单」,否则申请者面对的是纯沉默。
九、实测五:maxmailsize 大小限制
$ echo 1024 > control/maxmailsize
$ { printf "From: bob@example.com\nTo: mlmmj-test@lists.example.com\nSubject: maxmailsize 验证\n\n"; head -c 20480 /dev/zero | tr "\0" "A"; echo; } | sendmail -f bob@example.com mlmmj-test@lists.example.com
$ ls /var/spool/mlmmj/mlmmj-test/archive/ | tail -2
3
4 ← 存档未增长:超限信被拒
bob 收到拒信,原因措辞精确回显了配额:
The message from <bob@example.com> with subject "maxmailsize ??????" was
unable to be delivered to the list because it exceeded the maximum allowed
message size of 1024 bytes.
要点:maxmailsize 是字节单位的硬上限,检测在 process 阶段(邮件已进入列表管线);不设此文件则不限制大小。若不想让超限者收到通知(例如防地址探测),配 nomaxmailsizedenymails。
十、listtexts:邮件文本模板定制
自动化邮件(确认信、拒信、帮助文本、摘要外壳等)的文案来自列表目录下的 text/ 子目录,2.2.2 的 make-ml 一共铺了 17 个模板:
$ ls /var/spool/mlmmj/mlmmj-test/text/
confirm deny deny-post digest faq finish finish-sub gatekeep-sub
help list moderate-post notify …(共 17 个)
- 定制方式:直接编辑
text/下对应文件;官方建议保留一行%text占位符的写法见源码包README.listtexts(原文有逐模板说明)。 - 回滚:删掉自改文件、从
/usr/local/share/mlmmj/text.skel/拷回原始版即可。 - 实战提示:第六、八、九节拒信里的英文文案都出自这些模板;中文列表最值得定制的是
confirm(订阅确认)与deny-post(投稿被拒)两个——把第八节「closedlistsub 静默拒绝」的引导语写进自己的文案,比改代码体面得多。
十一、组合配方:三种常见列表形态
control 文件族的价值在组合。三个经过验证的组合起点(每一项都在本篇实测或全景表中有出处):
公告列表(单向广播)
$ touch control/closedlistsub control/subonlypost
$ printf "公告组维护员 <announce-admin@example.com>\n" > control/owner
订阅关闭、只有维护员能发信;订阅者只能收。
讨论列表(默认形态 + 守门)
$ echo "[dev-talk]" > control/prefix
$ touch control/subonlypost
$ printf "dev-talk@example.com\n" > control/customheaders
前缀辨身份、订阅者才能发言、回信默认回到列表(讨论组惯例)。
审核列表( moderated )
$ touch control/moderated control/notifymod
$ printf "mod-a@example.com\nmod-b@example.com\n" > control/moderators
所有投稿进 moderation/ 队列,moderator 收通知,通过方式见源码包 README(+approve/+reject 命令地址);modreqlife 控制滞留时长(默认 7 天)。
十二、FAQ 与陷阱清单
Q1:改了 control 文件需要重启什么吗?
不需要。mlmmj-process 每处理一封信都重新读取 control(2.2 节实测:touch 完下一封信立刻生效)。需要当心的是反向操作——删掉 boolean 文件即关闭对应开关,rm 就是关闭动作。
Q2:拒信主题里的中文怎么全是问号?
2.2.2 拒信在引用原主题时对非 ASCII 字符的再编码有缺陷,实测中文主题变 "????"(见第六节)。原信其他部分不受影响;在意观感就定制 listtexts,或推动上游修复。
Q3:replyto 还能用吗?
能创建但已废弃:与 customheaders 里的 Reply-To 同用会出现双 Reply-To(违反 RFC 5322,Gmail 等会拒收)。统一用 customheaders。
Q4:delheaders 删过头怎么办?
前缀匹配是双向刀:写 X- 会把所有 X 开头的头全部删掉。规则(TUNABLES 原文):带冒号结尾的 token(X-:)只匹配字面同名;不带冒号(X-)匹配所有前缀。另外 From (注意尾随空格)和 Return-Path: 无论写不写都会被删。
Q5:nosubconfirm 真的不能用吗? 公网列表不要用:伪造 From 的订阅信将直接入列。内部可信网段、CI 通知列表这类封闭环境用它换便利是合理的(8.1 节实测)。
Q6:为什么我的列表「看起来」拒了某人却毫无动静?
检查三个静默路径:closedlistsub(订阅静默丢弃,8.3 节实测)、各类 no*denymails 开关(拒绝时不发通知)、access 规则(首条命中即止,后面的放行规则轮不到)。逐个核对 control 目录即可定位。
Q7:VERP 能关吗?
能——建一个内容为空的 control/verp 文件即彻底关闭(mlmmj 与 SMTP 服务器都不做 VERP)。但关掉后退信无法定位到具体订阅者,自动退信处理(bouncelife)随之失效,一般不建议。
十三、参考链接
- 配置素材基准:2.2.2 源码包
TUNABLES、README.access、README.listtexts、README.postfix - 官方仓库(Codeberg):https://codeberg.org/mlmmj/mlmmj ;官网 https://mlmmj.org
- 本专栏系列:软件定位与架构总览见 mlmmj 2.2.2 介绍与选型指南;安装与 Postfix 集成见 Debian 12 安装 mlmmj 2.2.2 指南;服务编排部署路线见 mlmmj 2.2.2 部署指南;
mlmmj-*命令行工具族详解见本专栏命令篇