Discourse のメールメッセージが正しくスレッド化されていません

discuss python org では、Discourse のメール機能について議論しています。最大の不満は、スレッド表示がないことです。ヘッダーを少し調べたところ、以下のことがわかりました。

  • Message-ID ヘッダーは少なくとも一意です。
  • Reply-To および References ヘッダーは、他のメッセージの Message-ID を参照していません。ましてや、返信対象のメッセージのメッセージ ID を参照しているわけでもありません。
  • 代わりに、トピック番号に基づいた架空のメッセージ ID を参照しています。

これは、メールユーザーが (a) 全くフラットでスレッド表示されない議論を目にし、(b) In-Reply-To および References ヘッダーが実際にはどのメッセージにも存在しないメッセージ ID を参照しているため、ルートメッセージが見当たらないことを意味します。

これは悪いことであり、RFC 5322 に違反しています。そして、メールでの体験を本来あり得るよりもはるかに悪いものにしています。

例として、最初のメッセージに以下のヘッダーを持つスレッドがあります。

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

これは最初のメッセージです。どこにもその ID を持つメッセージがないため、References ヘッダーを持つべきではありません。

2 番目のメッセージには、次のヘッダーがあります。

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-To および References は完全に無意味です。

これは修正が容易なはずです。最初のメッセージは In-Reply-ToReferences ヘッダーも持つべきではありません。2 番目のメッセージは、最初のメッセージの Message-IDIn-Reply-To および References ヘッダーに持つべきです。

詳細については、RFC5322 セクション 3.6.4 を参照してください。

現状では、メールユーザーはフラットで構造化されていない議論を目にします。これらの修正により、わかりやすく見やすいスレッド表示が可能になります。

「いいね!」 9

もし興味のある方がいらっしゃれば、キャメロンが言及している議論のアーカイブは、https://mail.python.org/archives/list/python-dev@python.org/message/VHFLDK43DSSLHACT67X4QA3UZU73WYYJ/ にあります。

「いいね!」 2

それはリグレッションのようですね。この古いトピックと修正を参照してください。

「いいね!」 1

HEAD とその修正との差分を確認しています。

topic_canonical_reference_id がフォールバックとして使用されている場合でも、先行するものがなくても、現在の設定では常に References が設定されるようです。その 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

これは私の目には 2 つの問題があるようです。

  • フォールバックは、referenced_post_message_ids がない場合は topic_canonical_reference_id ではなく、ポスト #1Message-ID であるべきです。
  • 返信メールの In-Reply-To ヘッダーを削除している receipt-of-reply-emails コードのどこかに問題があるはずです。なぜなら、それらは referenced_post_message_ids 配列(「リスト」? Ruby は初心者です)を正しく設定しているはずだからです。
「いいね!」 3

キャメロンさん、このトピックを議論のために開いていただき、投稿で多くの詳細を提供していただきありがとうございます。これらの2つのコミットから、この厄介な問題の責任者です。

私たちはしばらくの間、Thunderbirdのようなメールクライアントでスレッディングに関するいくつかの問題があることを認識していましたが、Discourseからのメールスレッディングの消費者はそれほど多くなかったため、延期されていました。しかし、これが明らかになった今、私たちは問題の再検討と修正に取り組むために時間を費やす必要があります。

興味深いことに、Gmailでスレッディングが正しく機能するため、最初の送信メールとそれに続くすべてのメールにこのReferencesヘッダーを追加しましたが、理想的ではないことは認めます。また、後続のメールのIn-Reply-ToおよびReferencesヘッダーで元のMessage-IDを使用していないことも、スレッディングの問題を引き起こしている可能性があります。

古い議論やコードを調べ、この問題を解決していく間、しばらくお待ちください。その間、使用中で問題が発生している他のメールクライアントについて認識していますか?例えば、これがThunderbirdの問題であることは知っていますが、他に何かありますか?ありがとうございます。

「いいね!」 7

長い返信を書きましたが、以下のエラーが発生しました。

申し訳ありませんが、["incoming+8349bd9eb1f2b582df4f32dbe85c3363@meta.discoursemail.com"] へのメールメッセージ(件名: Re: [Discourse Meta] [bug] Discourse email messages are incorrectly threaded)は機能しませんでした。

理由:
申し訳ありませんが、新規ユーザーは投稿に2つのリンクしか含めることができません。
問題を修正できる場合は、もう一度お試しください。

フォーラムに移動して修正します…

「いいね!」 1

Cameron、このトピックを議論のために開いていただき、投稿で多くの詳細を提供していただきありがとうございます。私は、これらの2つのコミットから、この厄介な問題の責任者です。

3b13f1146b2a406238c50d6b45bc9aa721094f46

これは問題なさそうです。このIDをDBレコードに保存して、受信した返信を元のフォーラムメッセージに関連付けることができますか?

