Discourse 邮件消息的线程不正确

在 discuss python org 上,我们正在讨论 Discourse 的电子邮件方面。最大的抱怨是缺乏线程。我对邮件头进行了一些研究,发现:

  • Message-ID 邮件头至少是唯一的
  • Reply-ToReferences 邮件头 指向其他邮件的 Message-ID,更不用说它们所回复邮件的邮件 ID 了
  • 它们反而指向一个基于主题编号的虚构邮件 ID

这意味着使用电子邮件的人会看到(a)完全扁平的非线程化讨论,以及(b)根消息似乎丢失了,因为 In-Reply-ToReferences 邮件头指向一个实际上从未出现在任何消息中的邮件 ID。

这是糟糕的,并且违反了 RFC 5322。这使得电子邮件体验远不如它本应有的那样好。

例如,那里有一个主题,其第一条消息具有以下邮件头:

Message-ID: <topic/17208.dc83577b18fc3ecc438ed42a@discuss.python.org>
References: <topic/17208@discuss.python.org>

这是第一条消息。它不应该有 References 邮件头,因为没有任何消息具有该 ID。

第二条消息具有以下邮件头:

Message-ID: <topic/17208/60568.898edf234f56cf6f3a661c1a@discuss.python.org>
In-Reply-To: <topic/17208@discuss.python.org>
References: <topic/17208@discuss.python.org>

同样,Message-ID 是可以的,但 In-Reply-ToReferences 完全没有意义。

应该 很容易修复。第一条消息不应同时具有 In-Reply-ToReferences 邮件头。第二条消息应在 In-Reply-ToReferences 邮件头中包含第一条消息的 Message-ID

有关详细信息,请参阅 RFC5322 第 3.6.4 节:

目前,电子邮件用户看到的是扁平的、无结构的讨论。通过这些修复,他们可以获得易于理解的、有意义的线程显示。

9 个赞

如果有人感兴趣,卡梅隆提到的讨论存档可以在 https://mail.python.org/archives/list/python-dev@python.org/message/VHFLDK43DSSLHACT67X4QA3UZU73WYYJ/ 找到。

2 个赞

这似乎是一个回归,请参阅旧主题和修复程序

1 个赞

我只是在查看 HEAD 和那个修复之间的差异。

在我看来,即使没有先行者,当前版本也总是设置 Referencestopic_canonical_reference_id 用作后备。我仍然认为这是错误的,因为没有 ID 为该 ID 的电子邮件消息。

In-Reply-To 稍微正确一些,因为它仅在 post.post_number!=1 时设置,但它仍然回退到 topic_canonical_reference_id

@message.header['In-Reply-To'] = referenced_post_message_ids[0] || topic_canonical_reference_id

在我看来,这似乎有两个问题:

  • 如果没有 referenced_post_message_ids,后备应该是 #1 帖子的 Message-ID,而不是 topic_canonical_reference_id
  • receipt-of-reply-emails 代码中的某些内容一定是在删除回复邮件的 In-Reply-To 标头,因为它们应该正确地填充了 referenced_post_message_ids 数组(列表?我对 Ruby 是新手)
3 个赞

Cameron,感谢您就此话题展开讨论并在帖子中提供了大量详细信息。我对此“麻烦事”负有责任,源于这两次提交:

我们已经注意到像 Thunderbird 这样的邮件客户端在邮件主题方面存在一些问题一段时间了,但由于使用 Discourse 进行邮件主题的用户数量不多,所以一直被搁置。但现在这个问题浮出水面,我们需要花些时间重新审视这个问题并着手解决。

有趣的是,我们当时为发送的第一封邮件以及之后的所有邮件都添加了此 References 标头,因为它能使 Gmail 中的邮件主题正常工作。但我同意这并非理想状态,并且可能与后续邮件的 In-Reply-ToReferences 标头未使用原始 Message-ID 一起导致了邮件主题问题。

请耐心等待,我将查看旧的讨论和代码并着手解决。在此期间,您是否知道其他正在使用并且遇到问题的邮件客户端?例如,我知道这是 Thunderbird 中的一个问题,但其他客户端呢?谢谢。

7 个赞

写了一封很长的回复,但收到了以下信息:

抱歉,您发送给
["incoming+8349bd9eb1f2b582df4f32dbe85c3363@meta.discoursemail.com"]
(标题为 Re: [Discourse Meta] [bug] Discourse email messages are
incorrectly threaded)的电子邮件未能成功发送。

原因:
抱歉,新用户只能在帖子中添加 2 个链接。
如果可以修正问题,请重试。

我将把它发到论坛上,这样我就可以捕捉和修改了……

1 个赞

Cameron,感谢您开启这个话题并提供
您帖子中的大量细节。我对此“一团糟”负有责任,
源于这两个提交:

3b13f1146b2a406238c50d6b45bc9aa721094f46

这看起来没问题。它是否将此 ID 与数据库记录一起保存,以便入站回复可以与前一个论坛消息关联?

另外,您希望我验证后缀在语法上是否符合 RFC5322,就允许的字符而言?

82cb67e67b83c444f068fd6b3006d8396803454f

第二个提交似乎解决了我们看到的另一个问题:如果一篇文章来自电子邮件,发送给电子邮件用户的出站消息 ID 不是作者源消息的消息 ID。这导致邮件客户端认为有两个不同的消息,并且可能会破坏对原始消息的回复,而不是对论坛发送的副本的回复。例如:

