**比 Mailchimp 更自由:listmonk 开源 newsletters 系统从安装到高阶玩法全攻略**

**比 Mailchimp 更自由:listmonk 开源 newsletters 系统从安装到高阶玩法全攻略**

比 Mailchimp 更自由:listmonk 开源 newsletters 系统从安装到高阶玩法全攻略


为什么 listmonk 值得关注

在数字化营销和内容创业的时代, newsletters (邮件订阅)依然是与用户建立深度连接最有效的渠道之一。提到 newsletters 管理工具,很多人第一反应是 Mailchimp 、 SendGrid 这类 SaaS 平台。但这些平台有几个让人头疼的问题:费用随订阅者数量水涨船高、数据存储在第三方、数据导出受限、品牌调性难以完全自定义。

listmonk 正是为解决这些痛点而生的开源项目。它是一个功能完备、高性能的自托管邮件列表和 newsletters 管理平台,用 Go 语言编写,数据库支持 PostgreSQL 和 SQLite ,自带现代化的 Web 管理界面。开发者 knadh 在 GitHub 上维护这个项目,获得了超过一万颗星,充分说明了它的实用性和社区认可度。

选择 listmonk 的核心理由:

第一,完全自主可控。所有数据都存储在自己的服务器上,不存在第三方平台突然涨价或服务中断的风险。对于重视数据主权的企业和个人开发者来说,这一点至关重要。

第二,功能一应俱全。从订阅者管理、邮件模板编辑、群发活动编排到详细的统计分析, listmonk 提供了一站式解决方案,完全可以替代大多数商业邮件营销平台。

第三,性能优异。基于 Go 语言的高并发特性, listmonk 能够高效处理数十万甚至上百万的订阅者列表,对于成长型项目来说扩展性有保障。

第四,开源免费。作为开源项目, listmonk 可以免费使用,代码透明可审计,适合对成本敏感又不想被锁定在特定供应商的个人开发者和小型团队。

第五,配置灵活。支持 SMTP 配置、多邮件服务商对接、事务性邮件与营销邮件分离等高级功能,满足各种复杂场景需求。


环境搭建

系统要求

listmonk 对运行环境的要求相当友好,大多数主流操作系统都能运行。官方推荐的最低配置是:1 CPU 核心、1GB 内存、10GB 磁盘空间。如果你的订阅者数量超过十万,建议将内存提升到 2GB 以上以保证流畅运行。

listmonk 以单二进制文件发布,这意味着你不需要安装任何运行时环境。唯一的依赖是数据库,目前支持 PostgreSQL 12+ 和 SQLite 3。

安装方式一:Docker 快速部署(推荐新手)

Docker 是最容易上手的部署方式,一条命令就能启动完整的服务。

# 创建项目目录
mkdir listmonk && cd listmonk

# 下载官方 docker-compose 配置文件
curl -Lo docker-compose.yml https://raw.githubusercontent.com/knadh/listmonk/master/docker-compose.yml

# 创建必要的目录结构
mkdir -p data uploads

# 启动服务
docker-compose up -d

服务启动后, listmonk 的管理界面默认运行在 9000 端口。打开浏览器访问 http://你的服务器IP:9000 即可看到初始化页面。默认管理员账号是 admin ,密码是 admin ,首次登录后会强制要求修改密码。

docker-compose.yml 文件中已经预置了 PostgreSQL 数据库的配置,对于初学者来说完全开箱即用。如果你的服务器内存有限(比如只有 1GB),可以考虑将数据库换成 SQLite 以降低资源占用。

安装方式二:二进制直接安装

对于不想使用 Docker 的用户,可以直接从 GitHub releases 页面下载对应平台的二进制文件。

# 以 Linux AMD64 为例
wget https://github.com/knadh/listmonk/releases/download/v3.1.0/listmonk-3.1.0-linux-amd64.tar.gz

tar -xzf listmonk-3.1.0-linux-amd64.tar.gz

# 查看可用的命令
./listmonk --help

二进制安装后需要手动初始化配置文件和数据库:

# 初始化配置文件
./listmonk init

# 执行数据库迁移
./listmonk migrate

# 启动服务
./listmonk run

安装方式三:源码编译

如果你需要自定义功能或运行在非标准平台上,可以选择从源码编译:

# 克隆仓库
git clone https://github.com/knadh/listmonk.git
cd listmonk

# 安装前端依赖并构建
cd frontend && npm install && npm run build && cd ..

# 编译后端
go build -o listmonk .

数据库配置详解

listmonk 的配置文件位于项目目录下的 config.toml,包含了数据库连接、邮件发送、安全设置等所有可配置项。以下是关键的数据库配置部分:

[app]
# 应用运行地址和端口
address = "0.0.0.0:9000"

# 上传文件存放目录
upload_path = "uploads"

# 是否启用压缩(减少带宽占用)
gzip_responses = true

[db]
# PostgreSQL 配置示例
provider = "postgres"
host = "localhost"
port = 5432
user = "listmonk"
password = "your_secure_password"
database = "listmonk"
ssl_mode = "disable"

# SQLite 配置示例(适合资源有限的环境)
# provider = "sqlite"
# dsn = "listmonk.db"

如果你选择 SQLite 作为数据库,只需要将 provider 改为 sqlite 并指定 dsn 文件路径即可。SQLite 适合个人项目或订阅者数量不超过五万的场景;对于更大型的生产环境,PostgreSQL 是更稳定的选择。

反向代理配置

在生产环境中,建议使用 Nginx 或 Caddy 作为反向代理,并配置 HTTPS 加密。

Nginx 配置示例:

server {
    listen 80;
    server_name listmonk.yourdomain.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name listmonk.yourdomain.com;

    ssl_certificate /path/to/fullchain.pem;
    ssl_certificate_key /path/to/privkey.pem;

    client_max_body_size 100M;

    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;

        # WebSocket 支持(后台长连接)
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

核心功能详解

订阅者管理

listmonk 提供了强大的订阅者管理功能,支持灵活的分段和标签系统。你可以给订阅者打上不同的标签(比如按兴趣、地区、购买阶段等维度),然后针对特定群体发送定向内容。

订阅者的核心属性包括:

# 订阅者数据结构示例(用于 API 调用)
subscriber_data = {
    "email": "subscriber@example.com",
    "name": "张三",
    "status": "subscribed",  # subscribed, unsubscribed, enabled, blocked
    "lists": [1, 3, 5],      # 关联的列表 ID
    "tags": ["付费用户", "技术爱好者", "北京地区"],
    "attribs": {             # 自定义属性
        "plan": "premium",
        "signup_source": "landing_page",
        "last_purchase_date": "2024-06-15"
    }
}

批量导入功能支持 CSV 和 JSON 格式,单次最多可以导入十万条订阅者记录。导入过程中系统会自动处理重复邮箱(可选择覆盖或跳过)、验证邮箱格式、清理无效数据。

列表与分段

listmonk 使用“列表”(List)和“分段”(Segment)两种方式来组织订阅者。

列表是订阅者主动选择加入的订阅组,比如“产品更新通知”“促销活动”“技术博客”等。一个订阅者可以同时属于多个列表。

分段则是基于条件的动态查询结果,比如“最近三十天活跃但未购买的用户”“打开率超过百分之五十的订阅者”等。分段在发送邮件时特别有用,可以精准触达特定群体。

# 分段查询语法示例
# 在 listmonk 的 UI 中可以通过可视化界面构建
# 也可以通过 API 用 SQL 风格的条件构建

segment_query = {
    "status": "subscribed",
    "tags": ["活跃用户"],
    "expression": "last_opened_at > NOW() - INTERVAL '30 days'"
}

邮件模板与编辑器

listmonk 内置了功能完善的邮件模板编辑器,支持两种编辑模式:可视化拖拽编辑和原始 HTML 编辑。

可视化编辑器提供了丰富的组件库,包括标题、段落、图片、按钮、社交链接、页脚等常用元素。每个组件都可以单独设置样式和行为,生成的代码会自动优化以保证邮件在各种客户端的兼容性。

对于有前端经验的开发者,HTML 编辑器提供了完全的代码控制权。你可以直接编写或粘贴 HTML 代码,系统会实时预览渲染效果。编辑器支持模板变量语法,用于插入订阅者姓名、取消订阅链接等动态内容:

<!-- 模板变量语法 -->
<h1>亲爱的 {{ .Subscriber.Name }},</h1>

<p>感谢您订阅我们的 newsletters,这是您的专属优惠码:</p>

<div style="background: #f5f5f5; padding: 20px; text-align: center;">
    <strong style="font-size: 24px;">WELCOME{{ .Subscriber.UUID }}</strong>
</div>

<p>
    <a href="{{ . unsubscribeURL }}" style="color: #666;">
        取消订阅
    </a>
</p>

邮件活动编排

创建邮件发送活动是 listmonk 的核心功能。完整的活动流程包括:

定义收件人 → 编辑内容 → 预览测试 → 发送设置 → 执行发送

在定义收件人阶段,你可以选择具体的列表或分段,也可以排除特定标签的订阅者。系统会实时显示预估的收件人数量,帮助你评估覆盖面。

发送设置提供了丰富的控制选项:

  • 定时发送:可以精确到分钟,支持时区自动转换,确保全球各地的订阅者都在合适的时间收到邮件。

  • 发送速率控制:避免邮件服务商将你的发送行为识别为垃圾邮件。listmonk 支持配置每秒发送数量,以及在指定时间段内的总量上限。

  • 重试机制:对于发送失败的邮件,系统会自动重试,最多可配置重试次数和间隔时间。

  • 发送窗口:可以设置在某个时间窗口内尝试发送,超时后标记为未送达,适合时效性不强的内容。

统计分析

listmonk 提供了多维度的数据统计,帮助你持续优化邮件营销效果。

汇总统计展示整体表现:

  • 发送总数、成功数、失败数、待发送数
  • 打开率(基于唯一打开次数)
  • 点击率(基于唯一点击次数)
  • 退订率

趋势图展示关键指标随时间的变化,帮助你识别最佳发送时间、内容类型与效果的关联。

热力图展示邮件中的链接点击分布,直观了解读者的兴趣点。

单独的邮件报告提供每封邮件的详细表现数据,包括具体的退订原因统计(如果你配置了退订原因收集)。

事务性邮件

除了营销邮件,listmonk 还支持发送事务性邮件。事务性邮件是响应用户操作自动触发的通知,比如订单确认、密码重置、订阅成功等。

# 通过 API 发送事务性邮件
import requests

response = requests.post(
    "http://your-listmonk/api/tx",
    headers={"Content-Type": "application/json"},
    json={
        "subscribers": ["user@example.com"],
        "template_id": "order_confirmation",
        "data": {
            "order_id": "ORD-2024-12345",
            "order_total": "299.00",
            "items": [
                {"name": "产品A", "qty": 2},
                {"name": "产品B", "qty": 1}
            ]
        }
    }
)

事务性邮件与营销活动共享发送通道,但可以单独配置优先级、速率限制和追踪规则。


实战教程:从零开始搭建 newsletters 系统

场景设定

假设你是一个独立开发者,准备运营一个专注于 Python 编程的技术博客。你需要:

  • 建立订阅者列表,用于推送新文章
  • 定期发送技术教程和工具推荐
  • 追踪订阅者的阅读和点击行为
  • 自动处理新订阅欢迎邮件和退订请求

接下来的教程将手把手带你完成整个系统的搭建和配置。

步骤一:完成初始配置

首次登录 listmonk 后,系统会引导你完成基础配置:

第一步,修改管理员密码。点击右上角头像,选择“设置”,在“管理员”标签页修改密码。建议使用强密码,包含大小写字母、数字和特殊字符,长度不少于十二位。

第二步,配置发件人信息。在“设置”的“发件人”标签页,添加你的发件人档案:

名称:PythonTech Blog
邮箱:newsletter@pythontech.example.com
回复地址:hello@pythontech.example.com

这里的回复地址用于接收读者回复邮件,建议与发件地址分开管理。

第三步,配置 SMTP。切换到“设置”的“ SMTP ”标签页,配置你的邮件发送服务。以常见的 SendGrid 为例:

SMTP 主机:smtp.sendgrid.net
SMTP 端口:587
用户名:apikey  # SendGrid 使用 apikey 作为用户名
密码:SG.your_sendgrid_api_key
TLS:STARTTLS

如果你使用自己的邮件服务器,配置项会有所不同。配置完成后点击“测试连接”验证设置是否正确。

步骤二:创建订阅列表

返回仪表板,点击左侧菜单的“列表”,然后点击“新建列表”。

基本信息

名称:Python 技术周报
slug:python-weekly
描述:每周精选 Python 编程技巧、工具推荐和实战案例

订阅选项

是否需要双重确认:是(推荐开启,防止误订阅)
是否显示在公开订阅页面:是

邮件偏好

默认语言:简体中文
邮件频率:每周一次

创建完成后,记住这个列表的 ID(可以在列表详情页面看到)。接下来你需要创建一个“公开订阅页面”,让读者可以自助加入列表。

点击“订阅页面”菜单,然后点击“新建订阅页面”,选择刚才创建的列表,编辑页面标题和简介,然后保存。生成的订阅页面 URL 类似:https://your-domain.com/subscription/abcdef123456,你可以把这个链接放到博客、社交媒体等地方。

步骤三:创建邮件模板

模板是 newsletters 的外观骨架,好的模板既要有吸引力,又要保证在各种邮件客户端正常显示。

点击“模板”菜单,然后点击“新建模板”。listmonk 提供了几个内置模板作为起点,你也可以从头创建空白模板。

设计原则:邮件模板与网页设计有显著不同。由于邮件客户端对 CSS 支持有限,建议遵循以下原则:

  • 使用表格布局而非现代的 Flexbox 或 Grid
  • 内联所有 CSS 样式(许多客户端会忽略 head 部分的样式声明)
  • 图片设置固定宽高,避免加载时页面跳动
  • 字体使用常见的系统字体栈
  • 按钮使用真实的 <a> 标签而非 <button>

以下是适合 newsletters 的基础模板结构:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ .Subject }}</title>
    <!-- 注意:实际使用时请将所有样式内联 -->
