Discourse 邮件消息的线程不正确

抱歉,下面的一些语气可能有些过激。我之所以显得有点恼火,是因为我确实有点恼火。

Michael Brown 通过 Discourse Meta 于 2022 年 7 月 27 日 14:06 发表:

抱歉,我现在才看到,这里有一些想法,其中一些已经解决……

这里的问题是,Discourse 发送出去的 一条与传入消息不同的消息。它具有不同的元数据(为此目的,收件人/发件人/回复地址/取消订阅/等)和不同的正文(我猜是为每个用户自定义的?邮件列表模式下不这样吗?)。

到底什么是消息?根据 5322:

消息由头部字段组成,可选地后跟消息正文。

“Message-ID:”字段提供了一个唯一的邮件标识符,该标识符引用特定邮件的特定版本
[我的重点]

“特定版本”让我觉得重新发送具有不同 Message-ID 的传入消息是不合适的。不过,如果你将视角从 Discourse 的“论坛软件”转变为 Discourse 的“邮件列表软件”,那么这样做似乎有点道理,所以我明白你的意思。

嗯,不幸的是,这取决于对字面意思的过度解读,也许是解读了不存在的上下文。

_每_封电子邮件在被邮件系统传递时,其头部都会被修改。即使没有其他修改,Received: 头部在每一步都会被添加,并且多个系统会添加各种指示垃圾邮件过滤结果和签名的头部。这些都不会触发 message-id 的修改,实际上这样做会让 message-id 完全失效。

关于内容,如前所述,几乎所有的邮件列表都会在正文中添加内容,通常是一个带有列表管理页面链接或取消订阅链接的页脚。这些也_不会_触发 message-id 的更改。

事实上,几乎没有任何转发消息的操作会改变 message-id。因为这会破坏最终用户客户端的线程和重复检测。

我看到你继续引用了我正要引用的内容 :slight_smile:

5322 还说:

在许多情况下,消息会被“修改”,但这些修改并不构成该消息的新实例,因此消息不会获得新的消息标识符。例如,当消息被引入传输系统时,它们通常会加上额外的头部字段,如跟踪字段(在 3.6.7 节中描述)和重发字段(在 3.6.6 节中描述)。添加这些头部字段不会改变消息的身份,因此保留了原始的“Message-ID:”字段。在所有情况下,决定“Message-ID:”字段是否更改的是消息发送者希望传达的含义(即,这是同一条消息还是不同的消息),而不是消息中出现的任何特定语法差异。

我猜这归结于,当 Discourse 发送出去时,消息的发送者是否改变了?

我认为你在这里误读了。让我强调一下:

在所有情况下,决定“Message-ID:”字段是否更改的是消息发送者希望传达的含义(即,这是同一条消息还是不同的消息)

_发送者_是作者,而不是像 Discourse 这样的 MTA。

如果我通过电子邮件发布到 Discourse,我希望我的消息以其原始状态(语义上讲)到达读者。任何像取消订阅链接这样的附加信息都不会改变我在消息中所说的内容的语义。

这仍然是_同一条消息_。

也许我们应该使用 Resent-Message-ID 和相关字段?

绝对不行。它们是供_用户_重新提交消息的。例如,如果我将一条消息转发给其他人。它们不是为邮件中继(如列表和 Discourse)设计的。

它一直都在,从 822 开始。但正如你后来所说,是的,它已经被更新了。

哎哟。我以为当时只有 USENET。我收回我的话。

5322 还直接谈到了 Discourse 和 Github 的使用方式:

“In-Reply-To:”字段可用于标识新消息所回复的消息,而“References:”字段可用于标识对话的“线程”。

可能有点不恰当,可能是由于缺少合适的“线程标识符”头部。但这可能不是 RFC 作者的意图……它没有处理带有“References”但没有“In-Reply-To”的消息。