收件人:论坛
抄送:其中一位参与者

该参与者将(嗯,可能会)收到论坛的一份副本和作者的一份直接副本,并且由于它们具有不同的消息 ID,因此在他们的终端上,这些将是不同的消息。

我本来打算在解决 in-reply-to 和 references 标头问题后,再就此问题提交第二个 bug 报告,因为那个问题更为重要。

我们已经注意到电子邮件客户端(如 Thunderbird)中的一些关于线程的问题有一段时间了,但它并没有代表 Discourse 的大量电子邮件线程消费者,所以我们一直将其推迟,但现在这个问题浮出水面,我们需要花一些时间重新审视这个问题并着手解决。

我和几个人使用 mutt。我很乐意尽我所能协助调试和审查代码。我以前也曾担任过多年的邮件系统管理员。

[quote=“Cameron Simpson, post:1, topic:233499,
username:cameron-simpson”]
这是第一条消息。它不应该有 References 标头,因为没有任何消息具有该 ID。
[/quote]

有趣的是,我们当时向第一封发送的电子邮件以及之后的所有后续电子邮件添加了这个 References 标头,因为它使 Gmail 中的线程能够正常工作,

我认为正确的 References 标头(在第一条帖子中缺失,就像回复中的 in-reply-to 一样)也应该有效。但 Gmail 有时与邮件标准的关系相当松散。我有一个 Gmail 账户;我也可以在那里进行一些调试。原则上,我们可以使用这次讨论本身作为测试平台,也许吧。

但我同意这并非理想,并且很可能导致线程问题
以及在后续电子邮件
In-Reply-ToReferences 标头中未使用原始 Message-ID

请耐心等待,我将查看旧的讨论和代码并解决这个问题。

没关系。

同时,您是否知道其他正在使用并且遇到问题的电子邮件客户端?例如,我知道这是 Thunderbird 中的一个问题,但其他客户端呢?谢谢。

绝对是 mutt。至少使用 mutt 可以很容易地查看标头,并且还可以查看回复树链,这在其他客户端中经常被隐藏。

邮件线程完全由 Message-IDIn-Reply-To 标头定义。References 标头起源于 USENET 的后续帖子,并且(在那里)支持多个消息 ID;In-Reply-To 只支持一个。看起来 References 现在也存在于 RFC5322 中,我将检查其语义。

5 个赞

我只是在整理我的想法,稍后会发一篇关于此事的长文,感谢您到目前为止提供的额外信息!

1 个赞

好的,这有点大,请耐心等待。首先,感谢您又一次详细的回复和调试/审查的提议,这真的很有帮助 :+1: 我今天早上一直在研究这个问题,令人惊讶的是,在 Thunderbird 中,线程在大多数情况下都能正常工作,我认为 References 标头始终指向 OP 有所帮助(例如,此链中的主题“Reference”始终存在,为 \u003ctopic/53@discoursehosted.martin-brennan.com\u003e)。

线程未按预期工作的场景是:

  1. 在 Discourse 中创建一个帖子,然后向关注该主题的人发送电子邮件然后
  2. 另一个人回复该帖子,然后向关注该主题的人发送电子邮件

对于第二封电子邮件,它会收到不正确的 In-Reply-ToReferences 标头,因为它在此处生成了一个标头 discourse/lib/email/sender.rb at 98bacbd2c6b9fe57167cd32af5eb4839b4a5d1f6 · discourse/discourse · GitHub Message-ID。在截图中,以下模式的消息应该放置在此处:

image

答案是——这取决于。如果帖子是从传入电子邮件在 Discourse 中创建的,例如您的这封,当有人回复它时,我们会使用该帖子原始传入的 Message-ID 作为 In-Reply-ToReferences 标头,如下所示:

否则,我们只是使用主题 OP 的引用,并生成一个新的引用,这显然是导致所有问题的原因。在所有情况下,我们每次发送出站电子邮件时都会生成一个新的 Message-ID,这似乎是正确的,并且与其他邮件客户端一致。

我想我明白了你的意思,它是这样进行的吗?

  1. cameron 从 mutt 发送电子邮件到 Discourse,该邮件获得 Message-ID: 74398756983476983@mail.com
  2. Discourse 创建一个帖子,并将 Message-IDIncomingEmail 记录一起存储在帖子中
  3. johndoe 正在关注该主题,因此 Discourse 向他们发送一封电子邮件,其中包含 Message-ID: topic/222/44@discourse.com,并且没有引用原始的 Message-ID: 74398756983476983@mail.com

听起来对吗?我们应该只是将该 Message-ID “传递”给关注该主题的人,而不是生成我们自己的,因为它已经唯一了?那么,如果 cameron 也将他抄送给了原始出站消息呢?这听起来确实是一个单独的问题,因此最好为此打开另一个 bug 主题。

我将在本地设置一个 mutt 客户端,看看你们也看到了什么,我从未在基于文本的客户端(仅 Gmail 和 Thunderbird)中测试过此功能,所以我很想看看它是什么样的。


我今天早上解决这些问题的思路是放弃我们在发送电子邮件 Message-ID 标头时生成的随机后缀,而是改为使用发送者和接收者用户的 user_id 的方案。这样做的好处是无需将 Message-ID 存储在任何地方(除非传入电子邮件创建帖子),因此 ReferencesIn-Reply-To 标头将始终保持一致。让我举个例子。假设我们有这些用户:

  • martin - user_id 25
  • cameron - user_id 44
  • sam - user_id 78
  • bob - user_id 999