</head>
<body style="margin: 0; padding: 0; background-color: #f4f4f4; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;">

    <!-- 外层容器 -->
    <table role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%" style="background-color: #f4f4f4;">
        <tr>
            <td align="center" style="padding: 40px 10px;">

                <!-- 邮件主体 -->
                <table role="presentation" border="0" cellpadding="0" cellspacing="0" width="600" style="background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">

                    <!-- 头部区域 -->
                    <tr>
                        <td align="center" style="padding: 40px 40px 20px 40px; background-color: #306cde; border-radius: 8px 8px 0 0;">
                            <h1 style="margin: 0; color: #ffffff; font-size: 24px; font-weight: 600;">
                                PythonTech 技术周报
                            </h1>
                            <p style="margin: 10px 0 0 0; color: rgba(255,255,255,0.8); font-size: 14px;">
                                第 {{ .CampaignUUID }} 期 · {{ .CampaignDate }}
                            </p>
                        </td>
                    </tr>

                    <!-- 内容区域 -->
                    <tr>
                        <td style="padding: 40px;">

                            <!-- 问候语 -->
                            <p style="margin: 0 0 20px 0; color: #333333; font-size: 16px; line-height: 1.6;">
                                亲爱的 {{ .Subscriber.FirstName }},
                            </p>

                            <p style="margin: 0 0 20px 0; color: #333333; font-size: 16px; line-height: 1.6;">
                                欢迎阅读本期周报,以下是本周的精选内容:
                            </p>

                            <!-- 文章卡片示例 -->
                            <table role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%" style="margin: 20px 0; border: 1px solid #e0e0e0; border-radius: 6px;">
                                <tr>
                                    <td style="padding: 20px;">
                                        <h3 style="margin: 0 0 10px 0; color: #306cde; font-size: 18px;">
                                            <a href="#" style="color: #306cde; text-decoration: none;">异步编程实战:Python asyncio 完全指南</a>
                                        </h3>
                                        <p style="margin: 0 0 15px 0; color: #666666; font-size: 14px; line-height: 1.5;">
                                            从概念到实践,深入理解 asyncio 的核心机制,学习如何编写高效的异步代码...
                                        </p>
                                        <a href="#" style="display: inline-block; padding: 10px 20px; background-color: #306cde; color: #ffffff; text-decoration: none; border-radius: 4px; font-size: 14px;">
                                            阅读全文
                                        </a>
                                    </td>
                                </tr>
                            </table>

                        </td>
                    </tr>

                    <!-- 页脚区域 -->
                    <tr>
                        <td style="padding: 30px 40px; background-color: #f8f8f8; border-radius: 0 0 8px 8px; border-top: 1px solid #e0e0e0;">
                            <p style="margin: 0 0 10px 0; color: #666666; font-size: 12px; text-align: center;">
                                你收到这封邮件是因为订阅了 PythonTech 技术周报
                            </p>
                            <p style="margin: 0; color: #666666; font-size: 12px; text-align: center;">
                                <a href="{{ .unsubscribeURL }}" style="color: #306cde;">取消订阅</a>
                                ·
                                <a href="{{ .manageURL }}" style="color: #306cde;">管理订阅偏好</a>
                                ·
                                <a href="{{ .trackURL }}" style="color: #306cde;">查看在线版本</a>
                            </p>
                        </td>
                    </tr>

                </table>

            </td>
        </tr>
    </table>