对我来说,这两个字段涵盖了相同的信息:

  • References 显示了线性(通常)的线程,一直追溯到原始发帖人。
  • In-Reply-To 显示了父级,并隐含了与前面消息一起追溯到原始发帖人的相同线程。

这其中的棘手之处在于,我们不是发送_一封_电子邮件,而是发送 N 封——每位收件人一封——这样他们的个人元数据(取消订阅等)就可以是正确的。

这并不棘手。消息的语义是相同的,定制是次要的且在语义上无关紧要。它们_不_需要新的或不同的 message-id。

而且是的,我在测试中确实看到了强烈的迹象表明垃圾邮件的判定将与 Message-ID 相关联。如果后来再次看到(同一用户不同用户),它将更有可能被标记为垃圾邮件。

你能举出一些这样的例子吗?因为 message-id 允许在最终用户的端进行去重。并且请记住,许多“反垃圾邮件”措施是错误的垃圾。我曾有多少东西因为完全荒谬的原因被拒绝为潜在垃圾邮件……为了绕过错误的垃圾邮件错误过滤而破坏电子邮件是一个糟糕的选择。

至今我从不抄送有 GMail 地址的人,因为 GMail 的垃圾邮件过滤认识我并会丢弃邮件。如果我只发送给列表,他们就能收到。如果我抄送他们的 GMail 地址,它(a)会将其标记为垃圾邮件,然后(b)_也_会将邮件列表消息标记为垃圾邮件(相同的 message-id!)。最终用户看不到我的消息。这个逻辑完全是荒谬且无法修复的。

说实话,这里的好处完全是为了在某些邮件客户端中正确地对电子邮件进行线程化,而牺牲了可送达性。

叹气。对_所有_电子邮件客户端都是如此。而人们在 Pythonland 地区不愿意使用 Discourse 的一个主要原因是电子邮件方面的线程是坏的。_许多_人根本不使用论坛,因为每个论坛都要求他们_访问_它。电子邮件会发送给他们,他们可以使用_他们_喜欢的阅读器和_他们_喜欢的编辑器,线程化可以让人们清楚地看到讨论的流程。当它起作用的时候。

当前的 topic/#{topic_id}/#{post_id}.s#{sender_user_id}r#{receiver_user_id} 至少让它对用户在他们的邮箱中保持一致。假设

最大的担忧是可送达性——当主要提供商没有任何可见性时,电子邮件的送达已经足够困难了。

我想看看证据。世界各地的邮件列表都能正确地做到这一点。Discourse 绝对且客观地破坏了这一点。我正在努力解决这个问题。

让我重申这里的两个基本问题:

  • 原始发帖人的 In-Reply-ToReferences 引用了一个虚构的“前 OP” “topic” message-id,所以没有电子邮件用户拥有一个以(OP)为起点的线程——_所有_邮件,包括 OP,看起来都像是跟帖。
  • 通过 Discourse 收到的电子邮件和直接收到的电子邮件(例如通过 CC)具有不同的 message-ids,尽管它们在语义上是同一条消息;这破坏了线程和去重。

但我确实看到了一个强有力的论据,即让 Discourse 在邮件列表模式下表现得更像邮件列表软件。@martin 我认为我们在邮件列表模式下不自定义邮件正文吗?你认为在邮件列表模式下采取更严格的方法来保留和重用 Message-IDs 是否有意义?

Pythonland 的一些人发现“邮件列表模式”过于信息量过大。他们希望收到针对特定主题的电子邮件,而不是所有邮件。message-id 的处理方式应该对_所有_电子邮件端都_相同。

我是 discuss.python.org 上的“邮件列表模式”用户。但我在这里(discourse.org)打开了它,然后_立即又关掉了它。我在这里需要针对性模式。

4 个赞

Michael Brown 于 2022 年 7 月 27 日 22:37 通过 Discourse Meta 发布:

啊!我以为我们已经在做:topic/#{topic_id}/#{post_id}.s#{sender_user_id}r#{receiver_user_id}

