Stalwart 邮件服务器:如何利用 Sieve 脚本实现 SMTP 发件自动归档
如果你正在使用现代化的 Stalwart Mail Server,你可能已经注意到了一个经典的邮件架构痛点:
当你使用 Thunderbird 或 Outlook 等客户端发信时,已发送的邮件会乖乖躺在“已发送”文件夹里;但当你配置 通知机器人 (如 notification@example.com) 或 ERP 系统通过 SMTP 协议自动发信时,发件箱却空空如也。
为什么会这样? 这是因为标准 SMTP 协议只负责“传输”,不负责“存储”。桌面客户端之所以能保存备份,是因为它们在发送后多做了一步 IMAP 上传操作。而简单的 SMTP 脚本通常不会做这一步。
为了解决这个问题,我们需要在服务端层面进行拦截和归档。本文将介绍一种最稳定、最符合 RFC 标准的解决方案——回环子地址重定向法 (Loopback Sub-addressing Strategy)。
核心原理:回环子地址重定向
在 Stalwart 中,直接在发送阶段使用 fileinto 写入文件往往会因为上下文权限问题导致失败或不可预测的后果。
因此,我们采用一种更聪明的“两段式”策略:
- 出站拦截 (系统级): 当特定用户通过 SMTP 发信时,系统脚本拦截并生成一封副本,将其重定向回发件人自己,但加上一个特殊的标签(如
user+sent@domain)。 - 入站归档 (用户级): 当这封带
+sent标签的副本进入收件箱流程时,用户的个人脚本将其识别,标记为已读,并挪入“已发送”文件夹。
这种方法解耦了发送和存储逻辑,稳定且安全。
第一步:配置系统级出站拦截脚本
我们需要在 Stalwart 的 config.toml 中添加一个 Trusted Script (可信脚本),挂载在 SMTP 的 DATA 阶段。只有这个阶段才能获取完整的邮件内容。
1. 编写脚本逻辑
在 Web 界面中添加脚本,点击 Settings -> Scripting -> System Scripts -> Create script。这段脚本的核心任务是身份验证和防死循环。name 和 description 可以随意填写。下文 name 以 save-sent 为例。
require ["variables", "envelope", "copy", "subaddress"];
# --- 配置区 ---
# 定义需要监控的账号
set "monitor_user" "notification@example.com";
# 定义用于识别的子地址标签
set "sent_tag" "sent";
# --- 核心逻辑 ---
# 1. 安全性检查:验证当前 SMTP 会话的认证用户
# 这一点至关重要!我们只处理通过密码认证为 notification 的会话。
# 如果黑客伪造 From 头但未认证,此脚本不会执行,保证了审计的真实性。
if string :is "${env.authenticated_as}" "${monitor_user}" {
# 2. 防死循环检查 (Loop Prevention)
# 检查这封邮件是否已经是我们生成的副本 (即检查收件人是否包含 +sent)
# 如果不加这一步,副本会无限触发重定向,导致服务器崩溃。
if not envelope :detail :is "to" "${sent_tag}" {
# 3. 执行带副本的重定向
# :copy 确保原始邮件正常发给外部收件人
# 将副本发回给自己,并带上 +sent 标签
redirect :copy "notification+sent@example.com";
}
}技术细节:这里使用了
${env.authenticated_as}变量而不是检查 Header 中的From,这是为了防止发件人欺骗,确保只有通过认证的合法发送才能触发归档。
2. 启用脚本
在 Settings 界面点击 SMTP -> Inbound -> DATA stage,在 Run Script 中填入 "save-sent",点击 Save & Reload 保存并加载配置。
第二步:配置用户级入站归档脚本
现在,服务器会自动给自己发送一封 notification+sent@example.com 的邮件。我们需要告诉 Stalwart 如何处理这封“回旋镖”邮件。
这属于用户个人的 Sieve 过滤规则。你可以通过支持 ManageSieve 的客户端上传,或者管理员直接配置。
用户脚本内容:
require ["fileinto", "mailbox", "subaddress", "imap4flags", "envelope"];
# 检查邮件是否包含 +sent 标签
if envelope :detail :is "to" "sent" {
# 1. 标记为已读
# 已发送的邮件在逻辑上应该是"已读"状态 (\Seen)
setflag "\\Seen";
# 2. 存入"已发送"文件夹
# :create 确保如果文件夹不存在,会自动创建
# 注意:根据你的客户端习惯,这里可能是 "Sent", "Sent Items" 或 "Sent Messages"
fileinto :create "Sent Items";
# 3. 停止处理
# 避免这封邮件继续向下匹配,误入收件箱
stop;
}第三步:检查环境与生效
在应用上述配置之前,请确保你的 Stalwart 环境满足以下两个基础条件:
- 启用子地址 (Sub-addressing): 这是
notification+sent能被识别的关键,这是默认启用的,可以通过 Settings -> SMTP -> RCPT stage -> Sub-addressing 确认。 - 文件夹名称确认: 不同的客户端对已发送文件夹命名不同。建议在脚本中硬编码为你主要使用的名称,Stalwart 默认使用
Sent Items,可以通过 Settings -> Message Store -> Default Folders -> Sent 确认。
总结
通过这套 “回环子地址重定向” 方案,我们不仅完美解决了 Stalwart 中 SMTP 发件归档的问题,还保证了极高的安全性(基于认证身份)和稳定性(基于 RFC 标准)。这对于需要审计追踪的自动化通知系统来说,是目前的最佳实践。