</body>
</html>

保存模板后,你可以点击“预览”按钮,输入一个测试邮箱来查看实际渲染效果。预览功能非常实用,因为它会模拟真实邮件客户端的显示效果,包括图片加载、字体渲染等细节。

步骤四:配置自动回复邮件

新订阅用户通常期待一个即时的欢迎回复,这不仅能确认订阅成功,还能增强用户的参与感。

点击“事务性模板”菜单,然后点击“新建事务性模板”。

模板名称:新订阅欢迎邮件

邮件主题:欢迎加入 PythonTech 技术周报!

模板内容

<p>你好,{{ .Subscriber.Name }}!</p>

<p>感谢你订阅 PythonTech 技术周报。我是周报的主理人,专注于分享 Python 编程的实用技巧和前沿技术。</p>

<p>从下周开始,你将每周收到我们精心筛选的内容,包括:</p>

<ul>
    <li>实用的编程技巧和最佳实践</li>
    <li>热门工具和库的深度评测</li>
    <li>实战项目的完整解析</li>
    <li>行业动态和职业发展建议</li>
</ul>

<p>如果你有任何想了解的话题,可以直接回复这封邮件告诉我。</p>

<p>期待与你一起成长!</p>

<p>—— PythonTech 团队</p>

保存模板后,返回“设置”的“订阅者”标签页,勾选“新订阅者通知”选项,然后在下拉菜单中选择刚才创建的欢迎邮件模板。

步骤五:创建第一个邮件活动

点击左侧菜单的“活动”,然后点击“新建活动”。

第一步:基本信息

活动名称2024年7月第1期
主题Python 3.13 新特性抢先看
预发件人PythonTech Blog <newsletter@pythontech.example.com>

第二步:选择收件人

点击“选择列表”,勾选“Python 技术周报”。你可以在右侧预览到当前列表的订阅者数量。

如果你有多个列表或想排除某些用户,可以使用组合条件。比如,排除标签为“VIP用户”(他们可能已经通过其他渠道收到内容)的订阅者。

第三步:编辑内容

在内容编辑器中,点击“从模板新建”,选择之前创建的 newsletters 模板。然后将模板中的示例文章替换为实际内容。

对于本期刊物,我们假设要推荐三篇文章:

<!-- 文章一 -->
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%" 
       style="margin: 20px 0; border: 1px solid #e0e0e0; border-radius: 6px;">
    <tr>
        <td style="padding: 20px;">
            <h3 style="margin: 0 0 10px 0; color: #306cde; font-size: 18px;">
                <a href="https://pythontech.example.com/python-313-preview" 
                   style="color: #306cde; text-decoration: none;">
                    Python 3.13 新特性抢先看:JIT 编译器与交互式解释器
                </a>
            </h3>
            <p style="margin: 0 0 15px 0; color: #666666; font-size: 14px; line-height: 1.5;">
                Python 3.13 即将发布,最大的亮点是实验性的 JIT 编译器和全新的交互式解释器...
            </p>
            <a href="https://pythontech.example.com/python-313-preview" 
               style="display: inline-block; padding: 10px 20px; background-color: #306cde; color: #ffffff; text-decoration: none; border-radius: 4px; font-size: 14px;">
                阅读全文 →
            </a>
        </td>
    </tr>
</table>

<!-- 文章二 -->
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%" 
       style="margin: 20px 0; border: 1px solid #e0e0e0; border-radius: 6px;">
    <tr>
        <td style="padding: 20px;">
            <h3 style="margin: 0 0 10px 0; color: #306cde; font-size: 18px;">
                <a href="https://pythontech.example.com/fastapi-tips" 
                   style="color: #306cde; text-decoration: none;">
                    FastAPI 性能优化实战:从 100 QPS 到 1000 QPS
                </a>
            </h3>
            <p style="margin: 0 0 15px 0; color: #666666; font-size: 14px; line-height: 1.5;">
                分享一个真实案例,讲述如何在不改变业务逻辑的前提下将 API 吞吐量提升十倍...
            </p>
            <a href="https://pythontech.example.com/fastapi-tips" 
               style="display: inline-block; padding: 10px 20px; background-color: #306cde; color: #ffffff; text-decoration: none; border-radius: 4px; font-size: 14px;">
                阅读全文 →
            </a>
        </td>
    </tr>
</table>

第四步:预览和测试

在发送之前,点击“发送测试”按钮,输入你的个人邮箱地址。你会收到一封测试邮件,检查以下几点:

  • 邮件主题是否正确显示
  • 排版是否整齐美观
  • 图片是否正常加载
  • 链接是否可点击
  • 退订链接是否正确

收到测试邮件确认无误后,进入下一步。

第五步:发送设置

你可以选择立即发送,或者设置为定时发送。建议选择定时发送,这样可以避开周末和深夜时段。工作日的上午十点到下午两点通常是打开率较高的时段。