{receiver_user_id} 会让你为同一个源帖子在每个最终用户那里获得不同的消息 ID。一旦最终用户在 discourse 之外进行通信或通过非 discourse 方式获取副本,这就会产生问题。

我倾向于,为了平衡电子邮件唯一性与可送达性以及邮件列表模式的担忧,在邮件列表模式禁用时使用 (2),在邮件列表模式启用时使用 (3)。

并且在我最近的帖子中提到,邮件列表模式只涵盖了一种类型的 Discourse 电子邮件接收。无论是邮件接收者处于邮件列表模式还是仅仅是电子邮件-用于-某些-主题/标签模式,所有相同的担忧都适用。

同样,对于 References 标头,我倾向于在主题的帖子 #1省略它。

同样,In-Reply-To 标头也不应存在,因为它们必须引用一个虚构的、针对 OP 的消息才能存在。

并引用主题(所以是 topic/#{topic_id})以及它所回复的帖子(如果有的话)。

除非有一个帖子带有那个消息 ID 并以电子邮件形式发出,否则你无法引用“主题”消息 ID。如果你想这样做,请将 OP 的消息 ID 特殊处理为“主题”消息 ID,而不是 ...../1

3 个赞

这应该是“prior-to-OP”。抱歉,Cameron Simpson

正如你所说,正是让我们感到沮丧的问题:

我同意应该改变这一点。OP 的 message-id 应该是(如果没有通过邮件发送的话)(简化后)topic/1,并且不引用其他消息。

即使它只是一个 Discourse 帖子而从未成为电子邮件,message ID 也不会改变。

后续消息可以引用它。

为什么必须存在电子邮件?从语义上讲,只有帖子符合标准。它所回复的消息确实存在,只是不在那个人的电子邮件文件夹中。我们刚刚得出结论,消息才是重要的,无论是帖子正文还是电子邮件正文。由此看来,topic/#{topic_id}/1@site 是一个唯一的 message ID,引用该帖子,无论它是否在电子邮件消息中。

这与收到一封回复电子邮件不同,该回复引用了一封不在你收件箱中的电子邮件。它仍然是一个回复,因此 References 是合法且正确的。

根本上我同意你。我内心的完美主义者希望这是正确的。但需要将电子邮件发送到人们收件箱的实用性导致了这一点。更糟糕的是,大量用户使用 gmail 并且从不训练其过滤器,也从不正确使用它,而是通过报告为垃圾邮件来“取消订阅”[^1]。

[^1]:尽管我们实现了反馈循环,但 gmail 并没有将此报告给我们。

我同意,我认为我们对这句话的理解过于字面化了:

消息标识符精确地对应于特定消息的单个实例化

经过一段时间的思考,我认为我们应该回到以前的状态(删除随机化),并锁定每个帖子的单个 message-id,它应该是:

  • message_id_from_incoming_email || topic/#{topic_id}/#{post_num}@site`(OP 的 post_num 是 1)

而且,每当我们发送电子邮件时,我认为将 References 添加到所有父级直到 OP,并将 In-Reply-To 设置为适当的稳定帖子 message-ID(或回复主题时的 OP),因为消息就是帖子。但 OP 的这些字段应该是空白的,是的。

5 个赞

感谢 @supermathie@cameron-simpson 的回复,我认为我们已经达成了共识。我将把待办事项提取到一篇文章中,并希望很快能开始着手处理这些事情:

  1. 将生成的 Message-ID 格式更改为始终为 \u003cdiscourse/post/:post_id@:hostname\u003e,这足够独特,基本上是恢复我们以前的做法。引用 OP 现在将使用第一个帖子的 ID,而不是仅使用主题 ID。
  2. 如果帖子有一个关联的 IncomingEmail 记录,我们在发送邮件时始终使用该 Message-ID,否则我们使用上述格式生成一个。
  3. 在发送主题的 OP 的邮件时,不要使用 References,因为此时还没有可以 Reference 的内容,因为这是线程中的第一封邮件。
  4. 确保根据 PostReply 记录生成正确的 In-Reply-ToReferences 标头。

这可能会使已发送邮件的线程状态变得有些模糊,但我会尽力在过渡期内支持我们正在放弃的格式。感谢您的耐心!

3 个赞

只是为了澄清……这不会是服务器的 hostname,而是站点的 URL?如果是主机名,那么当 3 个不同的主机提供相同的站点时,我们将失去所有稳定性。

1 个赞

抱歉,是的,我的意思是站点域名,例如 meta.discourse.org,它来自 Email::Sender.host_for(Discourse.base_url),这是我们已经在使用的方法。

2 个赞

好主意,我没想到移动的情况。:post_id 是帖子的 ID (post.id) 还是编号(在主题内)?

如果是帖子 ID,我们可以简化并只使用 \u003cpost/:post_id@:hostname\u003e,因为它永远不会改变,这样我们就无需存储 Message-ID,除非它被覆盖为默认值。

如果不是……我们不妨在这里使用帖子 ID?这部分不必很长,只需确保它是唯一的。

2 个赞

是实际 ID,不是帖子编号。

这是一个很好的观点,\u003cpost/:post_id@:hostname\u003e 可能会很好地工作,并避免了额外的列要求。也许为了让它更具 Discourse 特色,我们可以添加 discourse 作为前缀,例如 \u003cdiscourse/post/543563@meta.discourse.org\u003e(考虑到许多站点的主机名中不会提及 Discourse)。这只是吹毛求疵。

我将尝试思考可能出错的地方。我想如果你将一个帖子移动到另一个主题,然后有人通过电子邮件回复该帖子,他们的回复将进入新主题而不是原始主题。也许这没关系?另一个风险是帖子被移动到一个私有类别,但我认为我们已经面临同样的风险并且我们已经处理了。

只是随便想想,应该没问题,测试更改时我会涵盖这些内容 :+1:

2 个赞

包含 topic_id 的论点是,如果人们将帖子从一个主题拆分到另一个主题,您可以故意中断线程。

我对此持中立态度。可以任选其一。但这将是其理念。

1 个赞

使用帖子的 ID 是因为它更静态,这正是我们想要的,因为如果您将帖子移动到另一个主题,帖子的 ID 在 Message-ID 中将保持不变,但主题将不同。

我想,如果我们最终移动帖子并从新主题发送电子邮件,邮件客户端中的新线程将正确创建,因为 ReferencesIn-Reply-To 标头链将不同。无论如何,我将确保测试这种情况,看看它是否能按预期工作。在各种场景按预期工作之前,不会将任何内容合并到核心。

1 个赞

基于这些进一步的讨论 @cameron-simpson,我已将待办事项更新如下,在此发布以便您收到更新,因为 Discourse 编辑不会通过电子邮件发送:

  1. 将生成的 Message-ID 格式更改为始终为 \u003cdiscourse/post/:post_id@:hostname\u003e,这足够独特,基本上是恢复我们以前的做法。引用 OP 现在将仅使用第一个帖子的 ID,而不是仅使用主题的裸 ID。
  2. 如果帖子有相关的 IncomingEmail 记录,我们始终在发送电子邮件时使用该 Message-ID,否则我们使用上述格式生成一个。
  3. Post 记录添加一个新的 outbound_message_id 列,该列将通过以下方式填充:a) 如果是创建帖子的传入电子邮件的 Message-ID,或 b) 在 Discourse Web UI 创建帖子的情况下,我们生成的传出 Message-ID
  4. 在发送主题 OP 的电子邮件时,不要使用 ReferencesIn-Reply-To 标头,因为还没有可以 Reference 或回复的内容,因为这是该线程的第一封电子邮件。
  5. 确保根据 PostReply 记录生成正确的 In-Reply-ToReferences 标头。
