Ubuntu 22.04 安装 Mailman 指南

Ubuntu 22.04.5 从零安装 Mailman 3 全套(Core 3.3.10 + Postorius + HyperKitty + Postfix)并完成订阅发信归档闭环实测,含 jammy 缺包实况与四段排障实录。

Mailman邮件服务器邮件列表2026/10/21

Ubuntu 22.04 安装 Mailman 指南

目标:在 Ubuntu 22.04 服务器上从零安装 GNU Mailman 3 全套——Core 3.3.10 + Postorius 1.3.13 + HyperKitty 1.3.12 + Postfix MTA 对接,并完成「成员订阅 → 发信 → LMTP 收信 → 出站投递 → HyperKitty 归档」全链路闭环实测。已在 Ubuntu 22.04.5 LTS 实测通过,示例账号 admin / q1w2e3r4。组件背景与选型请先读 《Mailman 3.3.10 介绍与选型指南》。

一、环境信息

  • 操作系统:Ubuntu 22.04.5 LTS(jammy),x86_64,主机名 list.example.org;
  • Mailman 套件(venv 内实测版本):mailman 3.3.10(Tom Sawyer)、mailman-web 0.0.9(Postorius 1.3.13 + HyperKitty 1.3.12)、Django 4.2.30、uWSGI 2.0.31;
  • 发行包:nginx-core 1.18.0-6ubuntu14.21、postfix 3.6.4-1ubuntu1.4、python 3.10.12;
  • 安装布局:虚拟环境 /opt/mailman/venv,运行数据 /opt/mailman/var,Web 数据 /opt/mailman/web,附加配置 /opt/mailman/etc,主配置 /etc/mailman3/;
  • 专用系统用户:mailman(本文创建,uid 999);
  • 服务端口:Core REST 127.0.0.1:8001、Core LMTP 127.0.0.1:8024、Web 80(nginx 反代 uWSGI)、SMTP 25(postfix);
  • 演示域名:list.example.org(真实部署请换成你的域名并配好 A 记录与反向解析)。

二、前置条件

  1. 一台 Ubuntu 22.04 服务器,具备 root/sudo 权限;
  2. 服务器能访问公网(需要连 Ubuntu 归档源与 PyPI);
  3. 一个解析到服务器的域名(本文全程用 list.example.org 演示,生产请替换);
  4. 磁盘剩余 ≥ 2 GB(venv + 数据库 + 归档);
  5. Python 3.10(22.04 系统自带,无需另装)。

⚠️ 注意:多数云厂商默认封禁出站 25 端口,向外部域投递邮件需申请解封或配置中继(relayhost)。本文的闭环实测在本机完成,不依赖出站 25 端口。

三、版本选型说明

安装路线的选择依据是「Ubuntu 22.04 官方源里到底有什么」。先实查源内候选(以下为源内真实回显):

apt-cache policy mailman3 mailman3-web
mailman3:
  Installed: (none)
  Candidate: (none)
  Version table:
mailman3-web:
  Installed: (none)
  Candidate: 0+20200530-2
  Version table:
     0+20200530-2 500
        500 http://archive.ubuntu.com/ubuntu jammy/universe amd64 Packages

再看 apt-cache search mailman 的全部命中:

listadmin - command line mailman moderator queue manipulation
mailman3-web - Django project integrating Mailman3 Postorius and HyperKitty
python3-django-hyperkitty - Web user interface to access GNU Mailman3 archives
python3-django-mailman3 - Django library to help interaction with Mailman3 (Python 3 version)
python3-django-postorius - Web user interface to access GNU Mailman3
python3-mailmanclient - Python bindings for Mailman3 REST API (Python 3 version)

⚠️ 关键结论:Ubuntu 22.04(jammy)官方源只收录 Web 三件套(mailman3-web 元包 + python3-django-postorius/hyperkitty/mailmanclient),没有 Core 的 mailman3 二进制包(Candidate 为空)。因此本文主线采用 pip/venv 路线,一次性拿到三大件的最新稳定版(Core 3.3.10 恰好也是官方当前最新);apt 只负责 postfix/nginx 等基础件。Debian 12 官方源收录了完整 mailman3 核心(官方源信息,未在本环境实测),Debian 用户可直接走 apt 全家桶。

版本对照表:

组件本文实测版本安装来源说明
Mailman Core3.3.10(Tom Sawyer)PyPI pip install mailman官方当前最新稳定
mailman-web0.0.9PyPI pip install mailman-webPostorius + HyperKitty 打包项目
Postorius1.3.13随 mailman-webWeb 管理界面
HyperKitty1.3.12随 mailman-webWeb 归档
mailman-hyperkitty随 pipPyPI pip install mailman-hyperkittyCore→HyperKitty 归档插件(必装,见 7.5 排障)
Django4.2.30随依赖LTS 线
uWSGI2.0.31PyPIWeb 应用服务器
postfix3.6.4aptMTA
nginx1.18.0aptWeb 反代