发送时机:定时发送
发送日期:2024-07-08
发送时间:10:00
时区:Asia/Shanghai (UTC+8)

点击“启动活动”, listmonk 会开始向选定的收件人发送邮件。你可以在活动详情页面实时监控发送进度。

步骤六:查看和分析结果

邮件发送完成后,等待几个小时让订阅者有时间打开邮件。然后回到活动详情页面查看统计数据。

关键指标解读:

发送统计展示了邮件的分发情况:

  • 总发送数:系统尝试发送的邮件数量
  • 已发送:成功交付到收件人邮箱服务器的邮件数量
  • 已退回:无法送达的邮件(可能是因为邮箱不存在或被拒收)

打开统计基于邮件中的追踪像素:

  • 打开数:邮件被打开的总次数
  • 唯一打开数:不同用户打开邮件的次数(去重后)
  • 打开率:唯一打开数除以已发送数的百分比

点击统计追踪邮件中的链接点击:

  • 点击数:所有链接被点击的总次数
  • 唯一点击数:不同用户点击链接的次数
  • 点击率:唯一点击数除以唯一打开数的百分比

如果你的打开率低于百分之二十,可以考虑优化邮件发送时间和标题;如果点击率偏低,可能是内容不够吸引人或行动号召不够明确。

步骤七:使用 API 自动化工作流

listmonk 提供了完整的 REST API,支持与外部系统集成。以下是一些实用的 API 调用示例。

场景一:当你发布新文章时自动发送 newsletters

假设你使用 Hugo 构建博客,可以在 CI/CD 流程中添加一个步骤,在发布新文章后自动触发 newsletters 发送:

#!/bin/bash

# 当博客构建成功后的钩子脚本
# 调用 listmonk API 创建并发送活动

CAMPAIGN_NAME="PythonTech 周报 - $(date +%Y-%m-%d)"

# 创建新活动
RESPONSE=$(curl -X POST "http://localhost:9000/api/campaigns" \
  -H "Content-Type: application/json" \
  -u "admin:your_password" \
  -d '{
    "name": "'"$CAMPAIGN_NAME"'",
    "subject": "PythonTech 周报:"'"$(git log -1 --pretty=%B | head -n 1)"'",
    "list_ids": [1],
    "template_id": 1,
    "type": "regular",
    "send_at": "'"$(date -d '+2 days' +%Y-%m-%dT10:00:00Z)"'",
    "from_email": "newsletter@pythontech.example.com",
    "from_name": "PythonTech Blog"
  }')

CAMPAIGN_ID=$(echo $RESPONSE | jq -r '.data.id')

echo "创建活动 ID: $CAMPAIGN_ID"

# 更新活动内容
curl -X PUT "http://localhost:9000/api/campaigns/$CAMPAIGN_ID/content" \
  -H "Content-Type: application/json" \
  -u "admin:your_password" \
  -d '{
    "body": "<h1>本期内容</h1><p>请登录管理后台编辑内容</p>"
  }'

# 立即发送(也可以留待定时)
curl -X POST "http://localhost:9000/api/campaigns/$CAMPAIGN_ID/send" \
  -u "admin:your_password"

echo "活动已发送"

场景二:收集用户注册信息并自动添加订阅者

假设你的网站有用户注册功能,用户注册成功后你想自动将用户添加到 listmonk 的订阅列表:

import requests
import hashlib

def sync_user_to_listmonk(user_email, user_name, user_plan="free"):
    """
    将新注册用户同步到 listmonk 订阅列表

    参数:
        user_email: 用户邮箱
        user_name: 用户名
        user_plan: 用户套餐类型
    """
    api_url = "http://localhost:9000/api/subscribers"
    auth = ("admin", "your_password")

    # 准备订阅者数据
    subscriber_data = {
        "email": user_email,
        "name": user_name,
        "status": "subscribed",
        "lists": [1],  # Python 技术周报列表 ID
        "tags": ["新用户", user_plan],
        "attribs": {
            "signup_date": "2024-07-01",
            "source": "website_registration",
            "plan": user_plan
        }
    }

    try:
        response = requests.post(
            api_url,
            json=subscriber_data,
            auth=auth,
            headers={"Content-Type": "application/json"}
        )

        if response.status_code == 200:
            result = response.json()
            if result.get("success"):
                print(f"用户 {user_email} 已成功添加到订阅列表")
                return True
            else:
                print(f"添加失败:{result.get('message')}")
                return False
        elif response.status_code == 409:
            # 邮箱已存在,更新订阅者信息
            print("用户已存在,正在更新...")
            return update_subscriber(user_email, user_name, user_plan)
        else:
            print(f"请求失败:{response.status_code}")
            return False

    except requests.exceptions.RequestException as e:
        print(f"网络错误:{e}")
        return False


def update_subscriber(email, name, plan):
    """
    更新已存在的订阅者信息
    """
    # 首先获取订阅者 ID
    get_response = requests.get(
        f"http://localhost:9000/api/subscribers?email={email}",
        auth=("admin", "your_password")
    )

    if get_response.status_code == 200:
        data = get_response.json()
        if data.get("results"):
            subscriber_id = data["results"][0]["id"]

            # 更新订阅者
            update_response = requests.put(
                f"http://localhost:9000/api/subscribers/{subscriber_id}",
                json={
                    "name": name,
                    "tags": ["付费用户"] if plan != "free" else ["免费用户"],
                    "attribs": {"plan": plan}
                },
                auth=("admin", "your_password")
            )

            return update_response.status_code == 200

    return False

场景三:获取统计数据并生成报告

import requests
from datetime import datetime, timedelta
import json