1 个赞

这是否也涵盖了引用(例如:一个帖子引用了 10 个不同的其他帖子,因此它引用了它们?)

1 个赞

By Sam Saffron via Discourse Meta at 29Jul2022 02:31:

Does this cover quotes as well (Eg: a post quoted 10 different other
posts, so it references them?)

In-Reply-To 只能引用一个前驱,所以选择一个。References 可以引用多个,但 RFC 明确建议不要这样做,因为并非所有客户端应用程序都可能期望除 OP 之外的线性链。

我对 References 中的任何一种都可以,但会倾向于保守的一种。易于计算的是:

  • In-Reply-To:使用第一个引用的消息的 message-id(或根据某些策略选择的任何单个引用)
  • References:上面同一单个选定前驱帖子的 References 以及该同一帖子的 message-id

这些将是稳定、可预测且正确的。

此致,
Cameron Simpson cs@cskk.id.au

2 个赞

References 的这种用法是 不被推荐 的:

注意:一些实现会解析“References:”字段来显示“讨论线索”。这些实现假定每条新消息都是对单个父项的回复,因此它们可以通过“References:”字段向后查找以找到其中列出的每个消息的父项。因此,尝试为具有多个父项的回复形成“References:”字段是不被推荐的;本文档未定义如何执行此操作。