然后我们有一个主题,topic_id 233499,帖子从 post_id 100 开始作为 OP。格式将变为 topic/#{topic_id}/#{post_id}.s#{sender_user_id}r#{receiver_user_id}。操作顺序如下:

  1. martin 创建 OP
  • cameron 收到一封包含以下标头的电子邮件:
    • Message-ID: topic/233499.s25r44@meta.discourse.org
    • References: topic/233499@meta.discourse.org
  • sam 收到一封包含以下标头的电子邮件:
    • Message-ID: topic/233499.s25r78@meta.discourse.org
    • References: topic/233499@meta.discourse.org
  1. cameron 通过电子邮件回复
  • discourse 从 mutt 收到一封包含以下标头的电子邮件:
    • Message-ID: 43585349859734@test.com
    • References: topic/233499@meta.discourse.org topic/233499.s25r44@meta.discourse.org
    • In-Reply-To: topic/233499.s25r44@meta.discourse.org
  1. discourse(作为 cameron,来自上面的电子邮件)创建帖子 101
  • sam 从 discourse 收到一封包含以下标头的电子邮件:
    • Message-ID: topic/233499/101.s44r78@meta.discourse.org
    • References: 43585349859734@test.com topic/233499@meta.discourse.org
    • In-Reply-To: 43585349859734@test.com
  1. sam 通过电子邮件回复 cameron
  • discourse 从 gmail 收到一封包含以下标头的电子邮件:
    • Message-ID: 5346564746574@gmail.com
    • References: topic/233499/101.s44r78@meta.discourse.org topic/233499@meta.discourse.org
    • In-Reply-To: topic/233499/101.s44r78@meta.discourse.org
  1. discourse(作为 sam,来自上面的电子邮件)创建帖子 102
  • cameron 从 discourse 收到一封包含以下标头的电子邮件:
    • Message-ID: topic/233499/102.s78r44@meta.discourse.org
    • References: 5346564746574@gmail.com topic/233499@meta.discourse.org
    • In-Reply-To: 5346564746574@gmail.com
  1. bob 在主题中创建帖子 103,不是回复任何人(请注意,这里的 References 包括发送给两个用户的 OP 电子邮件的 Message-ID
  • cameron 收到一封包含以下标头的电子邮件:
    • Message-ID: topic/233499/103.s999r44@meta.discourse.org
    • References: topic/233500@meta.discourse.org topic/23499.s25r44@meta.discourse.org
  • sam 收到一封包含以下标头的电子邮件:
    • Message-ID: topic/233499/103.s999r78@meta.discourse.org
    • References: topic/233499@meta.discourse.org topic/23499.s25r78@meta.discourse.org
  1. cameron 通过电子邮件回复
  • discourse 从 mutt 收到一封包含以下标头的电子邮件:
    • Message-ID: 6759850728742572@test.com
    • References: topic/233499@meta.discourse.org topic/233499/103.s999r44@meta.discourse.org
    • In-Reply-To: topic/233499/103.s999r44@meta.discourse.org

cameron 的收件箱

  • martin - 主题 OP
    • 发送 - > 收件人:discourse,回复:主题 OP
      • sam - 回复第二个帖子
    • bob - 主题中的回复,不是针对任何特定帖子
      • 发送 - > 收件人:discourse,回复:bob 的帖子

sam 的收件箱

*martin - 主题 OP

  • cameron - 第二个帖子
    • 发送 - > 收件人:discourse,回复:第二个帖子
  • bob - 主题中的回复,不是针对任何特定帖子

我认为这是正确的,你能帮我检查一下我写在这些标头中的内容,并确认这是否是您在此场景中所期望的吗?我唯一有点不确定的地方是我是否涵盖了所有的 References,当然,在推出之前,我会在开发分支上对实际电子邮件进行测试。我也还没有在 mutt 中测试过任何东西。


顺便说一句,我还查看了 GitHub 如何处理他们的通知电子邮件,并注意到他们做了类似的事情,他们有一个始终存在的 Referencediscourse/discourse/pull/252@github.com),该引用用于与该“主题”相关的所有电子邮件,在本例中是 GitHub 拉取请求:

References: <discourse/discourse/pull/252@github.com> <discourse/discourse/pull/252/issue_event/7042100517@github.com>
In-Reply-To: <discourse/discourse/pull/252/issue_event/7042100517@github.com>
6 个赞

作者:Martin Brennan,来源:Discourse Meta,时间:2022 年 7 月 22 日 06:34

好的,这件事相当重要,请耐心听我说。首先,感谢
您提供如此详尽的回复以及调试/审查的提议,这真的
非常有帮助 :+1: 今天早上我实际上一直在研究这个问题,
令人惊讶的是,在统一视图中,Thunderbird 中的线程功能在大多数情况下都能正常工作,我认为 consistently 指向原始发帖人(OP)的 References 头有助于实现这一点(例如,此链中始终存在的主题 Reference
<topic/53@discoursehosted.martin-brennan.com>)。

我刚刚仔细重读了 RFC5322 第 3.6.4 节。它已不同于早期版本(822 和 2822),并合并了电子邮件 In-Reply-To 头、USENET References 头以及现代引用多条先前消息的回复功能。

