На discuss.python.org мы обсуждаем работу с электронной почтой в Discourse. Основная претензия — отсутствие потоков (threading). Я немного покопался в заголовках, и вот что выяснилось:
заголовок Message-ID хотя бы уникален;
заголовки Reply-To и Referencesне ссылаются на Message-ID других сообщений, не говоря уже о сообщении, на которое они являются ответом;
вместо этого они ссылаются на вымышленный идентификатор сообщения, основанный на номере темы.
Это означает, что пользователи электронной почты видят: (а) абсолютно плоские обсуждения без потоков и (б) исходное сообщение, которое, по-видимому, отсутствует, поскольку заголовки In-Reply-To и References ссылаются на идентификатор сообщения, который никогда не появляется ни в одном из писем.
Это плохо и нарушает RFC 5322. К тому же это делает работу с электронной почтой гораздо менее удобной, чем она могла бы быть.
В качестве примера, на форуме есть ветка, у первого сообщения которой следующие заголовки:
Опять же, приемлемый Message-ID, но совершенно бессмысленные In-Reply-To и References.
Это должно быть легко исправить. У первого сообщения не должно быть ни заголовка In-Reply-To, ни заголовка References. У второго сообщения в заголовках In-Reply-To и References должен быть Message-ID первого сообщения.
Пожалуйста, ознакомьтесь с разделом 3.6.4 RFC 5322 для подробностей:
В текущем состоянии пользователи электронной почты видят плоские неструктурированные обсуждения. После этих исправлений они смогут получать логичное и удобное для чтения отображение потоков.
Просто посмотрел разницу между HEAD и этим исправлением.
Мне кажется, что текущая реализация всё равно устанавливает References, даже если нет предшествующего сообщения — в качестве запасного варианта используется topic_canonical_reference_id. Я по-прежнему считаю это ошибкой, потому что сообщения электронной почты с таким идентификатором не существует.
Поле In-Reply-To немного правильнее, так как оно устанавливается только если post.post_number != 1, но всё равно использует запасной вариант topic_canonical_reference_id:
запасной вариант должен быть Message-ID сообщения №1, если массив referenced_post_message_ids пуст, а неtopic_canonical_reference_id.
в коде обработки входящих ответов что-то удаляет заголовок In-Reply-To из ответов, потому что массив referenced_post_message_ids (список?) должен был быть корректно заполнен (я новичок в Ruby).
Кэмерон, спасибо за то, что подняли эту тему для обсуждения и предоставили столько подробностей в своих сообщениях. Я отвечаю за этот «котёл с червями», возникший в результате этих двух коммитов:
Мы уже некоторое время знаем о некоторых проблемах с цепочками писем в почтовых клиентах, таких как Thunderbird, но поскольку это затрагивало лишь небольшое число пользователей, использующих функционал цепочек писем из Discourse, вопрос был отложен. Однако теперь, когда проблема стала очевидной, нам нужно уделить время её повторному рассмотрению и работе над исправлением.
Интересно, что мы добавили этот заголовок References к первому отправленному письму и ко всем последующим, поскольку это обеспечивало корректную работу цепочек в Gmail. Однако я согласен, что это не идеально и, вероятно, вызывает проблемы с цепочками наряду с тем, что в заголовках In-Reply-To и References последующих писем не используется оригинальный Message-ID.
Пожалуйста, потерпите меня, пока я изучу старые обсуждения и код и разберусь с этим вопросом. Между прочим, знаете ли вы о других почтовых клиентах, которые используются и сталкиваются с подобными проблемами? Например, я знаю, что это проблема в Thunderbird, но как насчёт остальных? Спасибо.
Приносим извинения, но ваше письмо на адрес
["incoming+8349bd9eb1f2b582df4f32dbe85c3363@meta.discoursemail.com"]
(с темой Re: [Discourse Meta] [баг] Сообщения электронной почты Discourse
некорректно группируются в потоки) не было доставлено.
Причина:
К сожалению, новые пользователи могут добавлять только 2 ссылки в сообщение.
Если вы сможете исправить проблему, пожалуйста, попробуйте снова.
Я размещу это на форуме, где смогу проверить и отредактировать…
Кэмерон, спасибо за то, что подняли эту тему для обсуждения и предоставили
много подробностей в своих сообщениях. Я отвечаю за этот «ящик Пандоры»,
созданный этими двумя коммитами:
3b13f1146b2a406238c50d6b45bc9aa721094f46
Это выглядит нормально. Сохраняется ли этот ID вместе с записью в БД, чтобы входящие ответы могли быть привязаны к исходному сообщению на форуме?
Также, хотите ли вы, чтобы я проверил, что суффикс синтаксически корректен для RFC5322 с точки зрения допустимых символов?
82cb67e67b83c444f068fd6b3006d8396803454f
Этот второй коммит, похоже, решает ещё одну проблему, с которой мы сталкивались: если сообщение приходит по электронной почте, исходящий Message-ID, отправляемый пользователям почты, не совпадает с Message-ID исходного сообщения автора. Это приводит к тому, что с точки зрения почтового клиента это два разных сообщения, и, вероятно, ломает ответы, отправленные на оригинал, а не на копию, присланную форумом. Например:
To: форум
CC: один из участников
Участник (ну, или может) получит копию от форума и прямую копию от автора, и на его стороне это будут разные сообщения, так как у них будут разные Message-ID.
Я собирался создать второй отчёт об ошибке по этой проблеме после решения вопроса с заголовками In-Reply-To и References, что гораздо важнее.
Мы уже некоторое время осведомлены о некоторых проблемах с цепочками сообщений (threading) в почтовых клиентах, таких как 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 для продолжения обсуждений и поддерживал (там) несколько Message-ID; In-Reply-To поддерживает только один. Похоже, что References теперь также присутствует в RFC5322, и я изучу его семантику.
Хорошо, это довольно масштабная тема, так что, пожалуйста, проявите терпение. Сначала спасибо за ещё один подробный ответ и предложение по отладке/ревью — это действительно очень помогает Сегодня утром я как раз изучал этот вопрос и, к удивлению, группировка в едином виде в Thunderbird работает в большинстве случаев. Я думаю, что это во многом помогает заголовок References, который последовательно указывает на исходное сообщение (OP). Например, в этой цепочке всегда присутствует заголовок Reference со значением <topic/53@discoursehosted.martin-brennan.com>.
Случай, когда группировка работает не так, как задумано, следующий:
В Discourse создаётся пост, и рассылается письмо тем, кто следит за темой.
Затем кто-то другой отвечает на этот пост, и рассылается письмо тем, кто следит за темой.
В случае второго письма заголовки In-Reply-To и References формируются неверно, поскольку они генерируются на этой строке discourse/lib/email/sender.rb at 98bacbd2c6b9fe57167cd32af5eb4839b4a5d1f6 · discourse/discourse · GitHub, вместо использования существующих. Вместо этого должно использоваться Message-ID для первого отправленного письма. На скриншоте показано, где сообщения, следующие этому шаблону, должны располагаться:
Ответ: это зависит от ситуации. Если пост в Discourse создан из входящего письма, например, этого от вас, то при ответе на него мы используем исходный входящий Message-ID этого поста для заголовков In-Reply-To и References, как указано здесь:
В противном случае мы используем только ссылку на исходное сообщение темы (OP) и генерируем новую ссылку, что, очевидно, и вызывает все проблемы. Во всех случаях мы генерируем новый Message-ID при отправке каждого исходящего письма, что кажется правильным и соответствует поведению других почтовых клиентов.
Кажется, я понял, что вы имеете в виду. Всё происходит так:
Кэмерон отправляет письмо в Discourse через mutt, которое получает Message-ID: 74398756983476983@mail.com.
Discourse создаёт пост и сохраняет Message-ID в записи IncomingEmail, связанной с этим постом.
Джон Доу следит за темой, поэтому ему отправляется письмо от Discourse с Message-ID: topic/222/44@discourse.com, без ссылки на исходный Message-ID: 74398756983476983@mail.com.
Правильно ли я понял, что мы должны просто «передавать» этот Message-ID тем, кто следит за темой, вместо того чтобы генерировать свой собственный, поскольку он уже уникален? Что тогда произойдёт в почтовом клиенте Джон Доу, если Кэмерон также включил его в копию (CC) в этом исходном исходящем сообщении? Это действительно звучит как отдельная проблема, так что стоит открыть для неё новую тему с багом.
Я настрою локально клиент 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}. Порядок операций будет таким:
martin создаёт OP
cameron получает письмо со следующими заголовками:
bob создаёт пост 103 в теме, не отвечая никому (обратите внимание, что здесь в References включён Message-ID, отправленный обоим пользователям для письма OP)
cameron получает письмо со следующими заголовками:
bob — ответ в теме, не привязанный к конкретному посту
ОТПРАВЛЕНО → для: discourse, RE: пост bob
Входящие Сэма
martin — OP темы
cameron — второй пост
ОТПРАВЛЕНО → для: discourse, RE: второй пост
bob — ответ в теме, не привязанный к конкретному посту
Я думаю, всё верно. Не могли бы вы взглянуть на то, что я написал в этих заголовках, и подтвердить, что это соответствует вашим ожиданиям для данного сценария? Единственное, в чём я немного не уверен, — это то, охватил ли я все поля References. Конечно, я протестирую это на реальном наборе писем в ветке разработки перед выпуском. Я также пока ничего не тестировал в mutt.
Кстати, я также посмотрел, как GitHub обрабатывает свои уведомления по почте, и заметил, что они делают нечто подобное: у них есть постоянный Reference (discourse/discourse/pull/252@github.com), который используется во всех письмах, связанных с этой «темой», которая в данном случае является pull request в GitHub:
От Мартина Бреннана через Discourse Meta, 22 июля 2022 г., 06:34:
Хорошо, это довольно масштабная тема, так что, пожалуйста, будьте терпеливы. Во-первых, спасибо за
ещё один подробный ответ и предложение по отладке/ревью, это действительно помогает На самом деле я этим утром
изучал этот вопрос и, удивительно, группировка в едином представлении работает в Thunderbird
в большинстве случаев, и я думаю, что заголовок References, который последовательно
ссылается на исходное сообщение (OP), помогает в этом (например, заголовок Reference в этой цепочке, который всегда присутствует, — это <topic/53@discoursehosted.martin-brennan.com>.
Я только что внимательно перечитал раздел 3.6.4 RFC5322. Он значительно изменился по сравнению
с более ранними версиями (822 и 2822) и объединил заголовки In-Reply-To электронной почты,
заголовки References USENET и современные ответы, цитирующие более одного предыдущего сообщения.
Краткое резюме:
Message-ID — это уникальный постоянный идентификатор сообщения.
In-Reply-To содержит все message-id сообщений, на которые это сообщение является прямым ответом. То есть, если я отвечаю на пару сообщений, в этом поле будут указаны эти два message-id.
References — это цепочка ответов, содержащая message-id предшествующих сообщений от OP до предыдущего сообщения. Таким образом, он действительно должен всегда начинаться с message-id OP.
Итак, для обсуждений, подобных этому, если представить, что метки — это message-id:
Также законно включить “reply1 reply2” в references (другая цепочка к reply5), но RFC явно рекомендует этого не делать, поскольку некоторые клиенты ожидают, что references будут представлять собой единую линейную цепочку ответов, а не какой-то сплющенный граф.
Поэтому моя рекомендация по построению references: использовать references “основного” предшествующего сообщения и добавить к ним message-id этого основного предшествующего сообщения. Таким образом, вы всегда получаете линейную цепочку в правильном порядке.
Интересно, что там, похоже, есть какая-то группировка.
Но обратите внимание: у верхнего поста есть маленькая стрелка «является ответом». Хотя это пост №1. Я предполагаю, что это из-за записи references «topic», которая заставляет 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 каждого сообщения указывает напрямую на вымышленный message-id «topic». Mutt, вероятно, игнорирует References, так как это почтовый клиент, а References возникли в новостях USENET. Возможно, Thunderbird использует references или дополняет in-reply-to информацией из references.
Для группировки достаточно обратиться только к одному из полей: In-Reply-To или References; первое пришло из электронной почты, а второе — из USENET. Вы поддерживаете оба (что отлично!), поэтому нам нужно сделать их согласованными.
(Кстати, также ведутся обсуждения о зеркальном отражении USENET, поскольку несколько людей из Python-сообщества потребляют списки рассылки через интерфейс USENET. Опять же, это отдельная тема.)
[…]
Ответ — зависит от ситуации. Если пост создаётся в Discourse из входящего письма, например, это ваше письмо, то при ответе на него мы используем оригинальный входящий Message-ID этого поста для заголовков In-Reply-To и References, как указано в:
В противном случае мы просто используем ссылку на OP темы и генерируем новую ссылку, что, очевидно, и вызывает все проблемы. Во всех случаях мы генерируем новый Message-ID каждый раз при отправке исходящего письма, что кажется правильным и соответствует другим почтовым клиентам.
К сожалению, не совсем так. Если вы являетесь источником сообщения (то есть оно написано в Discourse), генерация message-id допустима. Если message-id отсутствует (что незаконно), его генерация — стандартная практика (обычно это делают MTA). Но если вы пересылаете сообщение (оно написано в электронной почте), существующий message-id должен быть сохранён.
На мой взгляд, вам нужно делать три вещи:
иметь стабильный message-id и не заменять message-id входящего сообщения.
генерировать корректный In-Reply-To, который легко вычисляется на основе непосредственного предшествующего сообщения (сообщений), то есть Message-ID предшественника(ов).
генерировать корректный References, который легко вычисляется как References предшественника + Message-ID предшественника.
По пункту 1, глядя на код, на который вы ссылаетесь, вам, вероятно, нужно, чтобы ID сообщения электронной почты был (синтаксис Python, извините):
def message_id(post):
return post.incoming_email.message_id or discourse_message_id(post)
то есть это должен быть message-id входящего письма поста, если оно возникло из электронной почты, в противном случае — message-id Discourse, используя что-то вроде алгоритма, который вы описываете позже в этом сообщении: что-то (а) стабильное и (б) синтаксически корректное.
Затем вычисление полей In-Reply-To и References — это простая механическая работа, как в пунктах 2 и 3.
Я думаю, я понимаю, что вы имеете в виду. Происходит ли это так:
Кэмерон отправляет письмо в Discourse из mutt, которое получает Message-ID: 74398756983476983@mail.com
Discourse создаёт пост и сохраняет Message-ID в записи IncomingEmail относительно поста.
Верно.
johndoe подписан на тему, поэтому ему отправляется письмо от Discourse с Message-ID: topic/222/44@discourse.com и без ссылки на оригинальный Message-ID: 74398756983476983@mail.com
Нет. Вам действительно нужно передавать IncomingEmail.message_id как Message-ID в письме для johndoe. Это одно и то же сообщение.
Звучит ли это правильно: мы должны просто «переслать» этот Message-ID тем, кто подписан на тему, вместо того чтобы генерировать свой собственный, поскольку он уже уникален? Что тогда произойдёт в почтовом клиенте johndoe, если
Кэмерон также добавил его в копию (CC) в этом оригинальном исходящем сообщении? Это действительно звучит как отдельная проблема, поэтому было бы хорошо открыть отдельную тему с баг-репортом.
Передавая его, оригинальное сообщение (Кэмерон->CC: johndoe) и пересланное сообщение Discourse (Кэмерон->Discourse->johndoe) имеют одинаковый message-id и одинаковое содержание. Принимающая почтовая система хранит оба. Почтовый клиент видит оба и либо показывает оба, либо оставляет только одно (это решение политики почтового клиента — оставлять только одно — распространённая практика). Поскольку это одно и то же сообщение, в целом не имеет значения, какое из них будет сохранено.
Если мы проигнорируем Discourse и рассмотрим сообщение, которое является копией, полученной как через список рассылки, так и напрямую по электронной почте. Это одно и то же сообщение с одинаковым message-id.
Я настрою локальный клиент mutt, чтобы посмотреть, что вы видите, я никогда не тестировал эту функциональность в текстовом клиенте (только Gmail и Thunderbird), так что мне очень интересно увидеть, как это выглядит.
Рад помочь с настройками. Для группировки вам нужно установить сортировку по цепочкам (threaded). Mutt очень настраиваемый.
Моя идея по решению этих проблем сегодня заключалась в том, чтобы отказаться от случайно генерируемых суффиксов, которые создаются при отправке заголовков Message-ID в письмах, и вместо этого перейти к схеме, где мы используем user_id как отправителя, так и получателя. Преимущество этого в том, что нет необходимости хранить Message-ID где-либо (кроме случаев, когда входящее письмо создаёт пост), и поэтому заголовки References и In-Reply-To всегда будут согласованы.
Да, это намного лучше. Отмечу, что message-id входящего письма должен переопределять message-id, сгенерированный Discourse, для исходящего письма.
(Большинство почтовых систем используют случайные строки, потому что нет окружающего контекста, такого как структура сообщений темы 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}.
Заголовок References в OP быть не должен. Он не нужен для группировки и фактически притворяется, что существует какой-то «пост 0», которого нет. Это означает, что каждый OP (а) выглядит как ответ, которым он не является, и (б) выглядит так, будто сообщение, на которое он отвечает, отсутствует в почтовом ящике читателя.
Это создаёт разные message-id для каждой исходящей копии OP. Это плохо. Они должны быть одинаковыми. Предположим, sam напрямую добавляет cameron в копию (CC) в ответе. In-Reply-To будет ссылаться на message-id, который cameron никогда не получал.
Вы можете просто убратьsender_user_id и receiver_user_id из поля message-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 из 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
Обратите внимание, что у всех есть ведущая стрелка? Это потому, что почтовый клиент считает, что все они являются ответами на общее (и отсутствующее) корневое сообщение, что происходит из-за «topic» message-id в заголовке References. А пост 1 на самом деле является нижним сообщением, показанным выше.
Резюме:
ваш план хорош, при условии, что вы уберёте отправителя и получателя из message-id — они излишни, и на самом деле получатель вызовет проблемы (отправитель просто избыточен).
уберите псевдо-message-id «topic» из References — он вводит почтовые клиенты в заблуждение (включая TB, даже если это не очевидно визуально).
cameron отвечает через почту
discourse получает письмо с этими заголовками от mutt:
Да, снова с оговоркой, что не должно быть ссылки на «topic». Как и ожидалось, есть ссылка на message-id OP. Хотя это должен быть тот же message-id, который видит sam для OP.
discourse (как cameron, из вышеуказанного письма) создаёт пост 101
sam получает письмо от discourse с этими заголовками:
И здесь всё идёт не так. Message-ID должен быть 43585349859734@test.com из поля .incoming_post.message_id. (Ну, в моём представлении это post.message_id(), которое возвращает post.incoming_post.message_id для поста, созданного из письма, и ваш сгенерированный Discourse в противном случае).
Подумайте об этом так: я составляю и отправляю свой ответ с message-id 43585349859734@test.com. По соображениям непрерывности я сохраняю копию этого в своей локальной папке, где он отображается как ответ на OP. В идеале Discourse также отправляет мне копию моего собственного поста (это настройка политики во многих списках рассылки), поэтому я получаю также версию Discourse. Она должна иметь тот же message-id, потому что это одно и то же сообщение, просто доставленное по другому пути.
Сообщение Discourse не «является ответом» на моё сообщение. Это мое сообщение, просто пересланное.
Этот эффект распространяется на ваши следующие примеры. Фактический процесс должен быть проще, чем вы его сделали.
Подумайте об этом так. Если я отвечаю на пост из письма, это фактически похоже на то, как я отправляю письмо sam (и другим) через Discourse. Discourse пересылает моё сообщение подписчикам, получающим почту, и «случайно» сохраняет копию на форуме
В качестве примечания я также посмотрел, что делает GitHub с их уведомлениями по электронной почте, и заметил, что они делают что-то подобное: у них есть постоянно присутствующий Reference
(discourse/discourse/pull/252@github.com), который используется во всех письмах, связанных с этой «темой», которая в данном случае является pull-запросом GitHub:
Ох, GitHub. Что за катастрофа их письма по вопросам
Однако в их сценарии PR и есть OP. Поэтому ссылка напрямую на pull-запрос разумна. Вы можете использовать «topic» message-id для поста 1, при условии, что вы не будете также использовать id «topic/1». Но в этом мало смысла — это лишние усилия для специального случая поста 1 — я бы просто использовал «topic/1».
Чтобы добавить немного сложности. Насколько я понимаю, администратор может переместить пост или тему. Не ломает ли это схему «генерации message-id», особенно если они перемещают только пост? Я склоняюсь к мнению, что у каждого поста должно быть поле _message_id, заполненное из входящего сообщения (из письма) или сгенерированное (при публикации через Discourse). Тогда оно будет постоянным, стабильным и устойчивым к любым перестановкам постов или изменениям алгоритма.
Наконец, есть небольшой вопрос безопасности: вы должны игнорировать входящий message-id письма (и потенциально отклонять сообщение), если он утверждает, что это message-id существующего поста. Поскольку как автор я могу вписать в этот заголовок что угодно Я бы выбрал просто отказ от message-id — принять пост, но не позволять ему лгать о том, что это какой-то другой пост — присвойте вашей копии сгенерированный Discourse ID и продолжайте как обычно.
Огромное спасибо ещё раз за этот замечательно подробный ответ. Мне, вероятно, потребуется немного времени, чтобы осмыслить эту информацию и превратить её в конкретные задачи, так что, пожалуйста, отнеситесь к этому с пониманием (к тому же в данный момент я занимаюсь другими внутренними проектами, имеющими высокий приоритет). Я считаю, что с этой информацией мы сможем сделать наши системы потоков гораздо более надёжными и соответствующими спецификациям. У меня могут возникнуть дополнительные вопросы по мере того, как я буду изучать ваш пост. Спасибо, Кэмерон.
От Мартина Бреннана через Discourse Meta, 25 июля 2022 г., 00:28:
Вау, ещё раз спасибо за этот замечательный развёрнутый ответ. Мне, вероятно, потребуется немного времени, чтобы осмыслить его и превратить в конкретные задачи, так что, пожалуйста, будьте снисходительны (к тому же в данный момент я работаю над другими внутренними проектами высокой приоритетности). Я считаю, что с этой информацией мы сможем сделать наши системы потоков гораздо более надёжными и соответствующими спецификации. У меня могут возникнуть дополнительные вопросы по мере изучения вашего поста, спасибо, Кэмерон.
То есть сохранён мой оригинальный идентификатор сообщения электронной почты. Следовательно, поле In-Reply-To указано верно, а в поле References как минимум присутствует мой идентификатор сообщения.
Ах, это интересное наблюдение, я не заметил эту маленькую стрелочку.
Это тоже очень интересно. Я считаю (не изучая исходный код), что Thunderbird действительно так делает, и, скорее всего, интерфейс Gmail тоже, поскольку он делает то же самое.
Похоже, мы это делаем, но, видимо, не последовательно? В основном нам нужно убедиться, что:
TODO #1 - Если у поста есть связанная запись IncomingEmail, мы всегда используем этот Message-ID при отправке письма.
TODO #2 - Не использовать References при отправке писем, связанных с OP темы. @cameron-simpson, один вопрос: если OP был создан через входящее письмо, будем ли мы использовать этот Message-ID в References для OP или все же исключать его?
Это интересно, я думал, что каждый получатель письма должен иметь уникальный Message-ID? На самом деле, я считаю, что именно поэтому мы пошли по пути добавления уникальности к Message-ID каждого получателя, чтобы избежать спам-поведения, оглядываясь на нашу внутреннюю тему. Возможно, @supermathie, который работает в нашей инфраструктурной команде и проводил много тестов с почтой в начале года, тоже выскажется здесь?
То, что вы говорите, означает, что именно пост должен определять единый Message-ID для всех получателей. Так что, возможно, мы просто будем генерировать один для каждого поста, который порождает письмо? Тогда мы также сможем перенести IncomingEmail.message_id сюда. Предварительно изменение, которое нам нужно будет внести, следующее:
TODO #3 - Добавить поле outbound_message_id в таблицу Post. Генерировать его один раз при первой отправке письма, связанного с постом. Использовать для последующих заголовков References и In-Reply-To. Устанавливать его значение при создании поста из IncomingEmail. Формат должен быть topic/:topic_id/:post_id/:random_alphanumeric_string@host, например, topic/233499/33545/gvy8475y7c45y87554c@meta.discourse.org
После этого изменения мой первый пример будет выглядеть так:
martin создает OP
cameron получает письмо со следующими заголовками:
С учетом того, что у OP теперь нет специальной обработки, он больше не будет иметь формат topic/:topic_id@hostname.
TODO #4 - Обеспечить генерацию правильных заголовков In-Reply-To и References на основе записей PostReply и нового столбца outbound_message_id в таблице Post
Я думаю, у нас есть какая-то проверка на это, я перепроверю.
Действительно, так кажется
Кэмерон, можешь подтвердить, что эти TODO звучат разумно? Сейчас это кажется не таким уж большим объемом работы. Также интересно: когда я приступлю к этой работе, будешь ли ты готов присоединиться к тестовой установке Discourse со мной, где будут развернуты изменения в разработке, чтобы мы могли обмениваться письмами и проверить, что все работает правильно? Конечно, я проведу собственное тестирование, прежде чем привлекать тебя.
Если нет, то тоже ничего — у меня есть Thunderbird, я установлю mutt и смогу всё протестировать там
@cameron-simpson, я хотел бы прояснить один момент: область действия «message_id».
Всё началось с того, что @supermathie выразил сильное подозрение, что наши не уникальные message_id вызывают проблемы.
Discourse генерирует уникальные email-адреса для каждого пользователя для каждого отправляемого письма. Например, если за этой темой следят два пользователя:
Пользователь 1 получает payload 1 с уникальной ссылкой для отписки, адресованной пользователю 1
Пользователь 2 получает payload 2 с уникальной ссылкой для отписки, адресованной пользователю 2
Если в обоих случаях наш message_id будет, скажем, discourse_topic_100/23 (topic_id/post_number), то мы сообщим внешним MTA, что discourse_topic_100/23 может соответствовать двум разным payload. Гипотеза заключается в том, что они расценивают это как сигнал спама.
Эй, Discourse… вы только что отправили два письма с именем discourse_topic_100/23. Что происходит?
Поскольку Discourse контролирует всю транспортировку email, и письма не добавляются в списки BCC или CC, как в традиционных рассылках, мы можем позволить себе использовать чистые ссылки для отписки, уникальные для каждого пользователя.
Что вы об этом думаете? Как насчёт простого изменения — использовать discourse_topic_100/23/7333 (topic_id, post_number, user_id) в качестве уникального идентификатора для письма? Это, безусловно, уникальный payload, и мы легко сможем ссылаться на него при генерации писем для пользователя.
От Мартина Бреннана через Discourse Meta, 26 июля 2022 г., 00:27:
Это тоже очень интересно. Я считаю (не изучая исходный код), что Thunderbird действительно так делает, и, вероятно, интерфейс Gmail тоже, поскольку он делает то же самое.
Думаю, Mutt будет использовать оба, но, скорее всего, только In-Reply-To, если он присутствует,
с возвратом к References. Мне нужно будет проверить исходный код.
С References вы как минимум знаете полную цепочку до автора оригинального сообщения (OP);
с In-Reply-To вам в той или иной мере нужны предшествующие сообщения вокруг,
чтобы собрать всё вместе. Для почтовых списков я обычно держу весь поток локально,
пока он не завершится, и, думаю, это распространённая практика.
Похоже, мы это делаем, но, видимо, не всегда последовательно? В основном нам нужно убедиться, что:
TODO #1 — Если у поста есть связанная запись IncomingEmail, мы всегда используем этот Message-ID при отправке письма.
Да. Именно поэтому я думал, что разумнее всего иметь явное
поле для message-id и заполнять его один раз. Затем всегда использовать его,
независимо от любых изменений в процессе генерации message-id в коде позже.
TODO #2 — Не использовать References при отправке писем, связанных с OP темы.
Да. У OP нет предшественников, поэтому нет ни References, ни In-Reply-To.
@cameron-simpson один вопрос, однако — если OP был создан через
входящее письмо, будем ли мы использовать этот Message-ID в References для
OP или всё равно исключать его?
Всё равно исключить. Но использовать его как постоянный message-id для OP.
Таким образом, сообщение, созданное по почте (OP или ответ), получает свой message-id из
этого письма. Созданное через веб-интерфейс получает его, когда пользователь нажимает
«Отправить», сгенерированный Discourse. С этого момента это message-id, независимо от способа создания.
[quote=“Cameron Simpson, post:11, topic:233499,
username:cameron-simpson”]
Вы можете просто убратьsender_user_id и receiver_user_id из поля
message-id и получить уникальный идентификатор, который видит каждый получатель.
Ограничение уникальности относится к самому посту, а не к отдельному
объекту «сообщения» на уровне электронной почты.
[/quote]
Это интересно, я думал, что каждый получатель письма должен иметь уникальный Message-ID?
Нет. Message-id идентифицирует «сообщение», а не отдельную копию. Я
могу опубликовать на форуме и напрямую скопировать кого-то (CC). Если этот кто-то получит копию
напрямую от меня и также через форум, у них должен быть одинаковый
message-id.
На самом деле, я считаю, что именно поэтому мы пошли по пути добавления
уникальности в Message-ID каждого получателя, чтобы избежать спам-поведений,
оглядываясь на нашу внутреннюю тему. Возможно, @supermathie, который работает в
нашей инфраструктурной команде и проводил много тестов с почтой ранее в
этом году, тоже сможет высказаться?
Возможно. Но на первый взгляд организация потоков действительно нарушена. Определённо
отправка одного и того же сообщения многим людям должна иметь одинаковый message-id,
и вообще, как форвардер (email → Discourse → email-получатели),
Discourse не должен изменять message-id.
Вы говорите, что скорее именно пост должен определять единый Message-ID для всех получателей. Так что, возможно, мы просто сгенерируем один для каждого поста, который порождает письмо?
Каждый пост должен иметь один стабильный уникальный message-id для использования на стороне
почты. Если пост был создан из письма, должен использоваться этот оригинальный message-id.
Иначе (через веб-интерфейс) Discourse должен генерировать message-id и хранить его вместе с постом.
Тогда мы также могли бы перенести IncomingEmail.message_id сюда.
Конечно. Наличие отдельного набора полей (message-id, кажется, достаточно),
содержащих состояние на стороне почты, должно решить проблему.
Предварительно, изменение, которое нам нужно внести:
TODO #3 — **Добавить outbound_message_id в таблицу Post. Сгенерировать
его один раз, когда письмо впервые отправлено в связи с постом.
Если вы получили пост из письма, вы должны использовать его, а не
генерировать новый.
Использовать его для последующих заголовков References и In-Reply-To. Установить его
значение, когда пост создан из IncomingEmail.
Да. К message-id из письма.
Формат должен быть topic/:topic_id/:post_id/:random_alphanumeric_string@host, например, topic/233499/33545/gvy8475y7c45y87554c@meta.discourse.org**
Для тех, что вы генерируете сами, это выглядит хорошо.
После этого изменения мой первый пример станет таким:
martin создаёт OP
cameron получает письмо со следующими заголовками:
Но обратите внимание: message-id должен быть просто стабильным и уникальным. Если topic/:topic_id/:post_id@host стабилен и никогда не будет перегенерирован,
этого достаточно. Но если вы обеспокоены этим (например, восстановление БД, миграции или импорты,
привозящие те же самые номера), то случайная строка сделает его устойчивым к коллизиям.
Обратите внимание, что левая часть message-id — это dot-atom-text, определённая здесь:
что включает буквы, цифры и ограниченный набор знаков пунктуации
(включая «/»).
Обратите внимание на угловые скобки. Message-id формально — это часть между
угловыми скобками, и угловые скобки обязательны. Синтаксис здесь:
С учётом того, что у OP нет специальной обработки, он больше не будет в формате topic/:topic_id@hostname.
Звучит хорошо.
TODO #4 — Обеспечить генерацию правильных заголовков In-Reply-To и References на основе записей PostReply и нового столбца outbound_message_id в таблице Post
Спасибо.
Я думаю, у нас есть некоторые соображения по этому поводу, я перепроверю.
+1
Определённо так кажется
Можете ли вы подтвердить, что TODO здесь звучат разумно, Кэмерон?
Мне они кажутся правильными.
Сейчас, когда я посмотрел на это, это действительно не кажется чем-то большим. Также интересно,
когда я дойду до этой работы, будете ли вы готовы присоединиться ко мне к тестовому
экземпляру Discourse, на котором будут развёрнуты изменения в разработке (WIP),
чтобы мы могли переписываться и тестировать, что всё работает
правильно? Конечно, я проведу собственные тесты, прежде чем вовлекать вас.
Конечно. С радостью помогу любым способом.
Если нет, то тоже нормально — у меня есть Thunderbird, и я настрою mutt, и смогу всё протестировать там
Я считаю, что вы всё ещё можете отправлять разные сообщения с одинаковым идентификатором сообщения (message-id), даже при наличии таких небольших различий.
Обычные рассылки делают это постоянно в той или иной степени. По крайней мере, всегда происходит какая-то манипуляция заголовками. Но иногда изменяется и тело сообщения. Ярким примером служит python-list, который отбрасывает вложения, не являющиеся текстом. Сообщение проходит с тем же идентификатором. И почти все списки добавляют внизу уведомление, например, ссылку на страницу администратора списка или ссылку для отписки. Это не было в сообщении, когда оно прибыло.
Также велись долгие обсуждения о подписи контента, вращающиеся вокруг того, что именно должно быть охвачено подписью.
Поэтому я совершенно не против, если вы добавите свою ссылку для отписки, специфичную для получателя, и сохраните оригинальный идентификатор сообщения. Преимущества намного-намного перевешивают потерю ветвления, если бы вы присваивали каждому копии сообщения уникальный идентификатор.
Ещё раз, подумайте о пользователе электронной почты. Я могу ответить на сообщение из Discourse и добавить в копию (CC) заинтересованного внешнего человека. Возможно, они получат копию от Discourse, а возможно, нет. Но если они её получат, она должна содержать исходный идентификатор сообщения, даже с вашим дополнительным уведомлением. Иначе у них будет две копии моего сообщения, но их почтовая система не поймёт, что это копии одного и того же сообщения. Последствия будут неприятными.
Таким образом, коротко: я не считаю, что ваш очень небольшой дополнительный текст для отписки оправдывает присвоение разных идентификаторов сообщений. Оставьте один.
Извините, я сейчас только догоняю, вот некоторые мысли, часть из которых уже была рассмотрена…
Сложность здесь заключается в том, что то, что отправляется из Discourse, — это фактически другое сообщение, чем входящее. У него другие метаданные (в данном контексте: To/From/Reply-to/Unsubscribe и т. д.) и другое тело (оно кастомизируется для каждого пользователя (полагаю? Разве этого не происходит в режиме рассылки?)).
Что именно является сообщением? Если следовать стандарту 5322 буквально:
Сообщение состоит из полей заголовка, за которыми необязательно следует тело сообщения.
Поле “Message-ID:” предоставляет уникальный идентификатор сообщения, который ссылается на конкретную версию конкретного сообщения.
[выделение моё]
Именно эта “конкретная версия” заставляет меня думать, что было бы некорректно пересылать входящее сообщение с другим Message-ID. Хотя, если изменить точку зрения: рассматривать Discourse не как “программу для форумов”, а как “программу для рассылки”, то такое решение в каком-то смысле имеет смысл, так что я понимаю вашу позицию. В стандарте 5322 также сказано:
Существует множество случаев, когда сообщения “изменяются”, но эти изменения не составляют новую инстанцию этого сообщения, и поэтому сообщение не получает новый идентификатор. Например, при вводе сообщений в транспортную систему к ним часто добавляются дополнительные поля заголовка, такие как поля трассировки (описаны в разделе 3.6.7) и поля повторной отправки (описаны в разделе 3.6.6). Добавление таких полей заголовка не меняет идентичность сообщения, поэтому оригинальное поле “Message-ID:” сохраняется. Во всех случаях именно смысл, который хочет передать отправитель сообщения (т. е. является ли это тем же самым сообщением или другим), определяет, меняется ли поле “Message-ID:”, а не какие-либо конкретные синтаксические различия, которые появляются (или отсутствуют) в сообщении.
Думаю, всё сводится к вопросу: меняется ли отправитель сообщения, когда Discourse отправляет его наружу?
Может быть, нам стоит использовать Resent-Message-ID и родственные поля?
Оно всегда было там, ещё со времён стандарта 822. Но, как вы позже отметили, да, оно было обновлено.
Стандарт 5322 также прямо говорит о том, как Discourse и GitHub его используют:
Поле “In-Reply-To:” может использоваться для идентификации сообщения (или сообщений), на которое отвечает новое сообщение, в то время как поле “References:” может использоваться для идентификации “цепочки” обсуждения.
Возможно, с небольшой неточностью, вероятно, из-за отсутствия подходящего заголовка “Thread Identifier”. Но такая интерпретация может не соответствовать замыслу авторов RFC… она не решает проблему сообщений с полем “References”, но без “In-Reply-To”.
Сложность здесь в том, что мы отправляем не одно письмо, а N писем — по одному для каждого получателя, чтобы их индивидуальные метаданные (Unsubscribe и т. д.) были корректными.
И да, во время тестирования я действительно видел сильные признаки того, что определение спама привязано к Message-ID. Если оно позже встречалось снова (у того же пользователя или у другого), вероятность пометить его как спам значительно возрастала.
Преимущества здесь, если быть честным, полностью связаны с правильной организацией цепочек писем в определённых почтовых клиентах, но ценой снижения доставляемости.
Текущий формат topic/#{topic_id}/#{post_id}.s#{sender_user_id}r#{receiver_user_id} хотя бы обеспечивает согласованность для пользователя в его почтовом ящике. Предположение
Моя главная проблема — доставляемость: даже сейчас трудно добиться доставки писем, когда у основных провайдеров нет видимости.
Но я вижу сильный аргумент в пользу того, чтобы Discourse в режиме рассылки вел себя больше как программное обеспечение для рассылки. @martin, я полагаю, что в режиме рассылки мы не кастомизируем тело сообщения? Как вы думаете, имеет ли смысл придерживаться более строгого подхода к сохранению и повторному использованию Message-ID в режиме рассылки?
Я не хочу оказаться в ситуации, когда стремление к совершенству становится врагом достаточного качества.
Сейчас мы используем «случайный суффикс» в идентификаторах сообщений, и это, безусловно, вызывает проблемы.
У нас есть три варианта:
Случайные идентификаторы сообщений, по которым невозможно отследить источник.
Идентификаторы сообщений, стабильные для темы/сообщения/пользователя.
Идентификаторы сообщений, стабильные для пары тема/сообщение.
Сейчас мы находимся в ситуации (1), что вызывает хаос.
Я беспокоюсь, что мы можем попасть в тупик при выборе между вариантами (2) и (3).
Возможно, стоит просто начать с варианта (2), признав, что добавление дополнительных копий в письмо от Discourse может вызвать непредвиденное поведение, но это хотя бы прекратит основную часть проблем?
Ах! Я думал, что мы уже используем формат: topic/#{topic_id}/#{post_id}.s#{sender_user_id}r#{receiver_user_id}.
Я склоняюсь к тому, чтобы, учитывая необходимость баланса между уникальностью и доставкой адресов электронной почты и требованиями режима рассылки, использовать вариант (2) для отключённого режима рассылки и вариант (3) для включённого режима рассылки.
Аналогично, в заголовке References я бы предпочёл, чтобы он отсутствовал для первого сообщения в теме, а для остальных сообщений ссылался на тему (то есть topic/#{topic_id}) и на сообщение, на которое дается ответ, если таковое имеется.