2 个赞

By Martin Brennan via Discourse Meta at 29Jul2022 01:57:

Based on these further discussions @cameron-simpson I updated the TODOs
to this, posting them here so you get the update since the Discourse
edits will not arrive via email:

  1. Change the generated Message-ID format to always be <discourse/post/:post_id@:hostname>, this is unique enough, it’s basically reverting to what we used to do. Referring to the OP will just use the first post ID now instead of just the bare topic ID.
  2. If a post has an associated IncomingEmail record, we always use that Message-ID when sending email, otherwise we generate one using the format above.
  3. Do not use a References when sending out emails for the OP of the topic, there is nothing to Reference yet because it is the first email in the thread.

I would also omit the In-Reply-To in the OP emails.

  1. Ensure that correct In-Reply-To and References headers are
    generated based on PostReply records.

Yes.

Personally, I would go the extra step of having a column for the
email-side message-id. That way, once you’ve allocated a message-id for
a post (from the source email if from email, or generated if from the
web interface), it remains stable regardless of whatever else might
happen in the code now or later. i.e. even if there’s no IncomingEmail
the message-id generation happens just the once, rather than being
recomputed (which could thus change).

i.e. make it stable once made by storing it.

You have an IncomingEmail relation by the look of it. Maybe you have
(or could use) an OutgoingEmail relation for the additional state for
the outbound email messages, made the first time a post is forwarded by
email.

I know the flow is basicly that that happens when a post is made, but I
can imagine some later user feature being things like:

  • please forward me emails for this whole topic, now that I’m interested
  • if an edit happens to a post, consider sending the updated message out
    with the same message-id

The reason the second example comes to mind is that we’ve got more
things to report :slight_smile: One is that Discourse seems to do some effort to
drop the quoted part of top posted replies to keep the post concise, or
something like that. I wrote a long post over in Python land a few weeks
back which got severe mistruncated. i went in and edited it on the forum
with the original text from my personal copy. But a receipient said he
had the full thing, and i was wondwering if Discourse sent edit updates
out as replacement messages with the same id. Which would be quite neat
depending on the end user client handling of that.
By Martin Brennan via Discourse Meta at 29Jul2022 01:57:

基于这些进一步的讨论 @cameron-simpson 我更新了待办事项
如下,在此发布以便您获得更新,因为 Discourse
编辑不会通过电子邮件发送:

  1. 将生成的 Message-ID 格式更改为始终为 <discourse/post/:post_id@:hostname>,这足够独特,基本上是恢复我们以前的做法。引用 OP 现在将使用第一个帖子的 ID,而不是仅使用主题 ID。
  2. 如果帖子有关联的 IncomingEmail 记录,我们发送电子邮件时始终使用该 Message-ID,否则我们使用上述格式生成一个。
  3. 在发送主题 OP 的电子邮件时,不要使用 References,因为还没有可以 Reference 的内容,因为这是该线程的第一封电子邮件。

