摘要
- RFC 3028 把邮件过滤限制为一套可推理的动作语言;当脚本没有选择会影响投递的动作时,“隐式保留”负责兜底。
keep、fileinto、redirect、discard与拒收只表达解释器边界上的处置决定;真正的存储、远端接收、协议拒绝和最终状态仍由邮件系统完成并举证。
2001 年初,把过滤规则放到服务器端,首先遇到的不是语法问题,而是权力问题。用户希望邮件在到达收件箱之前就能分类、转发或拒收,运营者却不能让每位用户在投递服务器上运行任意程序。若过滤语言拥有循环、函数、变量和外部命令,它就很容易从便利工具变成一个难以约束的执行入口。
RFC 3028 的办法是主动减法。Sieve 没有循环,不能调用外部程序,最初的核心语言也没有变量。测试命令只观察邮件事实,不产生副作用;控制命令选择分支;动作命令才提出处置要求。这种分层不只是让脚本更容易阅读,它把“看见什么”和“改变什么”拆开,也让一个站点能够限制改变的数量和组合。
最值得注意的设计,不是某个强大的命令,而是脚本没有选中命令时会发生什么。
没有动作,不等于可以让邮件消失
过滤规则天然会漏掉情况。发件人可能更换地址,邮件列表可能改变头字段,用户也可能写错条件。若所有分支都落空就意味着丢弃,那么规则遗漏会直接变成数据损失。RFC 3028 因此引入“隐式保留”:只要没有动作取消它,系统就执行未启用过滤时的默认处置,通常是把邮件放进用户的主邮箱。
这个默认动作不是在每条路径末尾额外复制一份邮件。keep、fileinto、redirect 和 discard 都会取消隐式保留。解释器计算的是一组相互作用的动作,而不是先执行显式命令、再无条件补一份安全副本。后来若扩展只产生旁路效果而不改变投递,它也必须明确说明自己是否取消隐式保留。
这也解释了 discard 的真实语义。它听起来像“把一切删除”,但在基础模型里,它的关键作用是静默取消隐式保留。若同一脚本先 fileinto 再 discard,文件夹中的投递仍然存在;discard 只阻止默认邮箱再收到一份。动词的日常含义很绝对,标准里的效果却是关系性的:取消兜底,不抹去所有兼容动作。
每个动作都要把控制权交给别的系统
keep 要求实现执行普通的默认处置。脚本作者不必知道主邮箱叫不叫 INBOX、背后是文件还是其他存储。这种抽象正因为不承诺存储细节才有价值。分支判断成功,不能证明容量足够、权限有效,更不能证明写入已经持久化。
fileinto 给出一个目标文件夹。RFC 3028 建议实现支持它,同时承认有些环境无法提供该能力。文件夹名称因此是交给投递代理的指令,不是文件夹存在或事务提交成功的回执。
redirect 更清楚地暴露边界。它按邮件传输代理的方式替换信封收件人,再把邮件向外发送。解释器能够选择这条路径,本地传输代理能够尝试发送,但远端服务器仍然决定是否接收;远端接收之后发生什么,又属于另一个系统。循环检测同样是实现责任。redirect 开启的是一段新的投递过程,而不是宣布过程已经结束。
discard 必须保持静默,不返回未投递通知。这能避免某些反馈,也让发件人失去可见回执。若运营者需要证明为何邮件消失,只能依靠执行侧在适当留存期限内保存的证据。没有退信,既不证明邮件被保存,也不证明它被转发,更不说明哪条规则命中。
动作组合就是副作用的治理
一封邮件可能命中多个动作,因而组合规则比单个动词更重要。RFC 3028 要求扩展说明自己如何与基础动作互动;站点可以限制动作数量,也可以禁止特定组合;即便脚本两次要求写入同一个邮箱,实现也不应因此重复投递。
这些约束防止放大和矛盾。无限重定向可以制造邮件轰炸;一边投递一边拒收,会让发件人相信消息没有到达,而系统其实保留了一份;把每个动作都当成互不相关的任务,会产生规则作者从未表达过的结果。
RFC 5228 在 2008 年取代 RFC 3028,但保留了隐式保留与基础动作模型,并更清楚地说明错误处理和扩展互动。2023 年的 RFC 9122 又建立 IANA Sieve 动作注册表,把“是否取消隐式保留”“与哪些动作相互作用”直接列成登记字段。注册表记录的是动作契约,不是某次执行成功的证明。
“拒绝”暴露了决定层与传输层混淆的代价
RFC 3028 原有的 reject 扩展会丢弃邮件,并向信封发件人发送邮件处置通知。问题在于,接收系统可能已经先接受了邮件。垃圾邮件若伪造发件人,通知就会打向无辜第三方,形成退信反射。
RFC 5429 后来改变了边界。新增的 ereject 在条件允许时优先于 SMTP 或 LMTP 会话中直接拒绝,而不是先接收再构造通知。它还明确拒收会取消隐式保留,禁止一封邮件执行多次拒收,并不建议把拒收与 keep、fileinto 或 redirect 等投递动作组合。原因不是语法整洁,而是证据诚实:系统不应一边告诉发件人“未投递”,一边又保存或转发邮件。
这段历史说明,“已拒绝”至少可能掩盖三件不同的事实:脚本选择了拒收;执行组件在当时的协议阶段是否有能力拒绝;发件人最终看见的是会话响应、稍后的通知,还是没有任何信号。若状态没有附带层次与证据,它就不是完整事实。
RFC 5804 的 ManageSieve 随后提供上传、校验、列出和激活脚本的管理界面。这个管理面也不应与本文混为一谈。脚本可以语法正确并处于激活状态,却没有处理某封邮件;脚本可以处理邮件并选择动作,动作仍可能未达到目标。管理、决定与结果是三条记录。
来源
- RFC 3028 的 RFC Editor 记录
- RFC 3028 HTML 版
- RFC 3028 文本版
- RFC 5228 的 RFC Editor 记录
- RFC 5228 HTML 版
- RFC 5429 的 RFC Editor 记录
- RFC 5429 HTML 版
- RFC 5804 的 RFC Editor 记录
- RFC 5804 HTML 版
- RFC 9122 的 RFC Editor 记录
- RFC 9122 HTML 版
- IANA Sieve 扩展注册表
- IANA Sieve 动作数据
- Lu Heng:运行代码优先
- Lu Heng:最低初始规范
- Lu Heng:现实层次
Lu Heng 并非 RFC 3028、RFC 5228 或 RFC 5429 的作者,也未为这些标准背书。本文只公开借用其分析视角。
会员简报
档案背景详情
使用相应会员等级登录,即可解锁完整简报与来源注释。
仅限 Strategic Circle
Strategic Circle
所有读者均可浏览。加入并登录后可解锁档案简报。
加入 Strategic Circle仅限 Leadership Alliance
Leadership Alliance
符合条件的 IP 资产所有者和管理层可登录查看 Leadership Alliance 简报。
加入 Leadership Alliance