💡 技术拓展:Core 与 Web 面的解耦部署在 Mailman 3 是官方设计——两者只通过两条本地通道交互:Core 的 REST API(Web 面调用)与 HyperKitty 归档 API(Core 回调 Web 面)。所以「pip 装 Core + 发行包装 Web」或「全部 pip」都能工作,唯一要求是两端配置里的 REST 凭据与归档共享密钥一致(分别见 4.4 与 4.6 节,配错就是 401,见 FAQ Q9)。

四、方案 A:pip/venv 全家桶安装(主线)

本节为 0→1 完整主线:基础依赖 → venv → Mailman 套件 → Core 配置与托管 → Web 面配置与初始化 → uwsgi/nginx 托管 → 验证清单。

4.1 安装基础依赖并创建专用用户

本步装齐 MTA、Web 服务器、编译工具与运行支撑库,并创建运行 Mailman 的专用系统用户(遵循最小权限,不用 root 跑服务):

sudo apt-get update
sudo apt-get install -y postfix python3-venv python3-dev gcc libssl-dev nginx sassc tzdata curl rsyslog
sudo useradd -r -M -d /opt/mailman -s /usr/sbin/nologin mailman
id mailman
uid=999(mailman) gid=999(mailman) groups=999(mailman)
  • postfix:MTA,收列表信并转交 Core(第七章对接);
  • python3-venv:创建虚拟环境所需(22.04 默认不装 ensurepip);
  • gcc/libssl-dev:uWSGI 在 venv 内以源码轮子/编译方式安装的编译兜底;
  • nginx:80 端口反代;
  • sassc:Postorius/HyperKitty 前端 SCSS 编译器,mailman-web compress 必需(缺了会报错,见 FAQ Q5);
  • tzdata:Django 时区库依赖,缺了 Web 页面直接 500(见 FAQ Q6);
  • rsyslog:postfix 日志走 syslog,生产服务器默认应有,无它则 /var/log/mail.log 不存在。

4.2 创建虚拟环境并安装 Mailman 套件

把三大件全部装进 /opt/mailman/venv,与系统 Python 隔离:

sudo python3 -m venv /opt/mailman/venv
sudo /opt/mailman/venv/bin/pip install --upgrade pip
sudo /opt/mailman/venv/bin/pip install mailman mailman-web uwsgi

实测安装成功回显(输出节选,实测首尾):

Successfully installed Mako-1.4.3 MarkupSafe-3.0.4 Pygments-2.21.0 aiosmtpd-1.4.6 alembic-1.20.0 asgiref-3.12.1 atpublic-8.0.1 attrs-26.1.0 authheaders-0.16.3 authres-1.2.0 certifi-2026.10.19 cffi-2.1.1 charset_normalizer-3.5.2 click-8.5.0 comrak-0.0.16 cryptography-50.0.2 defusedxml-0.7.1 django-4.2.30 django-allauth-65.19.7 django-appconf-1.2.0 django-compressor-4.6.0 django-extensions-4.1 django-gravatar2-1.4.5 django-haystack-3.4.0 django-picklefield-3.4.0 django-q2-1.10.0 django_mailman3-1.3.15 djangorestframework-3.17.2 dkimpy-1.1.8 dnspython-2.8.0 docutils-0.23 falcon-4.4.0 flufl.bounce-5.1.0 flufl.i18n-7.0.0 flufl.lock-9.2.0 greenlet-3.5.6 gunicorn-26.2.0 hyperkitty-1.3.12 idna-3.20 lazr.config-4.0.0 lazr.delegates-4.0.0 mailman-3.3.10 mailman-web-0.0.9 mailmanclient-3.3.5 mistune-3.4.0 networkx-3.4.2 nh3-0.3.7 oauthlib-4.0.0 packaging-26.3 passlib-1.7.4 postorius-1.3.13 psutil-7.2.2 publicsuffix2-2.20191221 pycparser-3.11 pyjwt-2.15.1 python-dateutil-2.9.0.post0 python3-openid-3.2.0 rcssmin-1.3.0 readme_renderer-46.0 requests-2.34.2 rjsmin-1.3.0 robot-detection-0.4 six-1.17.0 sqlalchemy-2.0.55 sqlparse-0.6.0 tomli-2.5.0 typing_extensions-4.16.0 urllib3-2.8.0 uwsgi-2.0.31 whoosh-2.7.4 zope.component-7.1 zope.configuration-7.1 zope.event-6.2 zope.hookable-8.3 zope.i18nmessageid-8.3 zope.interface-8.6 zope.schema-8.1
EXIT=0

安装完成后核对核心包版本:

/opt/mailman/venv/bin/pip list | grep -iE '^(mailman|postorius|hyperkitty|Django|uWSGI) '
Django              4.2.30
HyperKitty          1.3.12
mailman             3.3.10
postorius           1.3.13
uWSGI               2.0.31

⚠️ 注意:此时还缺 mailman-hyperkitty 归档插件,不装它列表消息会在归档环节被 SHUNT 丢弃(7.5 节有完整排障实录)。现在一并装上:

sudo /opt/mailman/venv/bin/pip install mailman-hyperkitty

4.3 认识 mailman 的配置查找顺序(重要,先踩坑再配置)