また、サフィックスがRFC5322の構文的に合法であること、つまり許可される文字に関して、私が検証することを望みますか?

82cb67e67b83c444f068fd6b3006d8396803454f

この2番目のコミットは、私たちが経験した別の問題に対処しているようです。投稿がメールから送信された場合、メールユーザーに送信されるアウトバウンドメッセージIDは、作成者のソースメッセージのメッセージIDではありません。これは、メールクライアントの観点から2つの異なるメッセージにつながり、おそらく元のメッセージへの返信と、フォーラムから送信されたコピーへの返信を壊します。たとえば:

To: the forum
CC: one of the participants

参加者は、フォーラムからのコピーと作成者からの直接のコピーを受け取るでしょう(おそらく)、そしてこれらは異なるメッセージIDを持つため、彼らの側では別々のメッセージになります。

私は、この問題について2番目のバグレポートを作成するつもりでした。これは、in-reply-toとreferencesヘッダーの問題を解決した後です。これははるかに重要です。

私たちは、Thunderbirdのようなメールクライアントでしばらくの間、メールスレッディングに関するいくつかの問題に気づいていましたが、Discourseからのメールスレッディングの消費者はそれほど多くなかったため、延期されていました。しかし、これが明らかになった今、私たちはこの問題を見直し、修正に取り組むために時間を費やす必要があります。

私は、そして他の数人もmuttを使用しています。このデバッグとコードレビューを支援するために必要なことは何でも喜んで行います。また、過去の生活で長年メールシステムの管理者をしていました。

[quote=“Cameron Simpson, post:1, topic:233499,
username:cameron-simpson”]
これは最初のメッセージです。Referencesヘッダーはありません。なぜなら、そのIDを持つメッセージはどこにも存在しないからです。
[/quote]

興味深いことに、私たちはこのReferencesヘッダーを最初の送信メールと、それ以降のすべてのメールに追加しました。なぜなら、これによりGmailでスレッディングが正しく機能するからです。

正しいReferencesヘッダー(最初の投稿では欠落しており、返信ではin-reply-toと同様)も機能するはずだと思います。しかし、Gmailは時々メール標準に対してかなり緩い関係を持っています。私はGmailアカウントを持っています。そこでもデバッグを行うことができます。そして原則として、私たちはこの議論自体をテストベッドとして使用できるかもしれません。

しかし、それが理想的ではなく、スレッディングの問題を引き起こしている可能性が高いことは同意します

元のMessage-IDを後続のメールのIn-Reply-ToおよびReferencesヘッダーで使用していないことと並んで。

その間、古い議論とコードを調べ、この問題を解決していきますので、しばらくお待ちください。

心配いりません。

その間、他のメールクライアントが使用されており、問題が発生していることに気づいていますか?たとえば、Thunderbirdでこれが問題であることは知っていますが、他のクライアントはどうですか?ありがとうございます。

間違いなくmuttです。少なくともmuttでは、ヘッダーを表示し、返信ツリーチェーンを表示することが非常に簡単です。これは他のクライアントではしばしば隠されています。

メールスレッディングは、Message-IDおよびIn-Reply-Toヘッダーによって完全に定義されます。ReferencesヘッダーはUSENETでフォローアップのために始まり、(そこでは)複数のメッセージIDをサポートしていました。In-Reply-Toは1つだけをサポートします。ReferencesもRFC5322に現在存在しているように見えます。そして、そのセマンティクスを調査します。

「いいね!」 5

後で投稿する予定の大きな投稿のために、考えをまとめているところです。今のところ追加情報ありがとうございます!

「いいね!」 1

わかりました、これはかなり大きな問題です。しばらくお付き合いください。まず、もう一つの詳細な返信とデバッグ/レビューの申し出、本当に助かります :+1: 実は今朝これを調べていたのですが、驚くべきことに、Thunderbird ではほとんどの場合、統合ビューでのスレッディングが機能しており、References ヘッダーが OP を一貫して指していることが役立っていると思います (たとえば、このチェーンのトピック Reference は常に \u003ctopic/53@discoursehosted.martin-brennan.com\u003e です)。

意図したとおりにスレッディングが機能しないケースは次のとおりです。

  1. Discourse 内で投稿が作成され、トピックを監視しているユーザーにメールが送信される。その後
  2. 他の誰かがその投稿に返信し、トピックを監視しているユーザーにメールが送信される。

2 番目のメールの場合、この行 discourse/lib/email/sender.rb at 98bacbd2c6b9fe57167cd32af5eb4839b4a5d1f6 · discourse/discourse · GitHub で新しいものが生成されるため、In-Reply-To および References ヘッダーが正しくありません。最初に送信されたメールの Message-ID を使用する必要があります。スクリーンショットでは、このパターンに従うメッセージはここに配置されるべきです。

image

