近几个月来,各类 Discourse 群组中多次提出改进入站邮件解析的请求。若以用户故事的形式表达,这些请求大致可归类为:
- “我希望在通过邮件回复时,能使用与在网站发帖时相同的 HTML 功能。”
- “我希望能够查看和搜索我们邮件列表中的消息。”
- “我希望通过邮件(包括邮件回复和批量导入)创建的内容,始终格式美观且解析准确。”
我将在下文链接这些请求的真实示例,但目前重要的是要理解:这三项看似“不同”的请求,实际上都在寻求同一个底层目标——更精准的入站邮件解析。
几个月前,我曾发邮件给 @sam,提议启用 Discourse 使用我们公司提供的一项商业 邮件解析 API。Sam 建议在此创建一个探索性帖子,说明集成我们的 API 相较于 Discourse 现有入站邮件解析方案的优势,以及我们的 API 如何作为插件进行集成。
我将详细探讨这两个主题,首先介绍 Discourse 当前邮件解析方案的现状。此外,为了那些过去几年未曾深入思考过邮件解析问题的读者,我也会提供一些关于该问题的背景信息。
本文篇幅较长,但您可以随意跳读。以下内容将涵盖:
- Discourse 当前邮件解析的现状
- 更优邮件解析带来的益处
- 利益相关者用户画像
- FWD:Everyone 邮件解析 API
A. 移除签名和回复
B. HTML 标记规范化
C. 语言支持
D. CSS 样式 - 拟议的集成方案
- API 测试
Discourse 当前邮件解析的现状
Discourse 已具备“通过邮件回复”功能,可将用户的邮件回复转换为论坛主题中的新帖子。该功能的工作流程如下:
- 用户收到一封邮件通知,其中包含其正在关注的论坛主题中的新帖子。
- 用户回复该邮件。
- 该邮件回复被转换为相关论坛主题中的新帖子。
从概念上讲,这是一项极具价值的功能;对许多人而言,这是首选工作流程,也是许多基于邮件列表的社区在考虑迁移至 Discourse 时的必备功能。
但问题在于,当这些邮件回复被转换为论坛帖子时,往往会出现格式缺失或错误,甚至文本丢失的情况。这带来了严重问题,原因如下文所述。
常见问题包括:
- 项目符号无法正确渲染
- 文本之间缺少换行符
- 文本之间出现多余换行符
- 用户撰写的文本被完全删除
当我称这些问题“常见”时,并非指在使用外语或冷门邮件客户端发送邮件时偶尔发生的情况。而是指在使用 Gmail 和 Outlook 等主流客户端以英语发送基本邮件回复时,这些问题也频繁出现。
以下是来自 [Python-Dev] 邮件列表的两个真实用户投诉示例:
https://www.prettyfwd.com/t/Wco-c1ZCR7mUwiww0j6s9w/#message-5
https://www.prettyfwd.com/t/Wco-c1ZCR7mUwiww0j6s9w/#message-36
(Prettyfwd 使用了 FWD:Everyone 邮件解析 API。)
虽然我尚未尝试将现有邮件列表的内容导入 Discourse,但根据经验,无论“邮件回复”功能的错误率如何,在渲染整个邮件线程时的错误率至少会高出十倍。这是因为在移除签名和回复、处理内联回复、应对深度嵌套的标记等方面,复杂度显著增加。
一个真实案例是 Tanya Lattner(LLVM 基金会主席)撰写的 Mailman 到 Discourse 迁移回顾,其在“技术关切”部分暗示了这些问题:
经询问得知,他们真正不满的是大量邮件因过早截断而丢失内容。由于该邮件列表过去 19 年的讨论和文档极具价值,他们认为在彻底解决此问题之前,无法停用 Mailman。
那么,我们如何判断 Discourse 当前邮件解析状态是否“足够好”?我提出以下三步测试标准:
- 用户必须完全信任:若使用“邮件回复”功能,其内容将被准确解析,且效果与通过网页界面发帖无异。
- 论坛管理员必须信任:若启用“邮件回复”,不会引发额外工作和投诉。
- Discourse 员工必须对该功能有足够信心,将其作为首要参与方式积极推广。
除非我们能完全确信上述每项条件均已满足,否则即使“邮件回复”功能存在,其绝大部分潜在效益也无法实现。
这正是当前状况。
换言之,我将现有的邮件解析代码视为一种 80-20 解决方案,但在该情境下,80-20 法则并不真正适用;问题在于,即使例如 80% 的邮件能正确解析,您获得潜在效益的可能性可能连 10% 都不到。
因此,尽管“邮件回复”(及批量邮件导入)功能已存在,用户最终未能获得期望的体验,版主和工作人员却增加了额外工作,社区则流失了宝贵内容和用户增长机会等。
更优邮件解析带来的益处
社交软件的成功程度取决于其满足人类需求的程度。
人们在网络论坛上发帖的原因包括:希望与他人分享知识、影响他人观点、展现自身智慧、体现领域专长、做出有价值的现实贡献等。
而在基于文本的沟通中,实现这些结果的可能性不仅取决于“说了什么”,还取决于“如何说”——即排版方式。
这就是为何会有整本书探讨莎士比亚作品中的空白;这也是为何《纽约时报》比《纽约邮报》更受重视;更是 Facebook 击败 MySpace 的重要原因之一。[1]
当用户撰写的文本因非自身原因而严重格式错乱时,驱动人们使用社交软件的人类需求便无法得到满足。事实上,情况恰恰相反:用户显得愚蠢。
即使未使用“邮件回复”功能的用户,若其所在主题(乃至整个论坛)的其他帖子看起来像“垃圾场”,也会失去权威性和尊重。
利益相关者用户画像
虽然当帖子始终以美观的排版渲染时,所有人都会受益,但以下用户画像可能从更优的入站邮件解析中获益尤为显著:
- 当前是 [Python-Dev] 和 [Django-Dev] 等邮件列表成员的群体,他们充分理解 Discourse 的优势,并乐见社区迁移至 Discourse,但前提是必须能够以与 GNU Mailman、Google Groups 等无差别的方式继续参与。以下是此类请求的真实示例:https://www.prettyfwd.com/t/Wco-c1ZCR7mUwiww0j6s9w/#message-89
- 基于邮件的社区成员,他们通常乐于迁移至 Discourse,但如果其数十年的既有内容能在同一平台内轻松搜索,他们将更加热情。
- 间歇性查看论坛的普通用户。例如,在 Growing Fruit 论坛上,我通过邮件订阅了所有关于种植北美木瓜(pawpaws)的主题。在夏季和秋季,我每天多次访问该论坛以阅读这些主题中持续涌现的新帖子;但在其他月份,主要依靠这些主题的邮件通知来保持参与。
- 仅间歇性使用网页的用户。人们常假设若不经常使用网页,便与“数字鸿沟”有关,但往往并非如此。有许多人既高度聪明又具备技术背景,但由于处于各自领域的顶端,无需定期使用网页。一个现实案例是 Donald Knuth,尽管他是当今顶尖的计算机科学家之一,却并不常规使用网页。每个领域都有类似人物,说服他们分享知识极具价值。根据我的经验,这些人不太可能成为论坛的常规贡献者,但若有人告知他们某个话题正被讨论且他们感兴趣,他们往往会通过邮件订阅并参与这些特定主题。
总体来看,改进入站邮件解析不仅应提升现有活跃 Discourse 贡献者的参与度,还应解锁许多原本希望迁移至该平台但受阻的社区,并吸引那些否则不会贡献的高价值内容。
FWD:Everyone 邮件解析 API
FWD:Everyone 邮件解析 API 实现两项核心功能:
- 准确移除每封邮件中的签名和回复,同时保留对引用文本的内联回复。
- 将邮件客户端生成的极其复杂的 HTML 标记规范化为约 12 种通常允许于用户生成内容网站的 HTML 标签,同时最大程度保留作者的原始意图。
我将分别详细解释这两项功能,但首先请看一段我制作的视频,其中通过真实邮件线程展示该问题:https://www.youtube.com/watch?v=nPb3NQlz6V4
移除签名和回复
FWD:Everyone 邮件解析 API 对纯文本和 HTML 邮件均能以同等精度工作。当存在 HTML 消息部分时,API 优先使用该部分,原因如下:
- 作者选择的 HTML 格式功能(如粗体、斜体、引用块、代码片段等)是其信息的重要组成部分,与文本本身同等重要。
- 当邮件客户端将消息的 HTML 版本转换为纯文本版本时,常出现错误。例如,邮件客户端不仅往往无法在纯文本中渲染 HTML 功能(如项目符号列表),而且 HTML 格式元素内的文本甚至可能完全缺失。
当然,部分用户偏好发送纯文本邮件;因此,纯文本邮件也必须以同等精度移除签名和回复。
FWD:Everyone 邮件解析 API 实现了这一点,包括正确处理纯文本和 HTML 邮件中的内联回复。
在精度方面,任何邮件解析库在移除签名和回复时可能出现两类错误:
- 假阳性(False positives)—— 本应包含在消息中的文本被错误排除。
- 假阴性(False negatives)—— 本不应包含在消息中的文本被错误包含。
由于不同 Discourse 社区(小写 d)使用邮件的方式差异巨大,难以提供精确的精度统计数据。但与 Discourse 当前解析方案相比,现实预期可能为:
- 移除签名和回复的假阳性减少 100 倍
- 移除回复的假阴性减少 10 倍
- 移除签名的假阴性减少 1–10 倍——可能更好,但未必达到一个数量级的提升。
需要说明的是,假阳性通常比假阴性更严重,因为它们歪曲了作者的原文。但假阴性同样非常糟糕,因为它们会让发帖人(以及论坛上的其他人)显得至少不专业,甚至愚蠢。
FWD:Everyone 采取的方法是摒弃任何可能导致假阳性的签名移除技巧;由此可能带来的假阴性增加,主要通过投入大量工作使算法以正当方式运行(无需走捷径)来大幅抵消。
FWD:Everyone 邮件解析 API 通常比 Discourse 当前方案更准确的根本原因在于:我们的 API 专为解析整个邮件线程而设计,这远比解析单次邮件回复帖子困难得多。最终结果是,我们的产品高度“过度工程化”,至少相对于 Discourse 的需求和现有先例而言。
HTML 标记规范化
为使通过邮件提交的回复(及导入的邮件线程)与其他用户生成内容外观一致,它们最终必须使用与用户通过网站回复时相同的 HTML 子集进行渲染。
这出乎意料地复杂。
在 Gmail 和 Outlook 等邮件客户端中撰写的邮件,通常使用约 50 种 HTML 标签、约 25 种 HTML 属性和约 175 种 CSS 样式的某种组合进行编码。此外,此类标记常被严重混淆;您可能期望一段文本的段落如下所示:
<p>Some text!</p>
但实际上,即使是简单段落,也常使用深度嵌套且完全无意义的 div、span、table、list 等组合进行编码。这是移除回复和规范化标记的主要复杂度来源。
无论如何,解析后,每条消息仅使用以下标记进行渲染:
允许的块级元素:<p>, <ul>, <ol>, <li>, <blockquote>, <pre>
允许的 Inline 元素:<code>, <a>, <b>, <i>, <u>, <s>, <span>
注意:
- 除
<a>标签外,唯一允许的属性是'style'和'dir'。 - 唯一允许的 inline 样式是
'font-weight'。 <a>标签还可拥有'href','rel','title'和'target'属性。<span>元素仅在有限情况下使用,以确保字体粗细正确级联。因此,它们总是与 inline'font-weight'一起使用。- 未来,
<img>标签也将用于显示内联图片。
以这种受限的 HTML 子集渲染帖子,使得任何通过邮件提交的帖子都能轻松使用与通过网页界面提交的帖子完全相同的排版进行渲染。
整个过程在最大程度保留作者意图的同时,也确保用户无法添加数十个不必要的段落间换行符。
另见下文“CSS 样式”部分。
语言支持
EmailReplyTrimmer 目前完整或部分支持 13 种语言:
英语、挪威语、法语、德语、葡萄牙语、西班牙语、意大利语、荷兰语、瑞典语、中文、俄语、波兰语、乌克兰语
相比之下,FWD:Everyone 邮件解析 API 目前支持 30 多种语言,包括 Discourse 当前支持的所有语言:
英语、西班牙语、葡萄牙语、加泰罗尼亚语、荷兰语、法语、德语、意大利语、挪威语、丹麦语、瑞典语、芬兰语、俄语、波兰语、乌克兰语、土耳其语、捷克语、罗马尼亚语、匈牙利语、希伯来语、阿拉伯语、波斯语、中文、日语、韩语、印地语、印尼语、泰语、菲律宾语、南非荷兰语
FWD:Everyone 邮件解析 API 完全支持 RTL(从右到左)语言。这意味着不仅阿拉伯语等语言的文本能正确从右向左流动,而且 HTML 标记会应用适当属性,使项目符号等功能在页面正确一侧渲染。
API 有时也能在其他语言中工作,具体取决于所用邮件客户端,但官方支持的语言集至少已针对 Gmail、Outlook 和 Apple Mail 进行测试验证。对于不太流行的邮件客户端,则在其使用量最大的语言中进行明确测试。由于 API 已针对来自公共邮件列表的数千个邮件线程进行测试,因此存在无数针对未知来源的真实世界异常行为的修复。
请注意,支持多种语言不仅是为了显示这些语言的文本。人们常以英语书写,但邮件客户端却配置为使用例如希伯来语。因此,在此类情况下,正确解析英语回复不仅需要完全支持希伯来语,还需普遍支持从右到左的语言。
支持来自多种语系的语言,也有助于确保 Unicode 被正确处理和存储,而非以未来在添加更多非西方语言支持时可能引发问题的方式处理。
CSS 样式
如上所述,我们 API 的关键优势在于其能够以周到且逻辑清晰的方式规范化 HTML 标记。该规范化过程旨在优化文本的可读性和可访问性,同时最大程度保留作者的原始意图。
因此,所有文本仅出现在 inline 或块级元素内(无游离文本),且所有 inline 元素仅出现在块级元素内。这使得文本易于样式化,例如确保不同元素间具有正确的空白间距。
作为其价值的示例,邮件客户端允许用户执行一些荒谬操作,例如在文本行之前或之后直接插入项目符号列表,中间无任何换行符。邮件客户端执行此操作时生成的(极度简化)代码可能如下所示:
<div>
Some text
<div> </div>
<span> • A bullet point</span>
<div> </div>
Some more text
</div>
FWD:Everyone 邮件解析 API 随后将上述标记规范化为如下形式:
<p>Some text</p>
<ul>
<li>A bullet point</li>
</ul>
<p>Some more text</p>
这种规范化标记易于理解和样式化,且在视觉上,项目符号列表前后现在也有了换行符。此类用户友好设计使文本更美观、更易读,同时保留作者意图。这些类型的用户友好设计确保通过邮件提交的高质量内容持续赋予社会地位,而非削弱它。
我们 API 生成的简化、规范化标记还确保:在设计文本样式时,设计师和开发人员只需考虑 API 允许的输出,而无需关心原始邮件的格式。由于 API 的允许输出与 Discourse 网页客户端允许的内容几乎完全相同,这应接近于即插即用解决方案。
拟议的集成方案
“邮件回复”功能将作为插件集成至 Discourse,随后可为所有托管的 Discourse 实例默认启用。
对于未启用该插件的 Discourse 实例,将继续使用现有的邮件解析代码。
此外,若 FWD:Everyone 邮件解析 API 暂时不可用,任何 incoming 消息将使用现有邮件解析代码处理。待 API 恢复在线后,任何自发布以来未通过网页界面编辑过的消息可重新由 API 处理。
该插件也可提供给自托管的 Discourse 实例选择性启用。
对于从现有邮件列表迁移至 Discourse 的群组,邮件列表上的每个邮件线程也可通过 API 解析,但这可能更宜集成至 Discourse 现有的迁移脚本和流程中,而非通过插件实现。[2]
API 测试
API 完全可供任何人测试,但非认证用户的速率限制极低。
对于拥有 Gmail 账户的用户,测试 API 的最便捷方式为:
- 访问 https://www.prettyfwd.com 并安装 G Suite 附加组件。
- 访问 https://api-demo.fwdeveryone.com 并使用客户端 OAuth 与 API 交互。
这两个基于网页的工具与实际 API 的主要区别在于:前者
- 不会处理包含使用 HTML 表格样式的消息的线程
- 不会移除线程中第一条消息的回复(例如,若线程包含超过 100 条消息,Gmail 会将其拆分为多个线程)
要通过代码直接测试 API,提供 Python 和 Ruby 的入门脚本:
相关文档(包括已知问题和产品路线图)如下:
[1] https://www.danah.org/papers/essays/ClassDivisions.html。
[2] 批量导入现有邮件列表内容时,建议先对少数线程进行快速合理性检查,以确保解析正确。某些群组可近乎完美地直接解析,但其他群组可能从几小时的预防性工作大幅受益。例如,某些邮件列表软件需要为每个列表编写少量自定义代码以移除附加在每条消息底部的文本,而其他邮件列表软件则可通过可预测的方式处理,适用于该平台上的任何列表。由于存在此类潜在问题,批量导入过程最好作为受监督迁移的一部分执行,而非通过插件完成。