def generate_weekly_report():
    """
    生成周报统计报告,包含发送、打开、点击等核心指标
    """
    auth = ("admin", "your_password")

    # 获取最近一周的活动列表
    week_ago = (datetime.now() - timedelta(days=7)).strftime("%Y-%m-%d")

    campaigns_response = requests.get(
        f"http://localhost:9000/api/campaigns?from={week_ago}&limit=10",
        auth=auth
    )

    if campaigns_response.status_code != 200:
        print("获取活动列表失败")
        return

    campaigns = campaigns_response.json().get("data", [])

    # 汇总统计
    total_sent = 0
    total_opened = 0
    total_clicked = 0

    report_data = []

    for campaign in campaigns:
        campaign_id = campaign["id"]

        # 获取活动统计
        stats_response = requests.get(
            f"http://localhost:9000/api/campaigns/{campaign_id}/stats",
            auth=auth
        )

        if stats_response.status_code == 200:
            stats = stats_response.json().get("data", {})

            sent = stats.get("sent", 0)
            opened = stats.get("views", 0)
            clicked = stats.get("clicks", 0)

            total_sent += sent
            total_opened += opened
            total_clicked += clicked

            open_rate = (opened / sent * 100) if sent > 0 else 0
            click_rate = (clicked / sent * 100) if sent > 0 else 0

            report_data.append({
                "name": campaign["name"],
                "sent": sent,
                "opened": opened,
                "clicked": clicked,
                "open_rate": f"{open_rate:.1f}%",
                "click_rate": f"{click_rate:.1f}%"
            })

    # 生成报告摘要
    summary = {
        "period": f"{week_ago}{datetime.now().strftime('%Y-%m-%d')}",
        "total_sent": total_sent,
        "total_opened": total_opened,
        "total_clicked": total_clicked,
        "overall_open_rate": f"{(total_opened / total_sent * 100):.1f}%" if total_sent > 0 else "0%",
        "overall_click_rate": f"{(total_clicked / total_sent * 100):.1f}%" if total_sent > 0 else "0%",
        "campaigns": report_data
    }

    # 输出 JSON 格式报告
    print(json.dumps(summary, ensure_ascii=False, indent=2))

    return summary

常见使用场景

场景一:技术博客 newsletters

这是 listmonk 最典型的应用场景。通过定期发送高质量的技术内容,建立与读者的长期连接。与社交媒体相比, newsletters 有几个优势:算法不干扰、触达率稳定、品牌记忆更强。

建议的运营节奏是每周一期,雷打不动地固定时间发送(比如每周一上午十点)。内容要保持高信息密度,可以包括原创教程、技术工具推荐、行业资讯、读者问答等板块。

关键指标监控重点应该放在打开率和退订率上。如果打开率持续下降,说明内容质量或发送频率出了问题,需要及时调整。

场景二:电商促销邮件

对于有线上店铺的商家, listmonk 可以用来发送促销活动通知。需要注意的是,促销邮件与内容 newsletters 的风格要有明显区分:前者强调紧迫感和行动号召,后者强调价值和知识分享。

建议配置:

  • 创建独立的“促销列表”,与内容订阅列表分开管理
  • 使用简洁有力的邮件模板,核心信息要在第一时间传达
  • 设置发送频率限制,避免过度打扰导致退订
  • 追踪每次促销的转化效果,持续优化发送策略

场景三:会员服务通知

SaaS 产品可以用 listmonk 发送服务通知,比如功能更新公告、系统维护通知、账单信息等。这类邮件的特点是打开率天然较高,因为用户有关注自身账户的需求。

建议配置:

  • 使用事务性模板,保持简洁专业的风格
  • 设置优先级,确保重要通知能够及时送达
  • 绑定用户属性字段,根据用户类型发送定制化内容
  • 配置退订链接时注意,这类通知邮件通常需要区分“营销邮件退订”和“服务邮件退订”

场景四:社区运营邮件

对于开发者社区或兴趣社群, newsletters 可以用来发布活动预告、嘉宾分享、成员风采等内容。与公众号文章相比,邮件更适合深度内容和私密互动。

建议配置:

  • 设计有社区归属感的邮件模板,包含社群 Logo 和独特的视觉风格
  • 充分利用标签功能,按兴趣方向细分订阅者
  • 定期分析订阅者增长趋势和活跃度,识别核心用户
  • 设置定期的“沉默用户唤醒”机制,对长期不活跃的订阅者发送激励内容

技巧与最佳实践

提升邮件送达率

邮件送达率是 newsletters 运营的生命线。再好的内容,如果进不了收件箱,一切都是白搭。

发件域名配置:务必配置 SPF 、 DKIM 和 DMARC 记录。这些 DNS 记录向邮件服务商证明你的域名授权了 listmonk 的服务器发送邮件,是防止邮件被标记为垃圾邮件的关键。

# SPF 记录示例(添加一条 TXT 记录)
v=spf1 include:sendgrid.net ~all

# DKIM 记录(SendGrid 等服务商会在后台提供具体的 DKIM 配置)

# DMARC 记录示例
v=DMARC1; p=quarantine; rua=mailto:dmarc-reports@your-domain.com; pct=100

发送频率控制:新账户或新域名在邮件服务商那里没有信誉积累,应该从小规模、低频率开始,逐渐建立信誉。建议首周每天发送量不超过一百封,之后根据送达率逐步增加。

邮件内容优化:避免使用垃圾邮件高风险词汇(比如“免费”“立即”“限时优惠”等),减少感叹号和大写字母的使用,图片与文字比例要适当。listmonk 提供了邮件评分功能,可以在上线前检查内容是否存在明显的垃圾邮件特征。

列表清洗:定期清理无效的邮箱地址。硬退回(邮箱不存在)会严重损害发件信誉。建议对连续多次硬退回的邮箱进行标记或自动移除。