mailman 命令按固定顺序查找配置:$MAILMAN_CONFIG_FILE → 当前目录 ./mailman.cfg → ./var/etc/mailman.cfg → ~/.mailman.cfg → /etc/mailman.cfg → /etc/mailman3/mailman.cfg。两个实测行为要提前知道:

行为一:root 运行需要显式声明。 直接以 root 跑 mailman info 会拒绝执行:

mailman info
Usage: mailman [OPTIONS] [COMMAND] [ARGS]...
Try 'mailman --help' for help.

Error: If you are sure you want to run as root, specify --run-as-root.

本文全程用专用用户 mailman 运行(生产推荐),后续 CLI 一律 sudo -u mailman。

行为二:找不到配置时会在当前目录自动生成。 若所有搜索路径都没有配置文件,mailman 会在「当前目录/var/etc/」下自动生成一份默认配置。本文实验中就出现过一次(在根目录下以 root 首次运行时):

# AUTOMATICALLY GENERATED BY MAILMAN ON 2026-10-21 04:14:05 UTC
#
# This is your GNU Mailman 3 configuration file.  You can edit this file to
# configure Mailman to your needs, and Mailman will never overwrite it.

这份「意外生成」的 /var/etc/mailman.cfg 会遮蔽后面正规路径的查找(它在搜索顺序里更靠前),必须删除,否则你的手工配置永远不生效:

sudo rm -rf /var/etc

💡 说明:Mailman 的这个「自动生成、且永不覆盖」行为是给新用户的便利,但在多路径共存时容易变成排查黑洞。规范做法:配置只放 /etc/mailman3/mailman.cfg 一处(发行版惯例路径),其余路径保持不存在。

4.4 编写 Core 配置 /etc/mailman3/mailman.cfg

创建配置目录并写入主配置(含归档插件段,注意 class 用 mailman_hyperkitty.Archiver——这是独立插件包的类,不是 core 内置的 mailman.archivers.*,写错必 SHUNT,见 FAQ Q7):

sudo mkdir -p /etc/mailman3 /opt/mailman/etc
sudo tee /etc/mailman3/mailman.cfg > /dev/null <<'EOF'
[mailman]
layout: custom
site_owner: admin@example.org
default_language: en

[paths.custom]
var_dir: /opt/mailman/var

[database]
class: mailman.database.sqlite.SQLiteDatabase
url: sqlite:////opt/mailman/var/data/mailman.db

[mta]
incoming: mailman.mta.postfix.LMTP
outgoing: mailman.mta.deliver.deliver
lmtp_host: 127.0.0.1
lmtp_port: 8024
smtp_host: 127.0.0.1
smtp_port: 25
configuration: /opt/mailman/etc/postfix-mailman.cfg

[webservice]
hostname: 127.0.0.1
port: 8001
admin_user: restadmin
admin_pass: q1w2e3r4