答えは「場合による」です。投稿が受信メールから Discourse で作成された場合、たとえばあなたのこの投稿のように、誰かがそれに返信するときに、In-Reply-To および References ヘッダーとしてその投稿の元の受信 Message-ID を使用します。これは次のとおりです。

それ以外の場合は、トピック OP の参照のみを使用し、新しい参照を生成しています。これは明らかにすべての問題を引き起こしている原因です。すべてのケースで、送信メールが送信されるたびに新しい Message-ID を生成しますが、これは他のメールクライアントと同等で正しいようです。

あなたの言っていることが理解できたと思います。次のような流れでしょうか?

  1. cameron が mutt から Discourse にメールを送信し、Message-ID: 74398756983476983@mail.com が付与される。
  2. Discourse が投稿を作成し、IncomingEmail レコードに対して Message-ID を保存する。
  3. johndoe がトピックを監視しているため、Discourse から Message-ID: topic/222/44@discourse.com のメールが送信され、元の Message-ID: 74398756983476983@mail.com への参照はない。

これは正しいでしょうか?独自のものを生成するのではなく、監視しているユーザーにその Message-ID を「渡す」だけでよいのでしょうか?その場合、cameron も元の送信メッセージに彼を CC に入れた場合、johndoe のメールクライアントではどうなりますか?これは別の問題のようですが、別のバグトピックとして開くのが良いでしょう。

mutt クライアントをローカルでセットアップして、あなたが見ているものと同じものを見るようにします。テキストベースのクライアントでのこの機能はテストしたことがありません (Gmail と Thunderbird のみ)。どのように見えるか見てみたいです。


今日の午前中のこれらの問題に対処するための私の考えは、メールに Message-ID ヘッダーを送信するときに生成されるランダムなサフィックスを廃止し、代わりに送信ユーザーと受信ユーザーの両方の user_id を使用するスキームに変更することでした。この利点は、Message-ID をどこにも保存する必要がないこと (受信メールが投稿を作成する場合を除く) であり、References および In-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
  • 送信済み → To: discourse, RE: トピック OP
  • sam - 2 番目の投稿への返信
  • bob - トピック内の特定の投稿ではない返信
  • 送信済み → To: discourse, RE: bob の投稿

sam の受信トレイ

*martin - トピック OP

  • cameron - 2 番目の投稿
  • 送信済み → To: discourse, RE: 2 番目の投稿
  • bob - トピック内の特定の投稿ではない返信

これが正しいと思いますが、これらのヘッダーに書いた内容を確認していただけますか?私が少し確信が持てないのは、References をすべてカバーしたかどうか、そしてもちろん、展開する前に開発ブランチで実際のメールのセットでこれをテストすることです。mutt でのテストもまだ行っていません。


ちなみに、GitHub が通知メールをどのように扱っているかも調べましたが、彼らも同様のことを行っており、その「トピック」(この場合は GitHub のプルリクエスト) に関連するすべてのメールで使用される、常に存在する Reference (discourse/discourse/pull/252@github.com) を持っていることに気づきました。

References: \u003cdiscourse/discourse/pull/252@github.com\u003e \u003cdiscourse/discourse/pull/252/issue_event/7042100517@github.com\u003e
In-Reply-To: \u003cdiscourse/discourse/pull/252/issue_event/7042100517@github.com\u003e
「いいね!」 6

By Martin Brennan via Discourse Meta at 22Jul2022 06:34:

Okay this is kind of huge, please bear with me. First, thanks for
another detailed reply and the offer of debugging / review, it is
really helpful :+1: I’ve actually been looking into this this morning
and, surprisingly, the threading in a unified view works in Thunderbird
for most cases, and I think the References header consistently
pointing to the OP helps with that (for example the topic Reference
in this chain which is always present is
<topic/53@discoursehosted.martin-brennan.com>.

I’ve just reread RFC5322 section 3.6.4 closely. It has moved on from
earlier versions (822 and 2822), and has merged the email In-Reply-To
headers, USENET References headers and modern
reply-citing-more-that-one previous messages.

The short summary:

  • The Message-ID is a single persisent identifier for a message
  • The In-Reply-To contains all the message-ids of which this message
    is a direct reply, so if I reply to a pair of messages it will have
    those 2 message-ids
  • The References is a reply chain of antecedant message-ids from the
    OP to the preceeding message. So indeed it should always start with
    the OP message-id.

So for a discussions like this, pretending that labels are message-ids:

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

The reply5 would have:

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

It is also leagel to include “reply1 reply2” in the references (the
other chain to reply5) but the RFC explicitly recommends against that
becaause some clients expect the references to be a single linear chain
of replies, not some flattened digraph.

So my recommendation for constructing the references is to use the
references of the “primary” antecedant message with the primary
antecedant message’s message-id appended. That way you always get a
linear chain in the correct order.

Interestingly there seems to be some threading there.

But notice: the top post has a little “is a reply” arrow. Even though it
is post 1. I expect that is because of the “topic” references entry,
which make TB think there was a earlier message (which of course there
was not).

In mutt-land we see almost no threading at all:

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

which is because every message’s In-Reply-To points directly at the
fictitious “topic” message-id. Mutt probably ignores the References
because it is a mail reader, and References originates in USENET news.
Maybe Thunderbird is using the references or augumenting the in-reply-to
with references information.

You only need to consult one of In=-Reply-To or References to do
threading; the former comes from email and the latter from USENET.
You’re supporting both (which is great!) so we need to make them
consistent.

(Aside: there’s also discussion about USENET mirroring, because several
python people consume the lists via a USENET interface. Again, a
separate topic.)

[…]

[quote=“Cameron Simpson, post:8, topic:233499,
username:cameron-simpson”]
This looks fine. Does it save this id with the db record so that inbound
replies can be tied to the antecedant forum message?
[/quote]

The answer is – it depends. If a post is created in Discourse from an inbound email, such as this one of yours, we use that post’s original inbound Message-ID when someone replies to it for the In-Reply-To and References headers as per:

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

Otherwise we are just using the topic OP reference and just generating a new reference, which obviously is what is causing all the issues. In all cases we generate a new Message-ID every time an outbound email is sent, which seems correct and on par with other mail clients.

Alas, not quite. If you’re the origin of the message (i.e. authored in
Discourse), generating the message-id is fine. If there’s no message-id
(illegal) generating one is standard practice (usually by MTAs). But if
you’re passing a message on (authored in email), the existing message-id
should be preserved.

To my mind you need to be doing 3 things:

  1. having a stable message-id and not replacing the message-id from an
    inbound message
  2. generating correct In-Reply-To, which is easily computed from the
    immediate antecedant message(s) i.e. antecedant(s)-Message-ID
  3. generating correct References, which is easily computed as
    antecedant-References + antecedant-Message-ID

For point 1, looking at the code you cite, you probably want the email
message id to be (Pythonish syntax, sorry):

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

i.e. to be the post’s email message-id if it originated from email,
otherwise the Discourse message-id using something like the algorithm
you outline later in this message: anything (a) stable and (b)
syntacticly valid.

Then computing the In-Reply-To and References fields is simple
mechanical stuff as in points 2 and 3.

I think I see what you mean, does it go like this:

  1. cameron sends email to Discourse from mutt which gets Message-ID: 74398756983476983@mail.com
  2. Discourse creates a post and stores the Message-ID with against the post with an IncomingEmail record

Correct.

  1. johndoe is watching the topic, so they get sent an email from Discourse with a Message-ID: topic/222/44@discourse.com and no reference to the original Message-ID: 74398756983476983@mail.com

No. You really want to pass through IncomingEmail.message_id as the
Message-ID in the email to johndoe. It’s the same message.

Does that sound correct, that we should just “pass on” that Message-ID to those watching the topic instead of generating our own since it’s already unique? What then happens in johndoe’s mail client if
cameron also CC’d him on that original outbound message? This does sound like a separate issue so it would be good to open another bug topic for it.

By passing it on, the original message (cameron->cc:johndoe) and the
Discourse forwarded message (cameron->Discourse->johndoe) have the same
message-id and the same message contents. The receiving mail system
stores both. The mail reader sees both, and either presents both or
keeps just one (this is a policy decision of the mail reader - keeping
just one is common). Because they’re the same message, in general it
does not matter which is kept.

If we ignored discourse and considered a message which was
a copy of the message via the list and also via direct email. They’re
the same message, with the same message-id.

I will set up a mutt client locally to see what you are also seeing, I have never tested this functionality in a text-based client (only Gmail and Thunderbird) so I am keen to see how it looks anyway.

Happy to help with settings. For threaded view you need to set the
sorting to threadeed. Mutt is very configurable.

My line of thinking to address these issues this morning was to dispose
with the randomly generated suffixes generated when we send
Message-ID headers in emails and instead change to a scheme where we
use the user_id of both the sending and receiving user. The benefit
of this is that there is no need to store the Message-ID anywhere
(apart from when an inbound email creates a post) and so References
and In-Reply-To headers will always be consistent.

Yes, that is much better. Noting that the inbound email message-id
should override the Discourse derived message-id for the outbound email.

(Most mail systems use random strings because there’s no surrounding
context such as the discourse topic message structure - messages are
considered alone; but the only real requirement is persistent
uniqueness.)

Let me give an example. Say we have these users:

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

And then we have this topic, topic_id 233499, with posts starting from post_id 100 as the OP. The format would become topic/#{topic_id}/#{post_id}.s#{sender_user_id}r#{receiver_user_id}.

The order of operations would look like this:

  1. martin creates the OP
  • cameron is sent an email with these headers:
    • Message-ID: topic/233499.s25r44@meta.discourse.org
    • References: topic/233499@meta.discourse.org
  • sam is sent an email with these headers:
    • Message-ID: topic/233499.s25r78@meta.discourse.org
    • References: topic/233499@meta.discourse.org
  1. There should not be a References header in the OP. It isn’t
    needed for threading and effectively pretends there’s some “post 0”
    which doesn’t exist. It meeans every OP (a) looks like a reply, which it
    is not and (b) looks like the thing to which it is a reply is missing
    from the reader’s mailbox.

  2. This makes different message-ids for each outbound copy of the OP.
    That’s bad. They need to be the same. Supposing sam CCs cameron
    directly in a reply. The In-Reply-To will cite a mesage-id cameron
    has never received.

You can just drop the sender_user_id and receiver_user_id from the
message-id field and get a single unique id which every receiver sees.

The uniqueness constraint is the post itself, not the individual
email-level “message” object.

Re the References, the OP should not have one. TB and everything else
will be fine. If they’re threading using References instead of
In-Reply-To, the References in the reply messages are enough.

Here’s the start of a mailing list discussion thread in 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

Ignore that I sort my email newest-on-top. See that there’s no arrow on
the initial post (at the bottom). That messgae has no References and
no In-Reply-To. All the others have In-Reply-To (and possibly
References, but this is an email mailing list so not necessarily; as I
mentioned before they’re complimentary.)

If I repeat my Discourse example from earlier:

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

See they all have a leading arrow? That is because the mail client
believes they are all replies to a common (and missing) root message,
which is because of the “topic” message-id in the References header.
Whereas post 1 is actually the bottom message displayed above.

Summary:

  • your plan is good, provided you drop the sender and receiver from the
    message-id - they’re unnecessary and in fact the receiver will cause
    trouble (the sender is just redundant).
  • drop the “topic” pseudo-message-id from the References - it misleads
    email clients (including TB, even if it isn’t visually evident)
  1. cameron replies via email
  • discourse is sent an email with these headers from 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

Yes, again with the caveat that there should not be a “topic” reference.
As expected, there is a reference to the OP message-id. Though it should
be the same message-id that sam sees for the OP.

  1. discourse (as cameron, from the above email) creates post 101
  • sam is sent an email from discourse with these headers:
    • Message-ID: topic/233499/101.s44r78@meta.discourse.org
    • References: 43585349859734@test.com topic/233499@meta.discourse.org
    • In-Reply-To: 43585349859734@test.com

And here it goes wrong. The Message-ID should be
43585349859734@test.com from the .incoming_post.message_id field.
(Well, in my mind this is post.message_id(), which returns
post.incoming_post.message_id for an email generated post and your
Discourse generated one otherwise).

Consider: I compose and send my reply with message-id
43585349859734@test.com. For continuity reasons, I keep a copy of that
in my local folder, where it shows as a reply to the OP. Ideally
Discourse also sends me a copy of my own post (this is a policy setting
on many mailing lists), so I get Discourse’s version also. That should
have the same message-id, because it is the same message, just via a
different route.

Discourse’s message is not “in reply to” my message. It is my
message, just forwarded.

This effect cascades through your following examples. The actual process
should be simpler than you’ve made it.

Think of it this way. If I reply to a post from email, it effectively is
like me emailing sam (and the others) via Discourse. Discourse
forwards my message to the email-receiving subscribers, and
“incidentally” keeps a copy on the forum :slight_smile:

As a side note, I also looked into what GitHub do with their
notification emails, and noticed they do a similar thing where they
have an ever-present Reference
(discourse/discourse/pull/252@github.com) that is used in all the
emails related to that “topic” which in this case is a GitHub pull
request:

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>

Hoo, github. What a disaster their issue emails are :slight_smile:

However, in their scenario, the PR is the OP. So a reference directly
to the pull is sane. You could use the “topic” message-id for post 1,
provided you didn’t also use the “topic/1” id as well. But there seems
little point - it is extra effort to special case post 1 - I’d just use
“topic/1” myself.

To add some complication. As I understand it, an admin can move a post
or topic. Doesn’t that break the “generate the message-id” scheme,
particularly if they move just a post? I’m somewhat of the opinion that
every post should have a _message_id field, filled in from the
incoming message (from email) or generated (posting via Discourse). Then
it is persistent and stable and robust against any shuffling of posts or
changes of algorithm.

Finally, there’s a small security consideration: you should ignore the
inbound email message-id (and potentially bounce the message) if it
claims the message-id of an existing post. Since as an author, I can put
anything I like in that header :slight_smile: I’d go with just dropping the
message-id - accept the post, but don’t let it lie about being some
other post - give your copy the Discourse-generated id and then proceed
as normal.

「いいね!」 7

素晴らしい、詳細な回答をありがとうございます。これを処理して実行可能な項目に変えるにはしばらく時間がかかると思いますので、しばらくお待ちください(また、現在取り組んでいる他の優先度の高い内部プロジェクトもあります)。この情報があれば、スレッドシステムをより堅牢で仕様に準拠したものにできると考えています。投稿を読み進める中で、さらに質問があるかもしれません。キャメロンより

「いいね!」 2

Martin Brennan 氏による Discourse Meta への投稿 (2022/07/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のUIも同様のことをしているので、おそらくそうでしょう。

私たちはこれを実行しているようですが、一貫性がないのでしょうか?基本的に、次のことを確認する必要があります。

  • TODO #1 - 投稿に関連するIncomingEmailレコードがある場合、メール送信時に常にそのMessage-IDを使用します。
  • TODO #2 - トピックのOPに関連するメール送信時には References を使用しないでください。 @cameron-simpson 1つ質問があります。もしOPが受信メールによって作成された場合、OPの References にその Message-ID を使用しますか、それとも引き続き除外しますか?

これは興味深いですね。すべてのメール受信者が一意の Message-ID を持つ必要があると思っていましたか?実際、スパム行為を避けるために、各受信者の Message-ID に一意性を追加する方向に進んだと記憶しています。おそらく、今年の初めにメールのテストをたくさん行っていたインフラチームの @supermathie も意見を述べることができるでしょう。

あなたが言っているのは、投稿がすべての受信者の単一の Message-ID を決定するものであるということですか?では、メールが送信される各投稿に対して1つ生成するだけでしょうか?そうすれば、IncomingEmail.message_id もここに移動できます。暫定的に、変更する必要があるのは次のとおりです。

  • TODO #3 - Postテーブルに outbound_message_id を追加します。投稿に関連するメールが最初に送信されたときに一度生成します。これを後続の References および In-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:


ここでのTODOは妥当に聞こえますか、Cameron?見ていると、それほど多くないように思えます。また、この作業に着手したときに、WIPの変更がデプロイされたテスト用Discourseインスタンスに私を招待して、メールをやり取りして、すべてが正しく機能していることをテストしてくれることに興味がありますか?もちろん、あなたを巻き込む前に自分でテストを行います。

もしそうでなくても構いません。Thunderbirdを持っており、mutt をセットアップして、すべてをテストできます :slight_smile:

「いいね!」 1

@cameron-simpson ここで明確にしたい点が1つあります。「message_id」のスコープについてです。

この一連のやり取りのきっかけとなったのは、@supermathie による、一意でない message_id が問題を引き起こしているのではないかという強い疑念でした。

Discourse は、送信するすべてのメールに対して、ユーザーごとに一意のメールを生成します。たとえば、このトピックをウォッチしているユーザーが2人いるとします。

  • ユーザー1 は、ユーザー1専用の購読解除リンクが付いたペイロード1を受け取ります。
  • ユーザー2 は、ユーザー2専用の購読解除リンクが付いたペイロード2を受け取ります。

この場合、両方のケースで message id が discourse_topic_100/23(topic_id/post_number)だったとすると、MTAs(メール送信エージェント)に対して、discourse_topic_100/23 が2つの異なるペイロードになり得ることを伝えていることになります。これはスパム信号として扱われるという仮説があります。

おい Discourse… discourse_topic_100/23 という名前のメールを2通送ったが、どういうことだ?

Discourse はすべてのメール転送を制御しており、従来のメーリングリストのようにメールが BCC や CC リストに追加されることはないため、ユーザーごとにクリーンな購読解除リンクを用意することができます。

これについてどう思いますか?たとえば、メールの一意の識別子として discourse_topic_100/23/7333(topic_id, post_number, user_id)を使用するという簡単な変更はどうでしょうか。これは間違いなく一意のペイロードであり、ユーザー向けのメールを生成する際に簡単に参照できます。

「いいね!」 1

By Martin Brennan via Discourse Meta at 26Jul2022 00:27:

[quote=“Cameron Simpson, post:11, topic:233499,
username:cameron-simpson”]
Mutt probably ignores the References
because it is a mail reader, and References originates in USENET news.
Maybe Thunderbird is using the references or augumenting the in-reply-to
with references information.

You only need to consult one of In=-Reply-To or References to do
threading; the former comes from email and the latter from USENET.
You’re supporting both (which is great!) so we need to make them
consistent.
[/quote]

This is also super interesting. I believe (without examining the source) Thunderbird does do that, and likely the Gmail UI as well since it does the same thing.

I think mutt will use both, but probably just In-Reply-To if present,
falling back to References. I’d need to check the source.

With References you do at least know the full chain to the OP; with
In-Reply-To you more or less need the antecedant messages around to
stitch things together. For mailing lists I usually keep the whole
thread locally until it’s done anyway, and I expect that is common.

We do seem to be doing this but I guess not consistently? Basically we need to make sure that:

  • TODO #1 - If a post has an associated IncomingEmail record, we always use that Message-ID when sending email.

Yes. This is why I was thinking it might be sanest to have an explicit
field for the message-id, and to fill it in once. Then use that from
then on always, regardless of any changes to the process in which the
message-id is manufactured in the code later.

  • TODO #2 - Do not use a References when sending out emails related to the OP of the topic .

Yes. The OP has no antecedant, so there’s no References or
In-Reply-To.

@cameron-simpson one question though – if the OP was created via an
inbound email, would we use that Message-ID in References for the
OP or still exclude it?

Still exclude. But use it as the persistent message-id for the OP.

So a message authored by email (OP or reply) gets its message-id from
the email. One authored on the web gets one when the user presses
Submit, generated by Discourse. From then on, that’s the message-id,
however created.

[quote=“Cameron Simpson, post:11, topic:233499,
username:cameron-simpson”]
You can just drop the sender_user_id and receiver_user_id from the
message-id field and get a single unique id which every receiver sees.

The uniqueness constraint is the post itself, not the individual
email-level “message” object.
[/quote]

This is interesting, I thought every recipient of the email had to have a unique Message-ID?

No. The message-id identifies the “message”. Not the individual copy. I
might post to the forum and CC someone directly. If that someone gets a
copy direct from me and also via the forum, they should have the same
message-id.

In fact I believe this is why we went down the path of adding
uniqueness to each recipient’s Message-ID, to avoid spam behaviours,
looking back on our internal topic. Perhaps @supermathie , who is on
our infra team and was doing a bunch of testing with email earlier in
the year, could weigh in here too?

Maybe. But on that face of it, threading is indeed broken. Certainly
sending the same message to many people should have the same message-id,
and generally, as a forwarder (email->discourse->email-recipients)
discourse shoud not be modifying the message-ids.

What you are saying is that it’s more that the post should be the thing determining a single Message-ID for all recipients. So perhaps we just generate one for each post that generates an email?

Every post should have one stable unique message-id for use in the email
side. If the post originated from an email, that original message-id
should be used. Otherwise (via the web interface) Discourse should be
generating a message-id and storing it with the post.

Then we could also move the IncomingEmail.message_id to here as well.

Sure. Having a distinct set of fields (message-id seems enough)
containing the email-side state should do it.

Tentatively, the change we would need to make is:

  • TODO #3 - **Add a outbound_message_id to the Post table. Generate
    it once when an email is first sent in relation to the post.

If you got the post from an email, you should be using that, not
generating a new one.

Use if for subsequent References and In-Reply-To headers. Set its
value when a post is created from an IncomingEmail.

Yes. To the message-id from the email.

Format should be
topic/:topic_id/:post_id/:random_alphanumeric_string@host e.g.
topic/233499/33545/gvy8475y7c45y87554c@meta.discourse.org**

For ones you generate yourselves, this looks good to me.

After this change my first example would become this:

  1. martin creates the OP
  • cameron is sent an email with these headers:
  • Message-ID: topic/233499/33545/gvy8475y7c45y87554c@meta.discourse.org
  • sam is sent an email with these headers:
  • Message-ID: topic/233499/33545/gvy8475y7c45y87554c@meta.discourse.org

Yes.

But note: the message-id only needs to be stable and unique. If the
topic/:topid_id/:post_id@host is stable and will never be regenerated,
that will do. But if you’re concerned about that (eg db restores or
migrations or imports bringing those same numbers) then the random
string will make it robust against collision.

Note that the message-id left part is dot-atom-text, defined here:

which is alphas and digits and a limited set of punctuation characters
(which includes “/”).

Um, your headers. They should have:

Message-ID: <topic/233499/33545/gvy8475y7c45y87554c@meta.discourse.org>

Note the angle brackets. The message-id is formally the bit between the
angle brackets, and the angle brackets are mandatory. Syntax here:

With the consideration also that the OP does not have special handling, it will no longer be in the format topic/:topic_id@hostname.

Sounds good.

  • TODO #4 - 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

Thanks.

I think we have some consideration for this, I will double-check.

+1

It definitely seems that way :sweat_smile:

Can you confirm the TODOs here sound reasonable Cameron?

They seem correct to me.

It really doesn’t seem like much now that I look at it. I also wonder,
when I get to this work would you be open to joining a testing
Discourse instance with me that will have the WIP changes deployed to
it so we can email back and forth and test that things are working
correctly? I will of course do testing of my own before I involve you.

Certainly. Happy to help in whatever way.

If not, that’s fine too – I have Thunderbird and will be setting up
mutt and I can test it all out there :slight_smile:

I can help you with mutt if you want it too.

「いいね!」 3

同じメッセージIDでも、これくらいのわずかな違いがあれば、依然として個別のメッセージを送信できると思います。

通常のメーリングリストでは、程度の差こそあれ、常にこのようなことが行われています。少なくとも、ヘッダーの操作は常に行われています。しかし、メッセージ本文が変更されることもあります。例えば、python-list ではテキスト以外の添付ファイルが破棄されます。メッセージは同じメッセージIDで通過します。そして、ほとんどのリストでは、リスト管理ページへのリンクや購読解除リンクなどが末尾に追加されます。これは、メッセージが到着したときには存在しなかったものです。

また、署名で何をカバーすべきかという、コンテンツ署名に関する長い議論もありました。

そのため、受信者固有の購読解除リンクを追加し、元のメッセージIDを保持することには全く問題ありません。もし各メッセージのコピーに個別のメッセージIDを付与した場合に失われるスレッディングよりも、メリットの方がはるかに大きいのです。

繰り返しますが、メールユーザーのことを考えてください。ディスコースのメッセージに返信して、関心のある外部の人にCCを追加することができます。その人はディスコースからコピーを受け取るかもしれませんが、受け取らないかもしれません。しかし、もし受け取った場合、追加のライダーがあっても、ソースメッセージIDが記載されているはずです。そうでなければ、その人は私のメッセージのコピーを2つ持つことになりますが、メールシステムはそれらが同じメッセージのコピーであることを認識しません。これは問題を引き起こします。

したがって、要するに、あなたの非常にわずかな追加の購読解除テキストは、個別のメッセージIDを必要とするほどのものではないということです。1つだけ保持してください。

「いいね!」 4

申し訳ありません、今追いつきました。いくつか考えを共有します。すでに解決済みのものもあります…

ここでの難しさは、Discourseから送信されるものは、受信したものとは異なるメッセージであるということです。異なるメタデータ(この目的のためには、To/From/Reply-to/Unsubscribe/など)と異なる本文(ユーザーごとにカスタマイズされています(そうだと思いますか?メーリングリストモードではこれは行われませんか?))を持っています。

メッセージとは何でしょうか? 5322を聖典として扱うと:

メッセージは、ヘッダーフィールドと、オプションでメッセージ本文で構成されます。

「Message-ID:」フィールドは、特定のメッセージの特定のバージョンを参照する一意のメッセージ識別子を提供します。

[強調は私によるものです]

「特定のバージョン」という部分が、受信したメッセージを異なるMessage-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」を持つメッセージには対応していません。

このことの難しい点は、私たちが1つのメールを送信しているのではなく、受信者ごとにN通のメールを送信していることです。これにより、個々のメタデータ(Unsubscribeなど)が正しくなります。

そしてはい、スパム判定がMessage-IDに関連付けられるという強い兆候をテスト中に見ました。後で(同じユーザーまたは異なるユーザー)再度表示された場合、スパムとしてマークされる可能性がはるかに高くなります。

正直なところ、ここでのメリットは、配信可能性を犠牲にして、特定のメールクライアントでメールを正しくスレッド化することに完全に関連しています。

現在のtopic/#{topic_id}/#{post_id}.s#{sender_user_id}r#{receiver_user_id}は、少なくともユーザーのメールボックス内では一貫性があります。仮定

私の最大の懸念は配信可能性です。主要プロバイダーからの可視性がゼロの場合、メールを配信することさえ困難です。

しかし、Discourseをメーリングリストソフトウェアのようにメーリングリストモードで動作させることには強い議論があると思います。@martin、メーリングリストモードではメッセージ本文をカスタマイズしていないと思いますが?メッセージIDを保持および再利用することに関して、より厳格なアプローチを取ることは理にかなっていると思いますか?

「いいね!」 5

ここでは、完璧を「十分良い」ことの敵にしたくないのです。

現在、メッセージに「ランダムな接尾辞」を使用しており、これが間違いなく問題を引き起こしています。

以下の3つの選択肢があります。

  1. 参照できないランダムなメッセージID
  2. トピック/投稿/ユーザーごとに安定したメッセージID
  3. トピック/投稿のペアごとに安定したメッセージID

現在、私たちは(1)の状況にあり、大混乱を引き起こしています。

(2)と(3)の間で意思決定麻痺に陥るのではないかと心配しています。

おそらく、追加のCCをDiscourseからのメールに追加すると予期しない動作が発生する可能性があることを認めつつ、まず(2)から始めることで、少なくとも大部分の問題を停止できるのではないでしょうか。

「いいね!」 4

ああ!すでに topic/#{topic_id}/#{post_id}.s#{sender_user_id}r#{receiver_user_id} を実行していると思っていました。

メールの一意性と配信可能性の懸念とメーリングリストモードの懸念のバランスを取るために、メーリングリストモードが無効の場合は(2)、メーリングリストモードが有効な場合は(3)を実行する傾向があります。

同様に、References ヘッダーについては、トピックの投稿#1では存在せず、トピック(topic/#{topic_id})と返信先の投稿を参照するようにする傾向があります。

「いいね!」 3