优化打开率

邮件被打开是后续转化的前提。以下几个技巧可以有效提升打开率。

标题优化:邮件标题是决定打开率的第一因素。好的标题要简洁明了,能够传递价值,并且引发好奇心。避免标题党式的内容,虽然能带来短期打开率,但会损害用户信任。

好的标题示例:“三行代码让你的 Python 脚本快十倍”

普通的标题示例:“Python 性能优化教程”

发送时间优化:不同受众的最佳阅读时间不同。对于技术类受众,工作日的上午十点到中午十二点通常是黄金时段;对于消费类受众,晚间和周末的打开率可能更高。建议用 A/B 测试找到你特定受众的最佳时间。

listmonk 支持 A/B 测试功能,你可以创建两个标题相同但发送时间不同的活动版本,或者创建两个发送时间相同但标题不同的版本,系统会自动选择表现更好的版本发送给剩余的订阅者。

预览文本优化:预览文本(邮件标题后的第一行摘要)会在很多邮件客户端紧跟在标题后面显示,是一个被很多人忽视但效果显著的展示位置。

提升点击率

点击率衡量的是内容对读者的吸引力和行动号召的有效性。

内容结构清晰:使用明确的标题层级和视觉分隔,让读者能够快速扫视并找到感兴趣的内容。重要信息放在邮件开头,因为很多读者只会浏览前半部分。

行动号召明确:每个邮件应该有明确的行动号召(Call to Action)。按钮要醒目、文字要具体、指向前景明确。不要在一个邮件里放置太多链接或按钮,这会分散注意力。

个性化内容:利用 listmonk 的订阅者属性功能,可以实现基础的个性化内容。比如,根据用户所在地区显示当地的活动信息,或根据订阅者的购买历史推荐相关产品。

<!-- 根据用户类型显示不同内容 -->
{{ if eq (index .Subscriber.Attribs "plan") "premium" }}
    <p>尊贵的付费用户,专属福利在此:</p>
    <a href="/premium-content" class="btn">查看高级内容</a>
{{ else }}
    <p>升级到高级版,解锁更多精彩内容:</p>
    <a href="/upgrade" class="btn">立即升级</a>
{{ end }}

数据备份与恢复

生产环境的数据安全至关重要。以下是推荐的备份策略。

数据库备份:如果是 PostgreSQL,可以使用 pg_dump 进行逻辑备份:

# 每日凌晨三点执行数据库备份
0 3 * * * pg_dump -U listmonk -d listmonk -F c -b -v -f /backup/listmonk-$(date +\%Y\%m\%d).dump

# 保留最近三十天的备份
30 3 * * * find /backup -name "listmonk-*.dump" -mtime +30 -delete

如果是 SQLite,直接备份数据库文件即可:

# 每日备份 SQLite 数据库文件
0 3 * * * cp /path/to/listmonk.db /backup/listmonk-$(date +\%Y%m%d).db

文件备份: listmonk 的 uploads 目录存放用户上传的图片和附件,需要一并备份:

# 备份上传文件
0 3 * * * tar -czf /backup/uploads-$(date +\%Y%m\%d).tar.gz /path/to/listmonk/uploads

恢复测试:定期进行恢复演练,确保备份文件可用。建议每季度至少测试一次完整的恢复流程。

安全加固

修改默认端口: listmonk 默认运行在 9000 端口,建议改为非标准端口以减少自动化扫描工具的探测。

[app]
address = "0.0.0.0:9088"

配置 CORS 策略:如果需要从外部应用调用 API,要正确配置跨域访问策略。

[app]
# 允许访问的域名列表
allowed_origins = ["https://your-frontend-domain.com"]

启用管理员双因素认证:在“设置”的“安全”标签页,可以启用 TOTP 双因素认证,大幅提升管理员账户的安全性。

限制 API 访问:如果只是内部使用,可以考虑只允许本地网络访问 API 端点,通过 Nginx 反向代理添加 IP 白名单。


高级配置与扩展

多语言支持

如果你的订阅者分布在全球各地,可以为不同语言的用户配置不同的模板。listmonk 支持在模板中检测订阅者的语言偏好,然后渲染对应的内容。

{{ if eq .Subscriber.Attribs.lang "en" }}
    <p>Welcome to our newsletter! Here are this week's highlights:</p>
{{ else if eq .Subscriber.Attribs.lang "ja" }}
    <p>ニュースレターへようこそ!今月のハイライトはこちら:</p>
{{ else }}
    <p>欢迎阅读我们的周报!以下是本周精选内容:</p>
{{ end }}

订阅者在订阅页面可以选择语言偏好,这个属性会被保存到订阅者记录中,供后续的模板渲染使用。

自定义退订页面

默认的退订页面可能与你的品牌形象不匹配。listmonk 允许你创建自定义的退订页面模板,提供更好的用户体验。

常见的增强包括:

  • 在用户退订前显示替代选项,比如“减少邮件频率”或“只订阅特定类别”
  • 收集退订原因,帮助你改进内容质量
  • 提供重新订阅的入口,方便用户后续回来

与外部系统集成

listmonk 的 API 是完整的,这意味着几乎可以与任何外部系统集成。以下是几个常见的集成场景。

Zapier / Make 集成:通过 API 将 listmonk 连接到数千个第三方应用。比如,当用户在 Typeform 填写表单时自动添加订阅者,或者当有新的 Stripe 付款时更新订阅者的付费状态。

网站登录集成:如果你的网站有用户系统,可以在用户登录后显示他们当前的订阅状态,允许用户直接在网站上管理订阅偏好。这些操作通过 API 与 listmonk 同步。