简要总结:

  • Message-ID 是单条消息的唯一持久标识符
  • In-Reply-To 包含该消息直接回复的所有消息 ID,因此如果我回复两条消息,它将包含这两个消息 ID
  • References 是从原始发帖人(OP)到前一条消息的回复链中的前置消息 ID。因此,它确实应该始终以 OP 的消息 ID 开头。

因此,对于像这样的讨论,假设标签就是消息 ID:

OP
  -> reply1
    -> reply2 ---+
  -> reply3      |
    -> reply4    |
      -> reply5 <+

reply5 将包含:

  • message-id=reply5
  • in-reply-to=“reply2 reply4”
  • references=“OP reply3 reply4”

在 references 中包含 “reply1 reply2”(通往 reply5 的另一条链)也是合法的,但 RFC 明确建议不要这样做,因为某些客户端期望 references 是单一的线性回复链,而不是某种扁平化的有向图。

因此,我建议在构建 references 时,使用“主要”前置消息的 references,并附加该主要前置消息的消息 ID。这样,您总能得到一个顺序正确的线性链。

有趣的是,那里似乎有一些线程结构。

但请注意:顶部的帖子有一个小的“是回复”箭头。尽管它是第 1 号帖子。我想这是因为“主题”references 条目,这让 TB 认为存在一条更早的消息(当然实际上并没有)。

在 mutt 环境中,我们几乎看不到任何线程结构:

23Jul2022 06:24 Olha via Discus - ┌>[Py] [Users] I need an advise  discuss-users 5.7K
22Jul2022 17:12 Paul Jurczak vi - ├>[Py] [Users] I need an advise  discuss-users 5.5K
22Jul2022 13:21 Rob via Discuss - ├>[Py] [Users] I need an advise  discuss-users 6.8K
22Jul2022 12:53 vasi-h via Disc - ├>[Py] [Users] I need an advise  discuss-users 5.5K
22Jul2022 11:38 Cameron Simpson - ├>[Py] [Users] I need an advise  discuss-users  14K
22Jul2022 10:27 Rob via Discuss - ├>[Py] [Users] I need an advise  discuss-users 6.6K
22Jul2022 06:14 vasi-h via Disc r ┴>[Py] [Users] I need an advise  discuss-users 6.5K

这是因为每条消息的 In-Reply-To 都直接指向虚构的“主题”消息 ID。Mutt 可能会忽略 References,因为它是邮件阅读器,而 References 起源于 USENET 新闻。也许 Thunderbird 正在使用 references,或者用 references 信息增强 in-reply-to。

您只需要查阅 In-Reply-ToReferences 其中之一即可实现线程化;前者来自电子邮件,后者来自 USENET。您同时支持两者(这很好!),因此我们需要使它们保持一致。

(题外话:关于 USENET 镜像也有讨论,因为几位 Python 用户通过 USENET 接口订阅列表。同样,这是一个独立的话题。)

[…]

[quote=“Cameron Simpson, post:8, topic:233499,
username:cameron-simpson”]
这看起来不错。它是否将此 ID 与数据库记录一起保存,以便将传入的
回复绑定到前置的论坛消息?
[/quote]

答案是——视情况而定。如果帖子是通过传入电子邮件在 Discourse 中创建的,例如您 这一封,当有人回复它时,我们会使用该帖子原始的传入 Message-ID 作为 In-Reply-ToReferences 头,如下所示:

discourse/lib/email/sender.rb at 98bacbd2c6b9fe57167cd32af5eb4839b4a5d1f6 · discourse/discourse · GitHub

否则,我们只是使用主题 OP 引用并生成一个新的引用,这显然就是导致所有问题的原因。在所有情况下,每次发送传出电子邮件时,我们都会生成一个新的 Message-ID,这似乎是正确的,并与其他邮件客户端保持一致。

唉,并不完全如此。如果您是消息的起源(即在 Discourse 中撰写),生成消息 ID 是可以的。如果没有消息 ID(这是非法的),生成一个则是标准做法(通常由 MTA 完成)。但如果您是在转发一条消息(在电子邮件中撰写),则应保留现有的消息 ID。

在我看来,您需要做三件事:

  1. 拥有一个稳定的消息 ID,不要替换传入消息的消息 ID
  2. 生成正确的 In-Reply-To,这很容易从直接的前置消息(即前置消息的 Message-ID)计算得出
  3. 生成正确的 References,这很容易计算为前置消息的 References + 前置消息的 Message-ID

针对第 1 点,查看您引用的代码,您可能会希望电子邮件消息 ID 为(Python 风格语法,抱歉):

def message_id(post):
    return post.incoming_email.message_id or discourse_message_id(post)

即:如果帖子源自电子邮件,则使用其电子邮件消息 ID;否则,使用类似您在消息后面概述的算法生成 Discourse 消息 ID:任何 (a) 稳定且 (b) 语法有效的内容。

然后计算 In-Reply-ToReferences 字段就是第 2 点和第 3 点中所述的简单机械操作。

我想我明白您的意思了,是不是这样:

  1. cameron 从 mutt 向 Discourse 发送电子邮件,获得 Message-ID: 74398756983476983@mail.com
  2. Discourse 创建一个帖子,并将 Message-IDIncomingEmail 记录一起存储在帖子中