[archiver.hyperkitty]
class: mailman_hyperkitty.Archiver
enable: yes
configuration: /etc/mailman3/mailman-hyperkitty.cfg
EOF
sudo tee /etc/mailman3/mailman-hyperkitty.cfg > /dev/null <<'EOF'
[general]
base_url: http://127.0.0.1/hyperkitty
api_key: SecretArchiverAPIKey
EOF
sudo cp /opt/mailman/venv/lib/python3.10/site-packages/mailman/config/postfix.cfg /opt/mailman/etc/postfix-mailman.cfg
sudo chown -R mailman:mailman /opt/mailman
sudo chown root:mailman /etc/mailman3/*.cfg
sudo chmod 640 /etc/mailman3/*.cfg

逐项说明:

段 / 键取值含义与影响
[mailman] layoutcustom路径布局方案名,与下一段 [paths.custom] 对应
[mailman] site_owneradmin@example.org站点所有者邮箱,异常通知/系统邮件收件人
[paths.custom] var_dir/opt/mailman/var运行数据根目录,logs/queue/data 等子目录自动派生
[database] class/urlSQLite列表/成员/规则库;小站点够用,大站点换 PostgreSQL(改 class 与 url 即可)
[mta] incomingpostfix.LMTP入站对接方式:等 postfix 用 LMTP 推给 Core
[mta] lmtp_host/port127.0.0.1:8024Core 的 LMTP 监听,须与 postfix 的 transport 指向一致
[mta] smtp_host/port127.0.0.1:25Core 出站交回本机 postfix
[mta] configurationpostfix-mailman.cfgpostfix 集成参数(postmap 属性模板),从包内样例复制
[webservice] admin_user/admin_passrestadmin / q1w2e3r4REST API 凭据;必须与 4.6 节 settings.py 中一致,生产务必改强密码
[archiver.hyperkitty] classmailman_hyperkitty.Archiver归档插件类(独立包)
[archiver.hyperkitty] enableyes开启消息归档到 HyperKitty
[archiver.hyperkitty] configurationmailman-hyperkitty.cfg插件自己的配置:base_url 指向 Web 面 /hyperkitty 路径,api_key 与 Web 面一致

验证 Core 读到了正确配置:

sudo -u mailman /opt/mailman/venv/bin/mailman info
GNU Mailman 3.3.10 (Tom Sawyer)
Python 3.10.12 (main, Aug 31 2026, 10:18:17) [GCC 11.4.0]
config file: /etc/mailman3/mailman.cfg
db url: sqlite:////opt/mailman/var/data/mailman.db
devmode: DISABLED
REST root url: http://127.0.0.1:8001/3.1/
REST credentials: restadmin:q1w2e3r4

config file 指向 /etc/mailman3/mailman.cfg、db url 与 REST credentials 均与配置一致,说明查找链干净、配置生效。

4.5 systemd 托管 Core

sudo tee /etc/systemd/system/mailman3.service > /dev/null <<'EOF'
[Unit]
Description=GNU Mailman 3 Core
After=network.target

[Service]
Type=forking
User=mailman
Group=mailman
ExecStart=/opt/mailman/venv/bin/mailman start
ExecStop=/opt/mailman/venv/bin/mailman stop
PIDFile=/opt/mailman/var/master.pid

[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now mailman3
systemctl is-active mailman3
ss -ltn | grep -E '8001|8024'
Created symlink /etc/systemd/system/multi-user.target.wants/mailman3.service → /etc/systemd/system/mailman3.service.
active
LISTEN 0      100        127.0.0.1:8024       0.0.0.0:*
LISTEN 0      2048       127.0.0.1:8001       0.0.0.0:*

Core 已托管并监听 REST(8001)与 LMTP(8024),均只绑 127.0.0.1(不暴露公网)。用 REST 接口做一次带认证的验证:

curl -s -u restadmin:q1w2e3r4 http://127.0.0.1:8001/3.1/system/versions
{"mailman_version": "GNU Mailman 3.3.10 (Tom Sawyer)", "python_version": "3.10.12 (main, Aug 31 2026, 10:18:17) [GCC 11.4.0]", "api_version": "3.1", "self_link": "http://127.0.0.1:8001/3.1/system/versions", "http_etag": "\"7a0313783b989609a9edd7b586965908b518385e\""}

4.6 编写 Web 面配置 /etc/mailman3/settings.py

mailman-web 的配置加载规则(源码确认):MAILMAN_WEB_CONFIG 环境变量指定配置文件,默认 /etc/mailman3/settings.py;文件不存在时 mailman-web 直接拒绝执行。该文件是一个 Django settings 模块,标准写法是先 import * 官方 base/mailman 两套默认值,再覆盖自己的项:

sudo tee /etc/mailman3/settings.py > /dev/null <<'EOF'
from mailman_web.settings.base import *      # noqa: F401,F403
from mailman_web.settings.mailman import *   # noqa: F401,F403

SECRET_KEY = "mm3web-demo-5f4dcc3b5aa765d61d8327deb882cf99"

ALLOWED_HOSTS = [
    "localhost",
    "127.0.0.1",
    "list.example.org",
]

MAILMAN_REST_API_URL = "http://127.0.0.1:8001"
MAILMAN_REST_API_USER = "restadmin"
MAILMAN_REST_API_PASS = "q1w2e3r4"
MAILMAN_ARCHIVER_KEY = "SecretArchiverAPIKey"
MAILMAN_ARCHIVER_FROM = ("127.0.0.1", "::1")

POSTORIUS_TEMPLATE_BASE_URL = "http://127.0.0.1"

ACCOUNT_DEFAULT_HTTP_PROTOCOL = "http"
DEFAULT_FROM_EMAIL = "postorius@list.example.org"

FILTER_VHOST = False
EOF
sudo chown root:mailman /etc/mailman3/settings.py
sudo chmod 640 /etc/mailman3/settings.py

逐项说明:

键取值含义与影响
SECRET_KEY任意长随机串Django 会话/CSRF 签名密钥,生产用 openssl rand -hex 32 生成并保密
ALLOWED_HOSTS域名 + localhostDjango 请求 Host 白名单,缺了你的域名会 400;localhost 必须保留(归档 API 回调用)
MAILMAN_REST_API_URL/USER/PASS与 mailman.cfg 一致Web 面→Core 的 REST 凭据,不一致即 401
MAILMAN_ARCHIVER_KEY与 mailman-hyperkitty.cfg 一致Core→Web 归档回调的共享密钥,不一致归档 403
MAILMAN_ARCHIVER_FROM127.0.0.1允许归档回调的来源 IP 白名单
POSTORIUS_TEMPLATE_BASE_URLhttp://127.0.0.1Core 反向取列表模板的 Web 地址
ACCOUNT_DEFAULT_HTTP_PROTOCOLhttp未配 HTTPS 前必须为 http,否则登录/确认链接生成成 https 打不开

4.7 Django 初始化:建库、静态资源、管理员

先建好数据与静态目录,再依次执行 migrate、collectstatic、compress、建超管:

sudo mkdir -p /opt/mailman/web/logs /opt/mailman/web/static
sudo chown -R mailman:mailman /opt/mailman/web
sudo -u mailman /opt/mailman/venv/bin/mailman-web migrate

实测回显(输出节选,实测首尾,共约 60 条迁移全部 OK):

  Applying postorius.0016_auto_20210810_2157... OK
  Applying postorius.0017_alter_emailtemplate_language... OK
  Applying postorius.0018_alter_emailtemplate_language... OK
  Applying postorius.0019_alter_emailtemplate_language... OK
  Applying sites.0002_alter_domain_unique... OK
  Applying socialaccount.0004_app_provider_id_settings... OK
  Applying socialaccount.0005_socialtoken_nullable_app... OK
  Applying socialaccount.0006_alter_socialaccount_extra_data... OK
sudo -u mailman /opt/mailman/venv/bin/mailman-web collectstatic --noinput

387 static files copied to '/opt/mailman/web/static'.
sudo -u mailman /opt/mailman/venv/bin/mailman-web compress
Compressing... done
Compressed 2 block(s) from 80 template(s) for 1 context(s).

⚠️ 实测排障:若 sassc 未安装,compress 会以 CommandError: An error occurred during rendering hyperkitty/user_profile/last_views.html: /bin/sh: 1: sassc: not found 失败——apt-get install sassc 后重跑即可。另有一个无害告警 ?: settings.ACCOUNT_EMAIL_REQUIRED is deprecated, use: settings.ACCOUNT_SIGNUP_FIELDS = [...],是上游依赖的弃用提示,不影响功能。

创建站点管理员(示例账号 admin / q1w2e3r4):

sudo -u mailman env DJANGO_SUPERUSER_PASSWORD=q1w2e3r4 \
  /opt/mailman/venv/bin/mailman-web createsuperuser --noinput --username admin --email admin@example.org
Superuser created successfully.

顺手把 Django 站点域名改成真实值(影响站内绝对链接):

echo "from django.contrib.sites.models import Site; s=Site.objects.get(pk=1); s.domain='list.example.org'; s.name='list.example.org'; s.save(); print(s.domain)" \
  | sudo -u mailman /opt/mailman/venv/bin/mailman-web shell
list.example.org

4.8 uWSGI + nginx 托管 Web 面

uWSGI 配置(HTTP socket 绑 127.0.0.1:8000,静态文件由 uwsgi 的 static-map 或 nginx alias 提供):

sudo mkdir -p /opt/mailman/etc
sudo tee /opt/mailman/etc/mailman-web.ini > /dev/null <<'EOF'
[uwsgi]
uid = mailman
gid = mailman
http-socket = 127.0.0.1:8000
chdir = /opt/mailman/web
module = mailman_web.wsgi:application
env = DJANGO_SETTINGS_MODULE=settings
env = PYTHONPATH=/etc/mailman3
processes = 4
threads = 2
master = true
vacuum = true
static-map = /static=/opt/mailman/web/static
EOF
sudo chown mailman:mailman /opt/mailman/etc/mailman-web.ini
  • DJANGO_SETTINGS_MODULE=settings + PYTHONPATH=/etc/mailman3:让 uwsgi 直接加载 /etc/mailman3/settings.py(4.6 节的文件名 stem 就是 settings);
  • static-map:把 /static/ 映射到 collectstatic 产物目录。

systemd 单元:

sudo tee /etc/systemd/system/mailman-web.service > /dev/null <<'EOF'
[Unit]
Description=Mailman 3 Web (Postorius + HyperKitty) via uWSGI
After=network.target mailman3.service

[Service]
User=mailman
Group=mailman
ExecStart=/opt/mailman/venv/bin/uwsgi --ini /opt/mailman/etc/mailman-web.ini
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now mailman-web

nginx 站点(80 端口反代 uwsgi,静态资源直接 alias):

sudo tee /etc/nginx/sites-available/mailman3 > /dev/null <<'EOF'
server {
    listen 80 default_server;
    server_name _;
    client_max_body_size 25m;
    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
    location /static/ {
        alias /opt/mailman/web/static/;
    }
}
EOF
sudo rm -f /etc/nginx/sites-enabled/default
sudo ln -sf /etc/nginx/sites-available/mailman3 /etc/nginx/sites-enabled/mailman3
sudo nginx -t
sudo systemctl start nginx

实测回显:

nginx: configuration file /etc/nginx/nginx.conf test is successful

⚠️ 实测排障:启动后若访问全部 500、uwsgi 日志出现 zoneinfo._common.ZoneInfoNotFoundError: 'No time zone found with key UTC',是系统缺 tzdata(apt-get install tzdata 后即可,无需重启 uwsgi)。

4.9 验证清单

systemctl is-active mailman3 mailman-web nginx postfix
active
active
active
active
curl -sI http://127.0.0.1/postorius/lists/ | head -1
curl -sI http://127.0.0.1/hyperkitty/ | head -1
curl -sI http://127.0.0.1/accounts/login/ | head -1
HTTP/1.1 200 OK
HTTP/1.1 200 OK
HTTP/1.1 200 OK

浏览器打开 http://list.example.org/(或服务器 IP):

  • /postorius/lists/:Postorius 列表管理(右上角登录 admin / q1w2e3r4);
  • /hyperkitty/:HyperKitty 归档首页;
  • /accounts/login/:统一登录页。

五、方案 B:apt 发行包路线(Ubuntu 22.04 实况与取舍)

本方案基于第三章的源内实查:mailman3-web(Postorius + HyperKitty Web 三件套,0+20200530-2)可用 apt 安装并自带 uwsgi/nginx 集成,但 Core 缺包。因此 Ubuntu 22.04 上「纯 apt」无法装出完整 Mailman 3,只能按以下两种取舍落地:

  1. apt Web 面 + pip Core 混配:apt-get install mailman3-web 装 Web 面(发行包自带 uwsgi、数据库初始化与站点配置,版本较旧:Postorius/HyperKitty 为 jammy 冻结版),再按 4.2–4.5 节用 venv 装 Core。两端版本可以不一致,只要 REST 凭据与归档密钥对齐即可(Core 与 Web 本就解耦,见第三章技术拓展)。适合希望 Web 面由发行包托管升级的场景。
  2. 换 Debian 12 直接 apt 全家桶:Debian 12 官方源收录 mailman3(Core)+ mailman3-web,一条命令装齐(官方源信息,未在本环境实测):
    sudo apt-get install mailman3 mailman3-web postfix
    

apt Web 面安装时的真实候选确认:

apt-cache policy mailman3-web
mailman3-web:
  Installed: (none)
  Candidate: 0+20200530-2
  Version table:
     0+20200530-2 500
        500 http://archive.ubuntu.com/ubuntu jammy/universe amd64 Packages

⚠️ 取舍提醒:jammy 冻结的 Web 面版本明显落后于 PyPI 最新(如 Postorius 1.3.13 / HyperKitty 1.3.12),且新功能修复都进不了旧版。除非有强烈的发行包托管诉求,Ubuntu 22.04 推荐 4.2–4.9 节的 pip 主线。

六、方案对比与选择建议

维度方案 A:pip/venv 全家桶(主线)方案 B:apt Web 面 + pip Core 混配
Core 版本3.3.10(最新稳定)3.3.10(同为 pip 装)
Web 面版本Postorius 1.3.13 / HyperKitty 1.3.12(最新)jammy 冻结版(旧)
配置文件/etc/mailman3/mailman.cfg + /etc/mailman3/settings.py(自行管理)Web 面由 dbconfig/debconf 接管,Core 部分同左
systemd 托管自建 2 个单元(mailman3 / mailman-web)Web 面发行包自带,Core 自建 1 个
升级方式pip install -UWeb 面随 apt、Core 随 pip,双轨
适合人群想要最新版、统一 pip 管理已有 apt 运维惯例、接受旧版本

选择建议:新部署直接选方案 A(版本最新、配置路径统一、本文全部实测);只有当团队规范强制「Web 应用必须走发行包」时选方案 B,并按第五章取舍处理 Core。

七、MTA(Postfix)对接与列表收发闭环实测

本节把 postfix 与 Core 打通,并做一次端到端闭环:成员订阅 → 向列表发信 → postfix LMTP 转交 Core → Core 出站投递到成员邮箱 → 消息归档进 HyperKitty。所有输出均为实测回显。

7.1 配置 postfix 的列表域路由

思路:list.example.org 是列表域,postfix 收到发往该域的信后不走本地邮箱投递,而是按 transport 表交给 Core 的 LMTP(127.0.0.1:8024):

echo "list.example.org" | sudo tee /etc/mailname
sudo postconf -e "myhostname = list.example.org"
sudo postconf -e "myorigin = /etc/mailname"
sudo postconf -e "mydestination = localhost"
sudo postconf -e "relay_domains = list.example.org"
sudo postconf -e "transport_maps = hash:/etc/postfix/transport"
sudo postconf -e "mynetworks = 127.0.0.0/8 [::1]/128"
echo "list.example.org lmtp:[127.0.0.1]:8024" | sudo tee /etc/postfix/transport
sudo postmap /etc/postfix/transport
sudo systemctl restart postfix
ss -ltn | grep ':25 '
LISTEN 0      100          0.0.0.0:25         0.0.0.0:*
LISTEN 0      100             [::]:25            [::]:*

逐项说明:

键取值说明
myhostnamelist.example.org本机对外主机名(真实部署写你的域名)
mydestinationlocalhost不能包含列表域!包含则信件被本地投递截走,永远到不了 Core
relay_domainslist.example.org声明本机为该域的中继
transport_maps/etc/postfix/transport域级路由表:列表域 → LMTP
transport 表内容list.example.org lmtp:[127.0.0.1]:8024与 mailman.cfg [mta] lmtp_host/lmtp_port 对应

7.2 建列表并订阅成员

sudo -u mailman /opt/mailman/venv/bin/mailman create test@list.example.org
Created mailing list: test@list.example.org
sudo -u mailman /opt/mailman/venv/bin/mailman lists
1 matching mailing lists found:
test@list.example.org

直接订阅成员(CLI 添加不走确认流程;两个示例成员:外域 alice 与本机域 bob):

echo "alice@example.org" | sudo -u mailman /opt/mailman/venv/bin/mailman addmembers - test@list.example.org
alice@example.org
echo "bob@mbox.example.org" | sudo -u mailman /opt/mailman/venv/bin/mailman addmembers - test@list.example.org
sudo -u mailman /opt/mailman/venv/bin/mailman members test@list.example.org
alice@example.org
bob@mbox.example.org

💡 说明:为了让「出站投递到成员邮箱」在单机上可验证,示例里把 mbox.example.org 声明为本机投递域(postconf -e "mydestination = localhost, mbox.example.org"),系统上建有本地用户 bob,投递落地在 /var/mail/bob;外域成员 alice@example.org 的投递行为如实反映外部 DNS 结果(见 7.4)。

7.3 向列表发信,观察 LMTP 入站

用本机 sendmail 投递一封测试信(生产中即任何外部用户发往 test@list.example.org):

sendmail test@list.example.org <<'EOF'
From: alice@example.org
To: test@list.example.org
Subject: third message full loop

full loop check.
EOF

postfix 侧日志(/var/log/mail.log)确认 LMTP 转交成功:

Oct 21 04:20:35 list postfix/lmtp[6468]: D2325158BE28: to=<test@list.example.org>, relay=127.0.0.1[127.0.0.1]:8024, delay=0.01, delays=0/0/0/0, dsn=2.0.0, status=sent (250 Ok)

Core 侧 LMTP 交互记录(/opt/mailman/var/logs/smtp.log,输出节选):

Oct 21 04:20:13 2026 (5646) ('127.0.0.1', 44224) >> b'MAIL FROM:<root@list.example.org> SIZE=336'
Oct 21 04:20:13 2026 (5646) ('127.0.0.1', 44224) sender: root@list.example.org
Oct 21 04:20:13 2026 (5646) ('127.0.0.1', 44224) >> b'RCPT TO:<test@list.example.org>'
Oct 21 04:20:13 2026 (5646) ('127.0.0.1', 44224) recip: test@list.example.org
Oct 21 04:20:13 2026 (5646) ('127.0.0.1', 44224) >> b'DATA'

7.4 Core 出站投递到成员邮箱

成员 bob 的本地邮箱里应收到「欢迎邮件 + 列表消息」两封信:

grep -E '^Subject' /var/mail/bob
Subject: Welcome to the "Test" mailing list
Subject: [Test] third message full loop

[Test] 前缀正是列表把消息展开投递给全体成员的标志(主题前缀为列表缺省行为)。外域成员 alice@example.org 的投递结果由外部 DNS 决定,本环境实测 example.org 为 nullMX 域,postfix 如实弹回(这也是有效的出站链路证据):

Oct 21 04:22:35 list postfix/smtp[6976]: DD3281589CFB: to=<alice@example.org>, relay=none, delay=0.02, delays=0.01/0.01/0/0, dsn=5.1.0, status=bounced (Domain example.org does not accept mail (nullMX))

7.5 HyperKitty 归档验证与排障实录

Core 的 archive runner 应把消息 POST 给 HyperKitty,日志(/opt/mailman/var/logs/mailman.log)出现 HyperKitty archived message 行:

Oct 21 04:22:35 2026 (6952) HyperKitty archived message <20261021042013.F009D158BE57@list.example.org> to https://list.example.org/archives/list/test@list.example.org/message/V7JRSUO3L3E5WOJL2YWXDZXOCKSHVBSO/
Oct 21 04:23:22 2026 (6952) HyperKitty archived message <20261021042320.C9DB0158BE49@list.example.org> to https://list.example.org/archives/list/test@list.example.org/message/XZTPMRS2R3DFTXBZASWF2CVVZNCZ3HLA/

浏览器打开 http://list.example.org/hyperkitty/list/test@list.example.org/ 即可看到线程归档与在线跟帖入口。

⚠️ 排障实录(本文实测踩过的最关键一坑):首次闭环时消息全部被 SHUNT 丢弃,mailman.log 报:

ModuleNotFoundError: No module named 'mailman.archiver'
Oct 21 04:20:37 2026 (5648) SHUNTING: 1792556437.3891735+18d1ed35b071776d12a1f7caf44816cf5785a261

定位过程与结论:

  1. 以为 Core 内置了 hyperkitty 归档器、把类名写成 mailman.archivers.hyperkitty.HyperKittyArchiver——同样报错(No module named 'mailman.archivers'),证明该模块根本不在 core 包里;
  2. HyperKitty 归档器是独立插件包 mailman-hyperkitty,正确类名是 mailman_hyperkitty.Archiver,且需要独立的插件配置文件;
  3. 修复三步:pip install mailman-hyperkitty → 按第 4.4 节改写 [archiver.hyperkitty] 段(class + configuration 指向 mailman-hyperkitty.cfg)→ 重启 Core;
  4. 已被 SHUNT 的历史消息用 mailman unshunt 打回队列重新处理(本文实测 2 封成功回收):
ls /opt/mailman/var/queue/shunt/
sudo -u mailman /opt/mailman/venv/bin/mailman unshunt

7.6 闭环证据链汇总

环节证据位置实测结果
成员订阅mailman members test@list.example.orgalice@example.org、bob@mbox.example.org
入站 LMTP/var/log/mail.log postfix/lmtp 行relay=127.0.0.1:8024 ... status=sent (250 Ok)
出站投递/var/mail/bobSubject: [Test] third message full loop
归档/opt/mailman/var/logs/mailman.logHyperKitty archived message ...
Web 查看http://list.example.org/hyperkitty/list/test@list.example.org/线程归档页面可访问

八、配置文件与 systemd 单元汇总

全部落盘清单(便于备份与迁移):

路径作用
/etc/mailman3/mailman.cfgCore 主配置(数据库/MTA/REST/归档)
/etc/mailman3/mailman-hyperkitty.cfg归档插件配置(base_url + api_key)
/etc/mailman3/settings.pyDjango(Postorius/HyperKitty)配置
/opt/mailman/etc/mailman-web.iniuWSGI 配置
/opt/mailman/etc/postfix-mailman.cfgCore 的 postfix 集成参数
/etc/postfix/transportpostfix 列表域 LMTP 路由表
/opt/mailman/var/Core 运行数据(库/队列/日志)
/opt/mailman/web/Web 面数据库与静态资源

服务自启状态实测:

systemctl is-enabled mailman3 mailman-web nginx postfix
enabled
enabled
enabled
enabled

💡 说明:Core 的日志与队列都在 /opt/mailman/var/ 下——logs/mailman.log(主日志,SHUNTING 就在这看)、logs/smtp.log(LMTP/SMTP 交互)、queue/shunt/(被搁置消息)、queue/out 等 13 个队列目录。日常巡检优先看 mailman.log 与 queue/shunt/ 是否非空。

九、常见问题(FAQ)

Q1:apt-get install mailman3 报「no installation candidate」。 Ubuntu 22.04 源内没有 Core 包(第三章实测回显),属发行版实况而非配置错误。走第四章 pip 主线,或换 Debian 12 用 apt 全家桶。

Q2:mailman info 报 Error: If you are sure you want to run as root, specify --run-as-root. Core 拒绝 root 直接运行。按本文创建 mailman 专用用户,CLI 用 sudo -u mailman 执行;确要 root 跑则加 --run-as-root(不推荐)。

Q3:首次运行后在当前目录出现 var/etc/mailman.cfg。 找不到配置时 mailman 会自动生成默认配置且「永不覆盖」,并因搜索顺序靠前而遮蔽正规配置。删除该文件(rm -rf /var/etc 或对应相对目录),把配置固定放在 /etc/mailman3/mailman.cfg。

Q4:mailman-web migrate 报 ValueError: Unable to configure handler 'file'。 日志目录不存在(默认写 BASE_DIR/logs/mailmanweb.log)。mkdir -p /opt/mailman/web/logs && chown -R mailman:mailman /opt/mailman/web 后重跑。

Q5:mailman-web compress 报 sassc: not found。 安装 apt-get install sassc 后重跑(Postorius/HyperKitty 的 SCSS 需要它编译)。

Q6:Web 页面 500,uwsgi 日志报 ZoneInfoNotFoundError: 'No time zone found with key UTC'。 系统缺 tzdata:apt-get install tzdata 即恢复,无需重启服务。

Q7:消息被 SHUNT,mailman.log 报 ModuleNotFoundError: No module named 'mailman.archiver'(或 'mailman.archivers')。 HyperKitty 归档器不在 Core 包内,需要独立插件:pip install mailman-hyperkitty,并把 [archiver.hyperkitty] 段写成 class: mailman_hyperkitty.Archiver + configuration: /etc/mailman3/mailman-hyperkitty.cfg(详见 7.5 排障实录),重启 Core 后 mailman unshunt 回收被搁置消息。

Q8:Web 面访问 Core 报 401 / 归档 403。 REST 401:settings.py 的 MAILMAN_REST_API_PASS 与 mailman.cfg [webservice] admin_pass 不一致;归档 403:mailman-hyperkitty.cfg 的 api_key 与 settings.py 的 MAILMAN_ARCHIVER_KEY 不一致。两端对齐即可。

Q9:外域成员投递 status=bounced (Domain example.org does not accept mail (nullMX))。 这不是 Mailman 故障:postfix 按外部 DNS 如实投递。换用真实可达的成员域名测试,或检查服务器出站 25 端口与 MX 记录。

十、卸载与清理

按依赖反序拆除(生产迁移前先备份 /opt/mailman/var 与 /opt/mailman/web 下的数据库):

sudo systemctl disable --now mailman-web mailman3 nginx postfix
sudo rm -f /etc/systemd/system/mailman3.service /etc/systemd/system/mailman-web.service
sudo systemctl daemon-reload
sudo rm -rf /opt/mailman /etc/mailman3
sudo apt-get purge -y postfix nginx-core
sudo userdel mailman

若仅卸 Mailman 保留 MTA/Web,则只执行前三条并保留 postfix/nginx。

十一、参考链接