数据分析集成:将 listmonk 的统计数据导出到 Google Analytics 或自定义的数据仓库,实现更复杂的用户行为分析。

性能调优

当订阅者数量达到数十万级别时,可能需要对 listmonk 进行性能调优。

数据库优化:PostgreSQL 环境下,定期执行 VACUUM 和 ANALYZE 维护命令可以保持查询性能。可以在 crontab 中添加:

# 每周日凌晨四点执行
0 4 * * 0 psql -U listmonk -d listmonk -c "VACUUM ANALYZE;"

发送速率调整:根据邮件服务商的要求和你的服务器性能,调整并发发送数量。默认值可能不是最优的,需要通过实际测试找到平衡点。

[app]
# 并发发送数量(默认值是 10)
concurrency = 20

# 单个 SMTP 连接的最大邮件数
max_message_grp = 100

缓存配置:对于高流量场景,可以考虑在反向代理层添加缓存,减少对 listmonk 后端的请求压力。


故障排查与常见问题

邮件发送失败

问题表现:活动状态显示“发送中”但长时间没有进度,查看发送日志发现大量失败。

排查步骤

  1. 检查 SMTP 配置是否正确。尝试发送测试邮件,确认能否成功送达。
  2. 查看邮件服务商是否有发送限制或异常告警。SendGrid 、 Amazon SES 等平台都有发送配额和信誉监控机制。
  3. 检查是否是目标邮箱的问题。尝试用不同邮箱服务(如 Gmail、QQ 邮箱、企业邮箱)进行测试。
  4. 查看 listmonk 日志文件,定位具体的错误信息。

订阅者导入失败

问题表现:CSV 或 JSON 导入时部分记录失败,或全部失败。

排查步骤

  1. 检查文件编码。推荐使用 UTF-8 编码的 CSV 文件。
  2. 确认 CSV 表头与 listmonk 要求的字段名一致。可参考帮助文档中的字段映射说明。
  3. 检查邮箱格式是否合法。系统会自动跳过格式错误的邮箱,但大量错误可能导致导入缓慢。
  4. 对于 JSON 格式,确保 JSON 结构完整,没有语法错误。

管理界面加载缓慢

问题表现:仪表板或列表页面加载需要很长时间。

排查步骤

  1. 确认数据库是否正常工作。如果使用 PostgreSQL,检查连接池配置是否合理。
  2. 查看系统资源使用情况。内存不足可能导致频繁的磁盘交换。
  3. 如果订阅者数量很大,检查是否对常用查询建立了索引。某些自定义查询可能需要额外的索引优化。

邮件打开率显示为零

问题表现:明明收到并打开了邮件,但统计数据中打开数为零。

排查步骤

  1. 确认是否在模板中正确添加了追踪像素。listmonk 的追踪像素是隐藏的 1×1 图片,加载路径包含追踪参数。
  2. 检查收件人是否在邮件客户端中默认阻止了图片显示。很多现代邮件客户端默认不加载图片。
  3. 查看浏览器或邮件客户端的开发者工具,确认图片请求是否成功发出。

总结与相关资源

通过这篇教程,我们从零开始详细介绍了 listmonk 这个开源 newsletters 管理系统的使用方法。从环境搭建、基础配置,到创建订阅列表、编写邮件模板,再到发送活动、分析数据,你已经掌握了使用 listmonk 运营 newsletters 的核心技能。

listmonk 的优势在于功能完备、部署简单、性能出色,完全可以满足个人博主、小型团队乃至中型企业的邮件营销需求。它让用户重新掌控自己的订阅者数据和邮件发送渠道,不再依赖昂贵的商业平台。

关键要点回顾

  • Docker 部署是入门最快的方案,一条命令即可启动
  • SMTP 配置是发送邮件的前提,建议使用 SendGrid 或 Amazon SES 等专业服务商
  • 邮件模板设计要遵循邮件兼容性原则,使用表格布局和内联样式
  • 发送前务必进行测试,监控送达率、打开率、点击率等核心指标
  • 定期备份数据和优化配置,确保系统长期稳定运行

如果你对 listmonk 感兴趣,以下是一些进一步学习的资源:

  • GitHub 仓库https://github.com/knadh/listmonk —— 官方项目地址,可以提交 issue 和参与讨论
  • 官方文档https://listmonk.app/docs/ —— 详细的配置说明和 API 参考
  • 演示站点https://demo.listmonk.app/ —— 在线体验 listmonk 的各项功能
  • 社区论坛:GitHub Discussions 中有丰富的使用案例和解决方案

推荐延伸阅读的相关 AI 项目

如果你的 newsletters 系统需要更智能的能力,以下项目可以与 listmonk 配合使用:

  • 向量数据库集成:使用 Milvus 或 Qdrant 存储和检索订阅者画像,实现更精准的内容推荐
  • A/B 测试自动化:集成 Optuna 或其他 AutoML 工具,自动优化邮件标题和发送时间
  • 内容生成辅助:结合 GPT-4 或 Claude 等大语言模型,协助生成邮件内容创意和标题优化
  • 用户行为分析:接入 Umami 或 Plausible 等隐私友好的分析工具,获取更详细的用户行为洞察

邮件营销是一个需要长期运营的领域,好的工具能够让你事半功倍。 listmonk 正是这样一个值得信赖的伙伴,帮助你专注于创作优质内容,而不必担心技术实现的细节。祝你运营成功!


本文档更新于 2024 年 7 月,内容基于 listmonk v3.1.0 版本。如有疑问或发现过时内容,欢迎在评论区讨论。

如果内容对您有帮助,欢迎打赏

您的支持是我继续创作的动力

前往打赏页面

评论区

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注