正确。

  1. johndoe 正在关注该主题,因此他们会收到来自 Discourse 的电子邮件,其中包含 Message-ID: topic/222/44@discourse.com,且没有对原始 Message-ID: 74398756983476983@mail.com 的引用

不。您确实需要将 IncomingEmail.message_id 作为发送给 johndoe 的电子邮件中的 Message-ID 传递过去。这是同一条消息。

这听起来正确吗?我们应该直接将那个 Message-ID “传递”给关注该主题的用户,而不是生成我们自己的,因为它已经是唯一的了?那么,如果 cameron 也在原始传出消息中抄送了他,johndoe 的邮件客户端会发生什么?这听起来确实像是一个独立的问题,所以最好为此开启一个新的 bug 话题。

通过传递它,原始消息(cameron->cc:johndoe)和 Discourse 转发的消息(cameron->Discourse->johndoe)具有相同的消息 ID 和相同的消息内容。接收邮件系统会存储这两条消息。邮件阅读器会看到这两条消息,并选择同时展示两者或只保留一条(这是邮件阅读器的策略决定——只保留一条很常见)。因为它们是完全相同的消息,通常保留哪一条都无所谓。

如果我们忽略 Discourse,考虑一条消息,它既通过列表副本发送,也通过直接电子邮件发送。它们是同一条消息,具有相同的消息 ID。

我会在本地设置一个 mutt 客户端,看看您看到的也是什么情况。我从未在基于文本的客户端中测试过此功能(仅在 Gmail 和 Thunderbird 中测试过),所以我很想看看它看起来如何。

很乐意协助设置。对于线程视图,您需要将排序设置为线程化。Mutt 的可配置性非常高。

我今天早上解决这些问题的思路是,放弃我们在发送 Message-ID 头时生成的随机后缀,转而采用一种使用发送者和接收者 user_id 的方案。这样做的好处是,不需要在任何地方存储 Message-ID(除非传入电子邮件创建帖子),因此 ReferencesIn-Reply-To 头将始终一致。

是的,这样好多了。需要注意的是,传入电子邮件的消息 ID 应覆盖传出电子邮件中由 Discourse 生成的消息 ID。

(大多数邮件系统使用随机字符串,因为缺乏像 Discourse 主题消息结构这样的上下文环境——消息被视为独立的个体;但唯一真正的要求是持久的唯一性。)

让我举个例子。假设我们有这些用户:

  • martin - user_id 25
  • cameron - user_id 44
  • sam - user_id 78
  • bob - user_id 999

然后我们有一个主题,topic_id 233499,帖子从 post_id 100 开始作为 OP。格式将变为 topic/#{topic_id}/#{post_id}.s#{sender_user_id}r#{receiver_user_id}

操作顺序如下:

  1. martin 创建 OP
  • cameron 收到带有以下头的电子邮件:
    • Message-ID: topic/233499.s25r44@meta.discourse.org
    • References: topic/233499@meta.discourse.org
  • sam 收到带有以下头的电子邮件:
    • Message-ID: topic/233499.s25r78@meta.discourse.org
    • References: topic/233499@meta.discourse.org
  1. OP 中不应References 头。它对于线程化是不必要的,并且实际上假装存在某个不存在的“第 0 号帖子”。这意味着每个 OP (a) 看起来像一条回复(而它实际上不是),以及 (b) 看起来像是回复的对象在阅读器收件箱中缺失了。

  2. 这为 OP 的每个传出副本生成了不同的消息 ID。这很糟糕。它们必须是相同的。假设 sam 在回复中直接抄送了 cameronIn-Reply-To 将引用 cameron 从未收到过的消息 ID。

您可以直接从消息 ID 字段中删除 sender_user_idreceiver_user_id,从而获得一个每个接收者都能看到的单一唯一 ID。

唯一性约束是帖子本身,而不是单个电子邮件层面的“消息”对象。

关于 References,OP 不应包含它。TB 和其他一切都会正常运作。如果它们使用 References 而不是 In-Reply-To 进行线程化,回复消息中的 References 就足够了。

这是 Mutt 中邮件列表讨论线程的开头:

16Jul2022 01:09 Rob Boehne      - │├>[Python-Dev] Re: [SPAM] Re: Swit python-dev 9.2K
16Jul2022 01:33 Peter Wang      - │├>                                 python-dev 3.0K
16Jul2022 00:24 Skip Montanaro  - ├>[Python-Dev] Re: Switching to Dis python-dev 4.2K
16Jul2022 04:49 Erlend Egeberg  - ├>[Python-Dev] Re: Switching to Dis python-dev  10K
16Jul2022 04:20 Mariatta        - ├>[Python-Dev] Re: Switching to Dis python-dev  10K
15Jul2022 21:18 Petr Viktorin   - [Python-Dev] Switching to Discourse python-dev 4.2K

忽略我按最新邮件在顶部排序的习惯。看到初始帖子(在底部)没有箭头了吗?该消息没有 References,也没有 In-Reply-To。所有其他消息都有 In-Reply-To(可能还有 References,但这是一个电子邮件邮件列表,所以不一定;正如我之前提到的,它们是互补的。)

如果我重复我之前的 Discourse 示例:

23Jul2022 06:24 Olha via Discus - ┌>[Py] [Users] I need an advise  discuss-users 5.7K
22Jul2022 17:12 Paul Jurczak vi - ├>[Py] [Users] I need an advise  discuss-users 5.5K
22Jul2022 13:21 Rob via Discuss - ├>[Py] [Users] I need an advise  discuss-users 6.8K
22Jul2022 12:53 vasi-h via Disc - ├>[Py] [Users] I need an advise  discuss-users 5.5K
22Jul2022 11:38 Cameron Simpson - ├>[Py] [Users] I need an advise  discuss-users  14K
22Jul2022 10:27 Rob via Discuss - ├>[Py] [Users] I need an advise  discuss-users 6.6K
22Jul2022 06:14 vasi-h via Disc r ┴>[Py] [Users] I need an advise  discuss-users 6.5K

看到它们全部都有一个前导箭头吗?这是因为邮件客户端认为它们都是对某个共同的(且缺失的)根消息的回复,这是由于 References 头中的“主题”消息 ID 造成的。而实际上,第 1 号帖子是上面显示的底部消息。

总结:

  • 您的计划很好,前提是您从消息 ID 中删除发送者和接收者——它们是不必要的,事实上接收者会导致麻烦(发送者只是多余的)。
  • References 中删除“主题”伪消息 ID——它会误导邮件客户端(包括 TB,即使视觉上不明显)
  1. cameron 通过电子邮件回复
  • discourse 收到来自 mutt 的带有以下头的电子邮件:
    • Message-ID: 43585349859734@test.com
    • References: topic/233499@meta.discourse.org topic/233499.s25r44@meta.discourse.org
    • In-Reply-To: topic/233499.s25r44@meta.discourse.org

是的,同样要注意,不应有“主题”引用。正如预期的那样,它引用了 OP 的消息 ID。尽管它应该是 sam 看到的 OP 的相同消息 ID。

  1. discourse(作为来自上述电子邮件的 cameron)创建帖子 101
  • sam 收到来自 discourse 的带有以下头的电子邮件:
    • Message-ID: topic/233499/101.s44r78@meta.discourse.org
    • References: 43585349859734@test.com topic/233499@meta.discourse.org
    • In-Reply-To: 43585349859734@test.com

这里出错了。Message-ID 应该是来自 .incoming_post.message_id 字段的 43585349859734@test.com。(好吧,在我看来这是 post.message_id(),对于电子邮件生成的帖子,它返回 post.incoming_post.message_id,对于其他 Discourse 生成的帖子则返回 Discourse 生成的 ID)。

考虑一下:我撰写并发送我的回复,消息 ID 为 43585349859734@test.com。为了连续性,我在本地文件夹中保留了一份副本,其中显示为对 OP 的回复。理想情况下,Discourse 也会发送我帖子副本给我(这是许多邮件列表的策略设置),所以我也收到了 Discourse 的版本。这应该具有相同的消息 ID,因为它是同一条消息,只是通过不同的途径。

Discourse 的消息不是“回复”我的消息。它就是我的消息,只是被转发了。

这种影响会延续到您随后的示例中。实际过程应该比您所做的更简单。

这样想。如果我通过电子邮件回复帖子,这实际上就像我通过 Discourse 给 sam(和其他人)发邮件一样。Discourse 将我的消息转发给接收电子邮件的订阅者,并“顺便”在论坛上保留一份副本 :slight_smile:

作为附注,我还研究了 GitHub 如何处理他们的通知电子邮件,注意到他们做了类似的事情,即他们有一个始终存在的 Referencediscourse/discourse/pull/252@github.com),用于所有与该“主题”相关的电子邮件,在这种情况下是一个 GitHub 拉取请求:

References: <discourse/discourse/pull/252@github.com> <discourse/discourse/pull/252/issue_event/7042100517@github.com>
In-Reply-To: <discourse/discourse/pull/252/issue_event/7042100517@github.com>

哦,GitHub。他们的议题电子邮件真是个灾难 :slight_smile:

然而,在他们的场景中,PR 就是 OP。所以直接引用拉取请求是合理的。您可以对第 1 号帖子使用“主题”消息 ID,前提是您不要同时也使用“主题/1”ID。但这似乎没什么意义——为第 1 号帖子做特殊处理需要额外的精力——我自己会直接使用“主题/1”。

增加一些复杂性。据我了解,管理员可以移动帖子或主题。这难道不会破坏“生成消息 ID”的方案吗,特别是如果他们只移动一个帖子时?我倾向于认为每个帖子都应该有一个 _message_id 字段,从传入消息(来自电子邮件)填充,或者生成(通过 Discourse 发帖)。这样它就是持久的、稳定的,并且能够抵御帖子的任何重新排序或算法变更。

最后,还有一个小的安全考虑:如果传入的电子邮件消息 ID 声称是现有帖子的消息 ID,您应该忽略它(并可能退回该消息)。因为作为作者,我可以在该头中放入任何我喜欢的内容 :slight_smile: 我会选择直接丢弃消息 ID——接受帖子,但不要让它谎称是其他帖子——给您的副本赋予 Discourse 生成的 ID,然后按正常流程继续。

7 个赞

非常感谢您再次提供如此深入的回复。我可能需要一些时间来消化这些信息并将其转化为可行的项目,所以请耐心等待(此外,我目前还有一些高优先级的内部项目正在进行中)。我认为有了这些信息,我们将能够使我们的线程系统更加健壮并符合规范。在深入研究您的帖子时,我可能会有更多问题,谢谢 Cameron。

