Apache Pony Mail 0.11-incubating 常用 CLI 命令与工具详解
Apache Pony Mail 0.11-incubating 常用 CLI 命令与工具详解:初始化/归档/导入/列表管理/诊断工具与 curl 验证 web 与 ES 的用法及真实回显,命令均基于实测
Apache Pony Mail 0.11-incubating 常用 CLI 命令与工具详解
目标:详解 Apache Pony Mail 0.11-incubating 随包附带的命令行工具——
setup.py(初始化)、archiver.py(归档入口)、import-mbox.py(历史导入)、list-lists.py/edit-list.py/copy-list.py(列表管理)、trace.py/elastic.py(调试诊断),以及用curl验证 web/ES 链路的常用命令。所有命令与回显均为真实环境实测。
一、环境信息
- 操作系统:Ubuntu 22.04.5 LTS(x86_64)
- Apache Pony Mail:0.11-incubating(工具目录
/opt/ponymail/tools) - Elasticsearch:2.4.6(
http://127.0.0.1:9200,索引ponymail) - python-elasticsearch:2.4.0
- 运行方式:全部工具在
/opt/ponymail/tools/目录下以python3 <工具>.py执行 - 示例账号:root(安装管理)、esuser(ES 进程账号,密码 q1w2e3r4)
二、工具概览
| 工具 | 位置 | 用途 |
|---|---|---|
setup.py | /opt/ponymail/tools/ | 初始化:连接 ES、建索引、生成 ponymail.cfg 与 config.lua |
archiver.py | /opt/ponymail/tools/ | 归档入口:从 STDIN 读原始邮件写入 ES |
import-mbox.py | /opt/ponymail/tools/ | 批量导入 mbox / maildir / pipermail / mod_mbox 历史数据 |
list-lists.py | /opt/ponymail/tools/ | 列出归档中的全部列表 |
edit-list.py | /opt/ponymail/tools/ | 列表批量编辑:改名、公私切换、删除、正文混淆 |
copy-list.py | /opt/ponymail/tools/ | 列表复制/迁移(可跨索引) |
trace.py | /opt/ponymail/tools/ | 调试追踪输出 |
elastic.py | /opt/ponymail/tools/ | ES 连通性与版本诊断 |
curl | 系统自带 | 验证 web API 与 ES 数据 |
三、前置条件
- 已完成 Pony Mail 0.11-incubating 安装:ES 2.4.6 运行中(
systemctl is-active elasticsearch为 active)、ponymail.cfg已由 setup.py 生成、python-elasticsearch 2.x 库已安装(⚠️ 1.x 库会使 archiver.py 拒绝启动)。 - 所有工具依赖
ponymail.cfg,需在/opt/ponymail/tools/目录下执行。
四、工具详解
4.1 setup.py —— 初始化工具
作用:连接 ES、探测引擎与库版本、创建索引(含 mbox/attachment/mbox_source/mailinglists/account/notifications 六类映射)、生成 ponymail.cfg 与 site/api/lib/config.lua。
帮助回显:
usage: setup.py [-h] [--defaults] [--dbprefix DBPREFIX] [--clobber]
[--dbhost DBHOST] [--dbport DBPORT] [--dbname DBNAME]
[--dbshards DBSHARDS] [--dbreplicas DBREPLICAS]
[--mailserver MAILSERVER] [--mldom MLDOM] [--wordcloud]
[--skiponexist] [--noindex] [--nocloud]
[--generator GENERATOR]
options:
-h, --help show this help message and exit
--defaults Use default settings
--dbprefix DBPREFIX
--clobber Allow overwrite of ponymail.cfg &
../site/api/lib/config.lua (default: create *.tmp if
either exists)
--dbhost DBHOST ES backend hostname
--dbport DBPORT DB port
--dbname DBNAME ES DB name
--dbshards DBSHARDS DB Shard Count
--dbreplicas DBREPLICAS
DB Replica Count
--mailserver MAILSERVER
Host name of outgoing mail server
--mldom MLDOM Domains to accept mail for via UI
--wordcloud Enable word cloud
--skiponexist Skip setup if ES index exists
--noindex Don't make an ES index, assume it exists
--nocloud Do not enable word cloud
--generator GENERATOR
Document ID Generator to use (legacy, medium, cluster,
full)
常用形式:
cd /opt/ponymail/tools
python3 setup.py --defaults --clobber # 用默认值并允许覆盖已存在配置
python3 setup.py --dbhost 10.0.0.5 --dbport 9200 --dbname mail \
--dbshards 3 --dbreplicas 1 --generator cluster
实测回显(--defaults):
Welcome to the Pony Mail setup script!
Let's start by determining some settings...
Okay, I got all I need, setting up Pony Mail...
Versions: library 2 (2.4.0), engine 2 (2.4.6)
Creating index ponymail
Index created! {'acknowledged': True}
Writing importer config (ponymail.cfg)
mod_lua configuration (config.lua)
Copying sample JS config to config.js (if needed)...
All done, Pony Mail should...work now :)
--defaults 默认值:ES localhost:9200、索引名 ponymail、分片 1、副本 0、mailserver localhost、可回复域 example.org、wordcloud 开、生成器 cluster。索引已存在且未加 --skiponexist 时脚本报错退出(防误覆盖)。
4.2 archiver.py —— 归档入口
作用:从 STDIN 读取一封原始邮件,按 List-ID 归类、解析正文/附件,写入 ES。生产环境由 MTA 别名管道或 Mailman3 插件调用;也可手工喂 stdin 验证。
帮助回显:
usage: archiver.py [-h] [--lid LID] [--altheader ALTHEADER]
[--allowfrom ALLOWFROM] [--ignore IGNOREFROM] [--private]
[--makedate] [--quiet] [--verbose] [--html2text] [--dry]
[--dumponfail DUMP]
options:
-h, --help show this help message and exit
--lid LID Alternate specific list ID
--altheader ALTHEADER
Alternate header for list ID
--allowfrom ALLOWFROM
(optional) source IP (mail server) to allow posts
from, ignore if no match
--ignore IGNOREFROM Sender/list to ignore input from (owner etc)
--private This is a private archive
--makedate Use the archive timestamp as the email date instead of
the Date header
--quiet Do not exit -1 if the email could not be parsed
--verbose Output additional log messages
--html2text Try to convert HTML to text if no text/plain message
is found
--dry Do not save emails to elasticsearch, only test parsing
--dumponfail DUMP If pushing to ElasticSearch fails, dump documents in
JSON format to this directory and fail silently.
STDIN 手动归档(实测,邮件含 List-ID: <ponymail-test.example.org>,经 lmtpd 投递):
[lmtpd] rc=0 Mon, 12 Oct 2026 21:33:23 -0000: Done archiving to <ponymail-test.example.org> as re040e3adc798ee1db640fe1c080917dffb7995d7f75f54b7577fe1bb@<ponymail-test.example.org>!
落库核对:
curl -s 'http://127.0.0.1:9200/ponymail/mbox/_count'
回显:
{"count":2,"_shards":{"total":1,"successful":1,"failed":0}}
💡 无
List-ID的邮件输出Nothing to import (no list-id found!)且不落库;用--altheader delivered-to指定替代头,或用--lid '<list.name.example.org>'强制指定。--dry只测解析不写库,适合上线前排错。
4.3 import-mbox.py —— 历史数据导入
作用:从本地 mbox/maildir 文件、mod_mbox 站点或 pipermail 页面批量导入历史邮件,支持 --dedup 按 Message-ID 去重、--lid 统一列表 ID。
帮助回显(节选):
usage: import-mbox.py [-h] [--source SOURCE] [--dir] [--interactive] [--quick]
[--mod-mbox] [--pipermail] [--lid LISTID]
[--project PROJECT] [--ext EXT] [--domain DOMAIN]
[--private] [--dry] [--duplicates] [--html2text]
[--requirelid] [--dedup] [--ignorebody IBODY]
[--resend RESEND] [--timeout TIMEOUT]
[--filter FROMFILTER] [--nomboxo]
options:
-h, --help show this help message and exit
--source SOURCE Source to scan (http(s)://, imap(s):// or file path)
--dir Input is in Maildir format
--quick Only grab the first file you can find
--mod-mbox This is mod_mbox, derive list-id and files from it
--pipermail This is pipermail, derive files from it (list ID has to
be set!)
--lid LISTID Optional List-ID to override source with. Format:
<list-name>@<domain>
--project PROJECT Optional project to look for ($project-* will be
imported as well)
--ext EXT Optional file extension e.g. ".gz" (or call it with an
empty string to not care)
--domain DOMAIN Optional domain extension for MIDs and List ID
reconstruction)
--private This is a privately archived list. Filter through auth
proxy.
--dedup Try to dedup messages based on ID before importing
--requirelid Require a List ID to be present, ignore otherwise
--timeout TIMEOUT Optional timeout in secs for importing an mbox/maildir
file (default is 600 seconds)
--filter FROMFILTER Optional sender filter: Only import emails from this
address
本地 mbox 导入实测:
cd /opt/ponymail/tools
python3 import-mbox.py --source /tmp/mboxtest/test.mbox --lid '<ponymail-test.example.org>' --dedup
回显:
Thread-1: 1 elements left to slurp
Thread-1: Slurping /tmp/mboxtest/test.mbox
Thread-1: Parsed 1 records (failed: 0) from /tmp/mboxtest/test.mbox
Thread-1: Done, 0 elements left to slurp
All done! 1 records inserted/updated after 0 seconds. 0 records were bad and ignored
💡 导入与实时归档使用同一 digest 口径(
ponymail.cfg的generator),同 Message-ID 重复导入不产生重复文档;--requirelid可跳过无 List-ID 的邮件。
4.4 list-lists.py —— 列表清单
帮助回显:
usage: list-lists.py [-h] [--dbname DBNAME] [--pretty] [--debug] [--counts]
options:
-h, --help show this help message and exit
--dbname DBNAME Override index name
--pretty Convert List IDs to email addresses
--debug Output the result JSON instead, very noisy!
--counts Show the count of messages for each list
实测(含已导入的两封邮件):
cd /opt/ponymail/tools
python3 list-lists.py --pretty
回显:
ponymail-test@example.org
--counts 追加每列表邮件数;--debug 输出原始 JSON。
4.5 edit-list.py —— 列表批量编辑
作用:批量改名、改描述、公私切换、删除、正文混淆(可选 --test 试运行不落库)。
帮助回显:
usage: edit-list.py [-h] [--source SOURCE | --mid MID] [--rename TARGET]
[--desc DESC] [--obfuscate OBFUSCATE]
[--private | --public] [--delete] [--wildcard] [--debug]
[--notag] [--test]
options:
-h, --help show this help message and exit
--source SOURCE Source list to edit
--mid MID Source Message-ID to edit
--rename TARGET (optional) new list ID
--desc DESC (optional) new list description
--obfuscate OBFUSCATE
Things to obfuscate in body, if any
--private Make all emails in list private
--public Make all emails in list public
--delete Delete emails from this list
--wildcard Allow wildcards in --source
--debug Debug output - very noisy!
--notag List IDs do not have <> in them
--test Only test for occurrences, do not run the chosen
action (dry run)
试运行实测(--public --test,不产生实际变更):
cd /opt/ponymail/tools
python3 edit-list.py --source '<ponymail-test.example.org>' --public --test
回显:
Beginning list edit:
- List ID: <ponymail-test.example.org>
- Action: Mark all emails public
DRY RUN - NO CHANGES WILL BE MADE
Updating docs...
All done, processed 2 docs in 0 seconds
💡 只给
--source不给动作会输出Nothing to do! No target list ID or action specified(实测)。生产执行前务必先加--test核对影响文档数。
4.6 copy-list.py —— 列表复制/迁移
帮助回显:
usage: copy-list.py [-h] --source <list id> [--target <list id>]
[--newdb <index name>] [--wildcard] [--notag]
options:
-h, --help show this help message and exit
--source <list id> Source list to edit
--target <list id> (optional) new list ID
--newdb <index name> (optional) new ES database name
--wildcard Allow wildcards in --source
--notag List IDs do not have <> in them
示例(把列表复制成新 ID 或迁到新索引):
cd /opt/ponymail/tools
python3 copy-list.py --source '<ponymail-test.example.org>' --target '<ponymail-test-archive.example.org>'
4.7 trace.py 与 elastic.py —— 调试诊断
trace.py 是代码级追踪工具,直接运行输出测试追踪行(实测):
>>>>[1]trace.py@62#<module>: test
>>>>[0]trace.py@45#trace: test
>>>>[1]trace.py@64#<module>: test
elastic.py 打印库/引擎版本并测试 ES 连通与索引存在性(实测):
Versions: Library: 2 (2, 4, 0) Engine: 2 (2.4.6)
True
False
- 第一行:库版本 2.4.0、引擎版本 2.4.6(两者一致说明归档器可运行);
- 第二行
True:ES 连接成功; - 第三行
False:默认索引校验结果(按脚本内置逻辑输出)。
4.8 curl —— web 与 ES 验证命令
# 验证 ES 引擎
curl -s http://127.0.0.1:9200/
# 验证索引内邮件数
curl -s 'http://127.0.0.1:9200/ponymail/mbox/_count'
# 验证 web 前端与 Lua API
curl -s http://127.0.0.1:8080/api/pminfo.lua
# 验证某封邮件可通过 web 检索
curl -s 'http://127.0.0.1:8080/api/email.lua?id=re040e3adc798ee1db640fe1c080917dffb7995d7f75f54b7577fe1bb%40%3Cponymail-test.example.org%3E'
# 查看线程视图
curl -s 'http://127.0.0.1:8080/api/thread.lua?id=re040e3adc798ee1db640fe1c080917dffb7995d7f75f54b7577fe1bb%40%3Cponymail-test.example.org%3E'
实测回显:
# /api/pminfo.lua
{"no_active_lists":0,"no_threads":0,"activity":{},"participants":0,"hits":0}
# /api/email.lua?id=... (节选)
{"tid":"re040e3adc...@<ponymail-test.example.org>","references":"","private":false,
"message-id":"<lmtp-test-001@example.org>",...,"subject":"Pony Mail LMTP test",
"body":"Hello from the LMTP import verification!...","attachments":{}}
# /api/thread.lua?id=...
{"emails":[{"tid":"re040e...","subject":"Pony Mail LMTP test",...}]}
五、注意事项
- ⚠️ 所有工具在
/opt/ponymail/tools/目录下执行(依赖同目录ponymail.cfg)。 - ⚠️ python-elasticsearch 必须 2.x(实测 1.x 库报
Exception: ('Unexpected elasticsearch version ', ...))。 - 归档/导入以
ponymail.cfg的generator为去重口径,变更生成器会造成历史重复。 edit-list.py/copy-list.py属批量变更工具,先--test试运行核对影响范围再执行。- 每条命令逐个单独执行;批量处理多个列表时逐条列出,勿用循环脚本。
六、常见问题
6.1 import-mbox.py 导入后 count 不增加
解决:同 Message-ID 被 digest 去重(正常现象,实测重复导入 All done! 1 records inserted/updated 但 count 不变);确认 generator 未变更。
6.2 archiver.py 报 Nothing to import (no list-id found!)
解决:邮件模板补 List-ID 头,或加 --altheader delivered-to / --lid '<list.name.example.org>' 指定列表。
6.3 工具报 Unexpected elasticsearch version
解决:pip3 install 'elasticsearch==2.4.0' 锁定 2.x 库后重试。
6.4 setup.py 报索引已存在
解决:索引已建(防误覆盖),加 --skiponexist 静默跳过,或改 --dbname 用新索引。
6.5 elastic.py 输出 False(连接失败)
解决:先 systemctl is-active elasticsearch 确认 ES 运行;核对 ponymail.cfg 的 hostname/port 与实际一致。
七、参考链接
- Apache Pony Mail GitHub:https://github.com/apache/incubator-ponymail
- 导入指南(docs/IMPORTING.md):https://github.com/apache/incubator-ponymail/blob/master/docs/IMPORTING.md
- 归档指南(docs/ARCHIVING.md):https://github.com/apache/incubator-ponymail/blob/master/docs/ARCHIVING.md
- Elasticsearch 2.4 REST 参考:https://www.elastic.co/guide/en/elasticsearch/reference/2.4/index.html