Skip to main content
五折优惠 全部方案,限时优惠。起价 $2.48/mo
剩余 14 分钟
Web 与商业应用

如何用 Listmonk 自托管你的邮件通讯

C By Chike 14 分钟阅读
如何在 VPS 上用 Listmonk 自托管你的邮件通讯:Docker Compose、SMTP 中继,以及 SPF/DKIM/DMARC 送达率配置。

通过自托管,Listmonk 跑在你本就在付费的 VPS 上。发信成本就是你的 SMTP 中继按每千封邮件收取的费用。订阅者数量不会改变这两个数字中的任何一个。这正是结构性的转变,让自托管在你的需求超出托管版免费额度之后值得花那点搭建时间。

Listmonk 是一款基于 Go 的开源邮件通讯管理工具。只需一台 VPS 加一个 SMTP 中继账号的成本,你就能拥有无限的订阅者、列表和发信活动。在你敲第一条命令之前,有一点必须搞清楚:Listmonk 处理除了实际发信之外的一切。你的邮件落入收件箱还是垃圾邮件箱,取决于你配置的 SMTP 中继和你在发信域名上设置的 DNS 记录。

本指南涵盖什么

  • 用 Docker Compose 在带 HTTPS 的 Nginx(或 Caddy)反向代理后面部署 Listmonk 和 PostgreSQL
  • 根据你的发信量和预算选对 SMTP 中继(Amazon SES、Postmark、Brevo 或其他)
  • 在你的发信域名上配置 SPF、DKIM 和 DMARC
  • 避开四种上线后才暴露、且往往不会报出明确错误的故障模式
  • 预计耗时:如果你已经备好 VPS 和域名,30 分钟。
  • 不在范围内:滴灌自动化、事务性邮件、多实例部署(见 FAQ)

什么情况下 Listmonk 是错的工具

Listmonk 是某种特定场景下的正确答案。如果你的场景不同,就会有更好的答案。

每月发信量低于约 1 万封。 在这个规模下,Brevo 或 Mailchimp 的托管版免费额度,全算下来可能比一台 VPS 加一个 SMTP 中继更便宜。自托管要等你跨过这个区间才开始划算。部署之前,先拿你真实的订阅者数量和发信频率把账算一遍。

非技术团队。 对于不在终端里工作的人,Mailchimp 和 Brevo 的界面确实更好用。Listmonk 默认团队里有人能 SSH 进服务器、看懂 Docker 日志、判断 DNS 是否生效。如果没有这样的人,托管服务才是正确选择。

需要自动化工作流。 Listmonk 发送发信活动。它不支持滴灌序列、行为触发邮件,也没有可视化工作流编排器。如果你需要这些,就运行 Mautic 或者把 Listmonk 接到 n8n 上来承担自动化这一层。

受 GDPR 约束的订阅者列表。 如果你的订阅者主要在欧盟,或你的列表受 GDPR 数据驻留规则约束,就把 Listmonk 部署在欧洲数据中心。我们提供符合欧盟驻留要求的法兰克福和伦敦机房。

开始之前你需要准备什么

Listmonk 加 PostgreSQL 再加一份中等强度的队列负载,至少需要 2 GB RAM 打底。4 GB 是舒适的生产环境目标。

硬件。 对于每月发信量低于 5 万封的个人列表,一台配 2 vCPU、4 GB RAM 和 120 GB NVMe 存储的 VPS 就够用。每月 20 万封以上、还在增长的列表需要 4 vCPU 和 8 GB RAM。我们这套 Compose 部署跑在法兰克福一台 4 GB 的 VPS 上。如果可以,挑一个离你订阅者近的机房。发信延迟无所谓;管理后台的响应速度才重要。

域名。 一个通过 A 记录指向你 VPS 的域名。用一个子域名作管理界面,例如 mail.example.com。发信域名和管理子域名可以是同一个主域名。

SMTP 中继账号。 先别急着创建。中继的选择是本指南中最关键的决定,而它取决于你的发信量。先跳到“选择你的 SMTP 中继”那一节,挑一个服务商,再回到这里,手上备好 SMTP 主机、端口、用户名和密码。