2 个赞

通过 Discourse Meta 的 Martin Brennan 于 2022 年 7 月 25 日 00:28 发布:

再次感谢您如此深入的回复。我可能需要一些时间来处理并将其转化为可行的项目,所以请耐心等待(此外,我目前还在处理一些其他高优先级的内部项目)。我认为有了这些信息,我们将能够使我们的主题系统更加健壮并符合规范。在仔细阅读您的帖子时,我可能会有更多问题,谢谢 Cameron。

好的。祝好,Cameron Simpson

1 个赞

顺便说一句,我注意到您这篇后续帖子有以下标题:

Message-ID: <topic/233499/1137586.d14eea2849d76c355ec214fb@meta.discourse.org>
In-Reply-To: <YttEVzlTh/ymDSPT@cskk.homeip.net>
References: <topic/233499@meta.discourse.org>
      <YttEVzlTh/ymDSPT@cskk.homeip.net>

也就是说,它保留了我原始邮件的消息 ID。所以 In-Reply-To 是正确的,而 References 至少包含了我的邮件消息 ID。

这并不是我们在 discuss.python.org 上观察到的情况。

祝好,
Cameron Simpson

1 个赞

啊,这是一个有趣的观察,我没注意到那个小箭头。

这也非常有趣。我相信(在不检查源代码的情况下)Thunderbird 确实是这样做的,Gmail 的用户界面也很可能如此,因为它做了同样的事情。

我们似乎确实在这样做,但我想不是一致的?基本上,我们需要确保:

  • TODO #1 - 如果帖子有相关的 IncomingEmail 记录,我们在发送电子邮件时始终使用该 Message-ID。
  • TODO #2 - 在发送与主题 OP 相关的电子邮件时,请勿使用 References @cameron-simpson 有一个问题——如果 OP 是通过入站电子邮件创建的,我们是否会在 References 中使用该 Message-ID 作为 OP,还是仍然排除它?

这很有趣,我以为每封电子邮件的收件人都必须有一个唯一的 Message-ID?事实上,我认为这就是我们选择为每个收件人的 Message-ID 添加唯一性的原因,以避免垃圾邮件行为,回顾我们内部的主题。也许 @supermathie,他是我们基础设施团队的一员,并且在今年早些时候进行了大量的电子邮件测试,他也可以对此发表意见?

你的意思是,帖子才是决定所有收件人单个 Message-ID 的事物。那么我们是否为每个生成电子邮件的帖子生成一个?然后我们也可以将 IncomingEmail.message_id 移到这里。初步来看,我们需要进行的更改是:

  • TODO #3 - 向 Post 表添加一个 outbound_message_id。在与帖子相关的电子邮件首次发送时生成一次。将其用于后续的 ReferencesIn-Reply-To 标头。当帖子由 IncomingEmail 创建时设置其值。格式应为 topic/:topic_id/:post_id/:random_alphanumeric_string@host,例如 topic/233499/33545/gvy8475y7c45y87554c@meta.discourse.org

在此更改后,我的第一个示例将变成这样:

  1. martin 创建 OP
  • cameron 收到一封带有以下标头的电子邮件:
    • Message-ID: topic/233499/33545/gvy8475y7c45y87554c@meta.discourse.org
  • sam 收到一封带有以下标头的电子邮件:
    • Message-ID: topic/233499/33545/gvy8475y7c45y87554c@meta.discourse.org

同时考虑到 OP 没有特殊处理,它将不再是 topic/:topic_id@hostname 的格式。

  • TODO #4 - 确保根据 PostReply 记录和 Post 表上新的 outbound_message_id 列生成正确的 In-Reply-To 和 References 标头

我认为我们已经考虑了这一点,我会再次检查。

看起来确实是这样 :sweat_smile:


Cameron,你能确认这里的 TODO 听起来合理吗?现在看来确实不多了。我也想知道,当我开始这项工作时,你是否愿意加入一个测试 Discourse 实例,其中部署了 WIP 更改,以便我们可以来回发送电子邮件并测试事情是否正常工作?当然,在让你参与之前,我会自己进行测试。

如果不方便,那也没关系——我有 Thunderbird,并且会设置 mutt,我可以在那里测试所有内容 :slight_smile:

1 个赞

@cameron-simpson 我想在这里澄清的一件事是“message_id”的作用域。
引起这场争论的起因是 @supermathie 强烈怀疑我们不唯一的 message_id 导致了问题。
Discourse 会为它发送的每封电子邮件生成唯一的用户邮件。所以,举个例子,假设有 2 个用户正在关注这个话题:

  • 用户 1 收到载荷 1,其中包含一个针对用户 1 的独特退订链接
  • 用户 2 收到载荷 2,其中包含一个针对用户 2 的独特退订链接
    如果在这两种情况下,我们的 message id 都是 discourse_topic_100/23(即 topic_id/post_number),那么我们将告诉 MTA(邮件传输代理)discourse_topic_100/23 可以是 2 个不同的载荷,假设它们将此视为垃圾邮件信号。

嘿 Discourse… 你刚发送了两封名为 discourse_topic_100/23 的电子邮件,是怎么回事?

