# Stalwart SMTP 发件自动归档


如果你正在使用现代化的 **Stalwart Mail Server**，你可能已经注意到了一个经典的邮件架构痛点：

当你使用 Thunderbird 或 Outlook 等客户端发信时，已发送的邮件会乖乖躺在“已发送”文件夹里；但当你配置 **通知机器人 (如 `notification@example.com`)** 或 ERP 系统通过 **SMTP** 协议自动发信时，发件箱却空空如也。

**为什么会这样？**
这是因为标准 SMTP 协议只负责“传输”，不负责“存储”。桌面客户端之所以能保存备份，是因为它们在发送后多做了一步 IMAP 上传操作。而简单的 SMTP 脚本通常不会做这一步。

为了解决这个问题，我们需要在服务端层面进行拦截和归档。本文将介绍一种最稳定、最符合 RFC 标准的解决方案——**回环子地址重定向法 (Loopback Sub-addressing Strategy)**。

## 核心原理：回环子地址重定向

在 Stalwart 中，直接在发送阶段使用 `fileinto` 写入文件往往会因为上下文权限问题导致失败或不可预测的后果。

所以我们采用一种更聪明的“两段式”策略：

1.  **出站拦截 (系统级)：** 当特定用户通过 SMTP 发信时，系统脚本拦截并生成一封**副本**，将其重定向回发件人自己，但加上一个特殊的标签（如 `user+sent@domain`）。
2.  **入站归档 (用户级)：** 当这封带 `+sent` 标签的副本进入收件箱流程时，用户的个人脚本将其识别，标记为已读，并挪入“已发送”文件夹。

这种方法解耦了发送和存储逻辑，稳定且安全。

## 1. 配置系统级出站拦截脚本

我们需要在 Stalwart 的 `config.toml` 中添加一个 **Trusted Script (可信脚本)**，挂载在 SMTP 的 `DATA` 阶段。只有这个阶段才能获取完整的邮件内容。

### I. 编写脚本逻辑

在 Web 界面中添加脚本，点击 **Settings -> Scripting -> System Scripts -> Create script**。这段脚本的核心任务是**身份验证**和**防死循环**。name 和 description 可以随意填写。下文 name 以 `save-sent` 为例。

```sieve
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`，这是为了防止发件人欺骗，确保只有通过认证的合法发送才能触发归档。

### II. 启用脚本

在 Settings 界面点击 **SMTP -> Inbound -> DATA stage**，在 **Run Script** 中填入 `"save-sent"`，点击 **Save & Reload** 保存并加载配置。

## 2. 配置用户级入站归档脚本

现在，服务器会自动给自己发送一封 `notification+sent@example.com` 的邮件。我们需要告诉 Stalwart 如何处理这封“回旋镖”邮件。

这属于用户个人的 Sieve 过滤规则。你可以通过支持 ManageSieve 的客户端上传，或者管理员直接配置。

**用户脚本内容：**

```sieve
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;
}
```

## 3. 检查环境与生效

在应用上述配置之前，请确保你的 Stalwart 环境满足以下两个基础条件：

1.  **启用子地址 (Sub-addressing)：** 这是 `notification+sent` 能被识别的关键，这是默认启用的，可以通过 **Settings -> SMTP -> RCPT stage -> Sub-addressing** 确认。
2.  **文件夹名称确认：** 不同的客户端对已发送文件夹命名不同。建议在脚本中硬编码为你主要使用的名称，Stalwart 默认使用 `Sent Items`，可以通过 **Settings -> Message Store -> Default Folders -> Sent** 确认。


---

> 作者: Nite  
> URL: https://www.nite07.com/zh-cn/posts/stalwart-save-sent/  