我也会省略 OP 电子邮件中的 In-Reply-To

  1. 确保基于 PostReply 记录生成正确的 In-Reply-ToReferences 标头。

是的。

就我个人而言,我会更进一步,为邮件端的消息 ID 设置一个列。这样,一旦您为帖子分配了消息 ID(如果来自电子邮件,则来自源电子邮件;如果来自 Web 界面,则生成),它将保持稳定,无论代码现在或将来发生什么。即,即使没有 IncomingEmail,消息 ID 的生成也只发生一次,而不是重新计算(因此可能会更改)。

即,通过存储来使其一旦生成就保持稳定。

从您的描述来看,您有一个 IncomingEmail 关系。也许您有(或可以使用)一个 OutgoingEmail 关系来处理出站电子邮件消息的附加状态,该状态在帖子首次通过电子邮件转发时创建。

我知道流程基本上是在帖子发布时发生的,但我可以想象一些稍后的用户功能,例如:

  • 请转发我关于整个主题的电子邮件,我现在感兴趣了
  • 如果帖子被编辑,请考虑使用相同的消息 ID 发送更新后的消息

第二个例子之所以浮现在脑海中,是因为我们还有更多事情要报告 :slight_smile: 其中之一是 Discourse 似乎在努力删除顶部回复中引用的部分,以保持帖子简洁,或者类似的东西。几周前我在 Python 领域写了一篇很长的帖子,结果被严重截断了。我进入论坛并在那里编辑了它,使用了我个人副本中的原始文本。但一位收件人说他收到了完整的内容,我当时想知道 Discourse 是否会以相同的 ID 将编辑更新作为替换消息发送出去。这可能相当不错,具体取决于最终用户的客户端如何处理。

1 个赞

By Martin Brennan via Discourse Meta at 29Jul2022 00:36:

  1. Add a new outbound_message_id to the Post table, so we can be
    sure the thread survives even if a post moves topics or anything like
    that, store the Message-ID here for both of the above cases.

是的,我认为这很重要,无论如何实现(关系或列)。我想我在你修改的待办事项中提到过。

  1. When sending out emails for the OP of the topic, do not use a References header. There is nothing to Reference yet because it is the first email in the thread.
  2. Ensure that correct In-Reply-To and References headers are generated based on PostReply records and the new outbound_message_id column on the Post table.

This has the potential to leave things in a bit of a murky state thread-wise for already sent emails, but I will try my best to allow for the format we are moving away from for a changover period as well. Thanks for bearing with us!

现有的邮件我们无能为力。向前看,确保线程正常!

谢谢!
Cameron Simpson cs@cskk.id.au

1 个赞

我们确实有 EmailLog,但这些记录每 90 天会被清理一次,我认为它不适合这个。我将只这样做:

1 个赞

原本是为了完全避免存储……但现在我想到了,帖子 ID 永远不会改变,但主机名可能会改变。所以我们应该在所有情况下保存后立即存储它。

messageid 成为每个帖子的属性,永远不可变,这应该不会有什么坏处……

这会不会是消息的不同版本?根据规范:

“Message-ID:” 字段提供了一个唯一的邮件标识符,该标识符引用特定邮件的特定版本……邮件标识符仅与特定邮件的一个版本相关;邮件的后续修订每个都会获得新的邮件标识符。

所以我们生成的 message-id 应该是:\u003cdiscourse/post/:post_id/rev/:revision_num\u003e(可能为第一个修订版省略 /rev/:revision_num)。考虑到

1 个赞

是的,我会这么做。至于其他关于编辑和修订的讨论,我认为那是另一大堆棘手的问题,我们现在不应该去触及……让我们先解决我们帖子串的问题吧 :slight_smile:

1 个赞