由于 Discourse 控制所有电子邮件传输,并且电子邮件不会像传统邮件列表那样添加到密送或抄送列表中,因此我们可以拥有干净的、针对每个用户的退订链接。
你对此有什么看法?使用 discourse_topic_100/23/7333(例如,topic_id、post_number、user_id)作为邮件的唯一标识符,这是一个简单的更改,它肯定是一个唯一的载荷,并且在为用户生成邮件时我们可以轻松地引用它。

1 个赞

作者:Martin Brennan,来源:Discourse Meta,时间:2022 年 7 月 26 日 00:27:

这也非常有趣。我相信(在不查看源代码的情况下)Thunderbird 确实是这样做的,Gmail 界面可能也是如此,因为它也做了同样的事情。

我认为 Mutt 会同时使用两者,但如果存在 In-Reply-To,它可能只会使用它,然后_回退_到 References。我需要检查一下源代码。

使用 References 时,你至少知道到原始帖子(OP)的完整链条;而使用 In-Reply-To 时,你基本上需要周围的前序消息来将它们拼接在一起。对于邮件列表,我通常会将整个线程本地保存直到结束,我想这也是普遍做法。

我们似乎确实在这样做,但可能不够一致?基本上,我们需要确保:

  • TODO #1 - 如果帖子关联了 IncomingEmail 记录,我们在发送电子邮件时始终使用该 Message-ID。

是的。这就是为什么我认为最合理的做法是拥有一个显式的消息 ID 字段,并一次性填充它。然后无论后续代码中生成消息 ID 的流程如何变化,始终使用它。

  • TODO #2 - 在发送与主题 OP 相关的电子邮件时,不要使用 References

是的。OP 没有前序消息,因此没有 ReferencesIn-Reply-To

@cameron-simpson 不过有个问题——如果 OP 是通过入站邮件创建的,我们是在 OP 的 References 中使用该 Message-ID,还是仍然将其排除?

仍然排除。但将其用作 OP 的持久消息 ID。

因此,通过电子邮件撰写的消息(OP 或回复)其消息 ID 来自该电子邮件。通过网页撰写的消息则在用户点击“提交”时由 Discourse 生成。从那时起,无论以何种方式创建,该消息 ID 就是固定的。

[quote=“Cameron Simpson, post:11, topic:233499, username:cameron-simpson”]
增加一些复杂性。据我了解,管理员可以移动帖子或主题。这难道不会破坏“生成消息 ID

3 个赞

我认为即使存在细微差异,您仍然可以使用相同的 message-id 发送不同的消息。

普通的邮件列表在不同程度上都会这样做。至少,总会发生一些报头处理。但消息正文有时也会被修改。一个典型的例子是 python-list,它会丢弃非文本附件。尽管如此,消息仍然会以相同的 message-id 通过。而且几乎所有的列表都会在底部添加一个附加信息,比如指向列表管理页面或取消订阅的链接。这在消息到达时是不存在的。

关于内容签名也曾有过长期的讨论,围绕着签名应该涵盖哪些内容。

因此,我完全赞成您添加特定收件人的取消订阅链接并保留原始 message-id。这样做的好处远远大于如果您为每条消息副本提供单独的 message-id 所带来的线程丢失。

再次考虑电子邮件用户。我可以回复一个 discourse 消息,并添加一个抄送给感兴趣的外部人员。也许他们会从 discourse 收到一份副本,也许不会。但如果他们收到了,即使有您附加的附加信息,它也应该带有源 message-id。否则,他们将收到我消息的 2 份副本,但他们的邮件系统不知道它们是同一条消息的副本。这会导致问题。

简而言之:我不认为您那微不足道的附加取消订阅文本值得使用不同的 message-id。只保留一个。

4 个赞

抱歉,我刚跟上进度,这里有一些想法,其中一些已经解决……

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

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

消息由报头字段组成,后面可以选择跟着消息正文。

“Message-ID:”字段提供了一个唯一的消息标识符,该标识符引用特定消息的特定版本

[我的重点]

正是“特定版本”让我认为重新发送具有不同消息 ID 的传入消息是不合适的。不过,如果你从 Discourse 是“论坛软件”的观点转变为 Discourse 是“邮件列表软件”的观点,那么这样做在某种程度上是有意义的,所以我理解你的想法。5322 还说:

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

我想这归结为,当 Discourse 发送消息时,消息的发送者是否改变了?

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

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

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

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

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

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

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

老实说,这里的最大好处是能够在某些邮件客户端中正确地将电子邮件线程化,但代价是可送达性。

当前的 topic/#{topic_id}/#{post_id}.s#{sender_user_id}r#{receiver_user_id} 至少在用户在收件箱中时使其保持一致。假设

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

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

5 个赞

我不想陷入“完美是足够好的敌人”的境地。

我们现在在消息中使用“随机后缀”,这无疑会带来痛苦。

我们有 3 个选择:

  1. 无法追溯的随机消息 ID
  2. 每个主题/帖子/用户的稳定消息 ID
  3. 每个主题/帖子对的稳定消息 ID

我们目前处于(1)状态,这造成了混乱。

我担心我们会在(2)和(3)之间陷入决策瘫痪。

也许我们先从(2)开始,承认向 Discourse 发送的电子邮件添加额外的抄送可能会导致意外行为,至少可以停止这里的大部分痛苦?

4 个赞

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

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

同样,对于 References 标头,我倾向于在主题的帖子 #1 中将其省略,并引用该主题(所以是 topic/#{topic_id})以及它所回复的帖子(如果有的话)。

3 个赞