VPS 上的软件。 Ubuntu 22.04 LTS 或 24.04 LTS。Docker Engine 24.0 或更高版本,并带 Docker Compose 插件。UFW 或同等防火墙,开放 22、80、443 端口。以非 root 的 sudo 用户身份获得 SSH 访问权限。

用 Docker Compose 部署 Listmonk

用 Docker Compose 在 VPS 上部署 Listmonk 和 PostgreSQL,置于一个负责终结 HTTPS 的 Nginx 或 Caddy 反向代理之后。

为这次部署建一个目录,然后放进一个 docker-compose.yml 文件,里面有两个服务:postgres 作数据库,listmonk 作应用。两者都在失败时重启。Listmonk 绑定到 127.0.0.1 因此反向代理是唯一能访问它的东西。

Docker Compose 文件

我需要您提供完整的英文文本来进行翻译。您发送的内容是"Here is the",这不是完整的句子。 请提供完整的英文短语或标签,我将立即为您翻译成简体中文。 docker-compose.yml。对照 Listmonk 官方安装文档核对确切的镜像标签和环境变量名。它们会随每次发布而更新。

# docker-compose.yml
services:
  postgres:
    image: postgres:16-alpine
    container_name: listmonk-postgres
    restart: unless-stopped
    environment:
      POSTGRES_USER: listmonk
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: listmonk
    volumes:
      - listmonk-postgres:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U listmonk"]
      interval: 10s
      timeout: 5s
      retries: 6

  app:
    image: listmonk/listmonk:latest
    container_name: listmonk-app
    restart: unless-stopped
    # Bind to loopback only. The reverse proxy is the public entrypoint.
    ports:
      - "127.0.0.1:9000:9000"
    depends_on:
      postgres:
        condition: service_healthy
    environment:
      LISTMONK_app__address: "0.0.0.0:9000"
      LISTMONK_db__host: postgres
      LISTMONK_db__port: 5432
      LISTMONK_db__user: listmonk
      LISTMONK_db__password: ${POSTGRES_PASSWORD}
      LISTMONK_db__database: listmonk

volumes:
  listmonk-postgres:

创建 .env 文件包含 POSTGRES_PASSWORD= 设为一段很长的随机字符串。然后启动整套服务,运行一次性的数据库安装:

# Pull images and start the database first
docker compose up -d postgres

# Run the install step (creates schema and the first admin user)
docker compose run --rm app ./listmonk --install --idempotent --yes

# Start the application
docker compose up -d

当前 --install 命令会提示输入管理员邮箱和密码。把它们保存下来。确认两个容器都在运行:

docker compose ps

预期输出:列出两个服务,状态都是 Up。postgres 那一行应显示 (healthy)。

当前 127.0.0.1:9000 这个绑定是有意为之。Listmonk 没有内置的认证限速器,也没有 IP 白名单。把 9000 端口暴露到公网,意味着地球上任何人都能撞到你的管理员登录页。反向代理的作用,就是让这个登录页只能通过 HTTPS 访问。

Nginx 反向代理与 SSL

从 Ubuntu 仓库安装 Nginx 和 Certbot。在 /etc/nginx/sites-available/listmonk 创建一个站点配置,带上 Listmonk 生成正确发信活动链接所需的代理头:

# /etc/nginx/sites-available/listmonk
server {
    listen 80;
    server_name mail.example.com;

    location / {
        proxy_pass http://127.0.0.1:9000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Listmonk streams campaign progress over WebSocket
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

把它软链到 sites-enabled,测试配置,重载 Nginx,然后签发证书:

sudo ln -s /etc/nginx/sites-available/listmonk /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d mail.example.com

Certbot 会重写 server 块,让它用新证书监听 443,并加上一条 HTTP 到 HTTPS 的跳转。验证:

curl -I https://mail.example.com

预期输出:HTTP/2 200,带一个有效的 strict-transport-security 头。如果你遇到重定向循环,检查上面 Nginx 配置里 X-Forwarded-Proto 头是否设好。十有八九,循环就是这个头引起的。

如果这台 VPS 上只跑 Listmonk,那就改用 Caddy。Caddyfile 只有三行,而且无需 cron 任务就能处理证书续期:

mail.example.com {
    reverse_proxy 127.0.0.1:9000
}

修复 Message-ID 头

默认情况下,Listmonk 在外发的 Message-ID 头里使用系统主机名。如果你的 VPS 主机名是 localhost 或者任何不是有效 FQDN 的东西,Listmonk 就会发出 Message-ID: <[email protected]>。Gmail 和 Outlook 的垃圾邮件过滤器会立刻把它标记出来。这一点记录在 Cloudron 论坛第 15410 号帖子.

修复方法是 Listmonk 的 config.toml里的一行。对于全新安装,通过 docker compose run --rm app ./listmonk --new-config生成该文件。然后设置:

[app]
hostname = "mail.example.com"

编辑后重启应用容器:

docker compose restart app

在你发出第一封发信活动之前就做好这件事。一个被 localhost.localdomain Message-ID 污染过的列表,比一个一开始就干净的列表更难挽救。

专业提示

如果你想跳过 Compose 的搭建,可以看看我们的 一键 Listmonk VPS 几分钟内一键部署 Listmonk。实例已预装并配好 PostgreSQL。你仍然需要配置你的 SMTP 中继并添加你的 DNS 记录。无论你怎么部署,这些步骤都不是可选的。

选择你的 SMTP 中继

为 Listmonk 选择 SMTP 中继:从成本结构、退信 webhook 和最佳适用场景对比 Amazon SES、Postmark、Brevo 和 Mailgun。

所有发信都经过你配置的中继。中继的 IP 信誉、速率限制和退信处理,才是决定你的邮件落入收件箱还是垃圾邮件箱的因素。

下面是功能对比。定价和免费额度会变。在决定之前,请在各家服务商的官方定价页上逐一核实。

提供商成本结构退信 webhook最适合
Amazon SES按封计费,量大时极低支持,通过 SNS量大时成本低;如果你已在用 AWS
Postmark月度基础费加按封计费支持,原生送达率优先;事务性邮件信誉好
Brevo低发信量有免费额度,更高量有付费档支持低发信量,且有升级路径
Mailgun按封计费无原生 webhook 端点;需要时用通用退信 API。开发者熟悉

以上只是对各家 SMTP 中继的简略一瞥。接下来,我们会逐一深入讲解。

SES 是量大时最便宜的选择,也是 Listmonk 社区里讨论最多的。它的搭建步骤比 Postmark 或 Brevo 多,但在任何真实发信量下,每封邮件的成本差距都大到足以让这点工作物有所值。

分三个阶段搭建。第一,创建一个带 AmazonSESFullAccess 策略的 IAM 用户(或一个只含 ses:SendRawEmail 以及 ses:GetSendQuota的更收紧的自定义策略)。第二,在 SES 控制台里验证你的发信域名。SES 会带你完成要添加的 DKIM CNAME。第三,在 SES SMTP 设置面板里生成 SMTP 凭据。这些不是你的 AWS 访问密钥;当你点击“Create SMTP credentials”时,SES 会生成一对单独的、专用于 SMTP 的用户名和密码。

在 Listmonk 管理后台的 Settings → SMTP 里,添加一个新服务器:

  • 主机: email-smtp.<region>.amazonaws.com (用你验证域名时所在的 SES 区域)
  • Port: 587
  • Auth protocol: LOGIN
  • TLS: STARTTLS
  • 用户名和密码:SES 生成的那对 SMTP 凭据

SES 在 587 端口上要求 STARTTLS。 如果你把 TLS 设为 none,或选了 465 端口,Listmonk 会连上,SES 返回 530 Must issue a STARTTLS command first,而管理后台里的 SMTP 凭据测试可能仍然显示成功。在跑任何发信活动之前,先给一个你能掌控的个人收件箱发一封真实的测试邮件。

新的 SES 账号都从沙盒模式开始。在沙盒里你只能发给已验证的邮箱地址,这对订阅者列表毫无用处。从 SES 控制台开一张工单,申请生产访问权限。审批通常需要一个工作日。

Postmark(送达率优先的替代方案)

Postmark 每封邮件的成本比 SES 高,但它原生支持退信 webhook,并且凭借严格的发信人策略,在收件箱送达率上有口碑。如果你的邮件通讯事关业务存亡,或者你不想去管 SES 从沙盒到生产的审批,那它就值这个价。

Listmonk 的配置形式和 SES 一样:主机、587 端口、STARTTLS、来自 Postmark 服务器 API 令牌面板的凭据。在 Postmark 的签名设置里验证你的发信域名,添加 Postmark 生成的 DKIM 记录,你就可以发信了。

当送达率比每封成本更重要时,选 Postmark。当发信量比省心更重要时,选 SES。

关于 SMTP 凭据测试的一个警告。 Listmonk 管理后台里的连接测试总是报告成功,哪怕凭据是错的。这一点记录在 几个 GitHub issue里。别信它。配置任何中继之后,先给单个测试订阅者发一封发信活动,并在目标收件箱里确认收到,再发给你的完整列表。

别用 Mailersend 来做批量发信活动。它每个连接 5 封邮件的上限会产生 421 Service not available 错误,而 Listmonk 会把这些记为已发送,尽管投递其实失败了。这场发信活动在 Listmonk 里看起来很成功,却悄无声息地丢掉了大部分邮件。

让邮件真正送达:SPF、DKIM 和 DMARC

在发信域名上配置 SPF、DKIM 和 DMARC 这三条 DNS 记录,让接收方邮件服务器能验证 Listmonk 的邮件,并把它挡在垃圾邮件箱之外。

这是你发信域名上的三条 DNS 记录,它们告诉接收方邮件服务器:你的域名授权了这个中继代你发信。漏掉其中任何一条,规模化之后都会有相当一部分发信落进垃圾邮件箱,无论你的中继或文案多干净。在发出第一封发信活动之前,就在你的 DNS 服务商那里把它们加好。

SPF 记录

SPF 授权特定的 IP 或发信服务为你的域名发信。在你发信域名的根上加一条 TXT 记录,带上你中继的 include。对于 SES,这条记录长这样:

v=spf1 include:amazonses.com ~all

对于 Postmark,把 include 换成 include:spf.mtasv.net。务必查阅你中继的官方 SPF 文档,确认确切的 include 值。它因服务商而异,有时还因区域而异。

一个域名只能有一条 SPF 记录。如果你已经为另一项服务(Google Workspace、Microsoft 365)配了一条,就把 include 合并进现有记录,而不是再加一条。

DKIM

DKIM 给外发邮件附上一个加密签名,接收方服务器会用你 DNS 里的公钥来验证它。密钥对由你的中继生成。你把公钥作为一条 TXT 记录加到一个选择器子域名(例如 sel1._domainkey.example.com)上,值就用中继给你的那个确切值。

Listmonk 不负责 DKIM 签名。中继负责。没有 Listmonk 专属的 DKIM 配置。跟着你中继的 DKIM 设置向导走,加上它给你的记录,然后等 DNS 生效(通常不到 30 分钟;有时要几个小时)。

DMARC

DMARC 告诉接收方服务器,对未通过 SPF 或 DKIM 校验的邮件该怎么处理。先用 p=none 进入监控模式,这样你能在汇总报告里看到失败情况,又不会在你排查配置错误期间影响送达率。在以下位置加一条 TXT 记录: _dmarc.example.com:

v=DMARC1; p=none; rua=mailto:[email protected]

两三周的报告都干净之后,再把策略收紧为 p=quarantine or p=reject。别跳过监控阶段。如果你 SPF include 里有个笔误,第一天又配上了 p=reject ,那一上来就会把你自己合法的邮件全部清掉,而且没有任何信号告诉你出了问题。

List-Unsubscribe 头(RFC 8058)由 Listmonk 自动生成。在 Settings → General 下确认它已启用。Gmail 和 Apple Mail 会把这个头呈现为一键退订选项,从而保护发信人信誉。

上线后真正会出问题的地方

四种故障模式,要等你发出第一封真实的发信活动才会冒出来。赶在你的订阅者之前抓住它们。

问题 1:退信率和你中继的数字对不上。 Listmonk 处理退信的方式,是通过 POP3 读取一个指定的退信邮箱地址,并删除它读到的每一封邮件。这其中包括休假回复、投递回执和外出自动回复,全都被归为退信。而你的中继只统计接收方邮件服务器返回的真正投递失败。如果 SES 报告 0.6% 而 Listmonk 报告 4%, 这就是差距所在。修复办法是配置退信 webhook 回调,取代 POP3。对于 SES,用 SNS 把退信通知投递到 Listmonk 的 webhook 端点。对于 Postmark,把它的原生 webhook 指向同一个端点。Webhook 退信是准确的;POP3 退信会虚高。

问题 2:SMTP 凭据测试明明是错的却说成功。 正如中继那一节提到的,连接测试无论凭据是否有效都报告成功。别信它。配置或更改任何 SMTP 设置之后,永远先发一封真实的测试邮件。

问题 3:一场发信活动发到一半就停了,没有报错。 哪怕只有 60% 的订阅者收到了邮件,Listmonk 也会把发信活动标记为 Finished。剩下那些发信,要么被中继拒绝,要么在 VPS 网络层被限流,而 Listmonk 不会把这两种情况作为发信活动级别的错误呈现出来(Cloudron 论坛第 13165 号帖子)。如果一场发信活动显示的发送数少于订阅者数,就打开你中继在该发送时间窗内的仪表盘,把中继接受的数量和 Listmonk 的数量做对比。真相在中继那边。

问题 4:没人在备份 PostgreSQL。 Compose 卷会在重启之间持久化数据。它防不住主机故障、误删 docker 卷,或损坏的升级。加一个每日的 pg_dump:

0 2 * * * docker exec listmonk-postgres pg_dump -U listmonk listmonk > /backups/listmonk-$(date +\%Y\%m\%d).sql

先手动跑一遍这行命令。在你信任这条 cron 条目之前,先确认输出文件非空。一个会写出零字节文件却不报错的备份脚本,比根本没有备份更糟,因为你会从此不再操心它。

在你把这一切交付到生产环境去信任之前,先给一个订阅者发一封测试发信活动,并在目标收件箱里确认收到。如果那一封邮件干净落地,接下来的一万封也会。

常见问题

为什么我的 Listmonk 退信率比 Amazon SES 报告的高?

Listmonk 的 POP3 退信处理会把外出回复和休假自动回复当作退信来读,从而虚高计数。配置 SES 的 SNS webhook 回调以获得准确的计数。

Listmonk 支持事务性邮件吗?

Listmonk 是一个邮件通讯和群发活动工具。它不原生处理事务性邮件(密码重置、订单确认、一对一的触发式邮件)。要从同一个发信域名发事务性邮件,请单独配置你中继的事务性端点,或者在 Listmonk 之外使用 Postal 或 Postmark 的事务性 API 这类专门工具。

我怎么把我的 Mailchimp 订阅者导入 Listmonk?

从 Audience → Export Audience 把你的 Mailchimp 列表导出为 CSV。在 Listmonk 里,进入 Subscribers → Import 并上传该 CSV。出现提示时映射 email 和 name 列。Listmonk 接受来自 Mailchimp、ConvertKit 以及大多数邮件通讯平台的标准 CSV 导出,无需格式转换。

当有人退订一场 Listmonk 发信活动时会发生什么?

Listmonk 默认在每一封发信活动邮件里加一个退订链接。当订阅者点击它时,他们会被加入屏蔽列表,并从所有未来的发信活动中移除。List-Unsubscribe 头(RFC 8058)会自动包含,因此支持一键退订的邮件客户端(Gmail、Apple Mail)会原生呈现它。该订阅者的记录会出于审计目的留在数据库里,但不会再有发信活动发给他们。

分享

博客更多内容

继续阅读。

准备好部署了吗? 起价 $2.48/月。

独立云厂商,自 2008 年起。AMD EPYC、NVMe、40 Gbps。14 天退款保证。