Mensagens de e-mail do Discourse estão incorretamente encadeadas

Desculpas antecipadas por parte do tom abaixo. Eu pareço exasperado, porque estou um pouco exasperado.

Por Michael Brown via Discourse Meta em 27 de julho de 2022, 14:06:

Desculpe, estou apenas me atualizando agora, aqui estão algumas reflexões, algumas das quais já foram abordadas…

A dificuldade aqui é que o que é enviado para fora do Discourse é uma mensagem diferente da que entra. Ela tem metadados diferentes (para este propósito, Para/De/Responder-para/Cancelar inscrição/etc.) e um corpo diferente (é personalizado por usuário (acho? Isso não acontece no modo lista de e-mails?)).

O que exatamente é a mensagem? Tratando 5322 como evangelho:

Uma mensagem consiste em campos de cabeçalho, opcionalmente seguidos por um corpo de mensagem.

O campo “Message-ID:” fornece um identificador de mensagem exclusivo que se refere a uma versão específica de uma mensagem específica.
[ênfase minha]

É essa “versão específica” que me faz pensar que seria inapropriado reenviar uma mensagem recebida com um Message-ID diferente. Embora, se você mudar seu ponto de vista de Discourse como “Software de Fórum” para Discourse como “Software de Lista de E-mails”, então de certa forma faz sentido fazer isso, então entendo de onde você está vindo.

Bem, infelizmente isso depende de uma leitura excessivamente literal, talvez lendo um contexto que não está lá.

Todo e-mail tem seus cabeçalhos modificados à medida que o sistema de e-mail o processa. Se nada mais, os cabeçalhos Received: são adicionados a cada etapa, e vários sistemas adicionam vários cabeçalhos indicando resultados de filtragem de spam e assinaturas. Nenhum desses aciona uma modificação do message-id, e de fato fazer isso tornaria o message-id totalmente disfuncional.

Em relação ao conteúdo, como já mencionado, quase todas as listas de e-mails adicionam conteúdo ao corpo do texto, geralmente um rodapé com um link para a página de administração da lista ou um link de cancelamento de inscrição. Isso também não aciona uma alteração do message-id.

Na verdade, quase nada que encaminha uma mensagem altera o message-id. Porque isso quebraria o encadeamento e a detecção de duplicatas para clientes de usuário final.

Vejo que você continua citando o que eu estava prestes a citar :slight_smile:

5322 também diz:

Há muitas instâncias em que as mensagens são “alteradas”, mas essas alterações não constituem uma nova instanciação dessa mensagem e, portanto, a mensagem não receberia um novo identificador de mensagem. Por exemplo, quando as mensagens são introduzidas no sistema de transporte, elas são frequentemente precedidas por campos de cabeçalho adicionais, como campos de rastreamento (descritos na seção 3.6.7) e campos reenviados (descritos na seção 3.6.6). A adição de tais campos de cabeçalho não altera a identidade da mensagem e, portanto, o campo “Message-ID:” original é retido. Em todos os casos, é o significado que o remetente da mensagem deseja transmitir (ou seja, se esta é a mesma mensagem ou uma mensagem diferente) que determina se o campo “Message-ID:” muda ou não, não qualquer diferença sintática particular que apareça (ou não apareça) na mensagem.

Suponho que se resume a, o remetente da mensagem muda quando o Discourse a envia?

Acho que você leu as coisas erradas aqui. Deixe-me enfatizar:

Em todos os casos, é o significado que o remetente da mensagem
deseja transmitir (ou seja, se esta é a mesma mensagem ou uma mensagem diferente) que determina se o campo “Message-ID:” muda

O remetente é o autor, não um MTA como o Discourse.

Se eu postar no Discourse por e-mail, quero que minha mensagem chegue aos leitores como está, semanticamente falando. Quaisquer adicionais como links de cancelamento de inscrição não mudam a semântica do que eu disse em minha mensagem.

Ainda é a mesma mensagem.

Talvez devêssemos usar Resent-Message-ID e amigos?

Absolutamente não. Eles são para um usuário reenviando uma mensagem. Por exemplo, se eu encaminhei uma mensagem para outra pessoa. Eles não são para retransmissores de e-mail (como listas e Discourse).

Sempre esteve lá, desde o 822. Mas como você diz mais tarde, sim, foi atualizado.

Ai. Pensei que fosse apenas USENET naquele ponto. Eu me corrijo.

5322 também fala diretamente sobre como o Discourse e o Github o usam:

O campo “In-Reply-To:” pode ser usado para identificar a mensagem (ou mensagens) à qual a nova mensagem é uma resposta, enquanto o campo “References:” pode ser usado para identificar um “thread” de conversa.

Possivelmente um pouco incorretamente, provavelmente devido à falta de um cabeçalho “Thread Identifier” adequado. Mas essa interpretação pode não ser o que os autores do RFC pretendiam… não aborda mensagens com “References” mas sem “In-Reply-To”.

Para mim, diz que os dois campos cobrem a mesma informação:

  • References mostra um thread linear (geralmente) de volta ao OP
  • In-Reply-To mostra o pai e implica o mesmo thread no agregado com as mensagens anteriores de volta ao OP

A parte complicada disso é que não estamos enviando um e-mail, estamos enviando N - um por destinatário - para que seus metadados individuais (Cancelar inscrição, etc.) possam estar corretos.

Isso não é complicado. O significado das mensagens é o mesmo, as personalizações são menores e semanticamente irrelevantes. Elas não justificam message-ids novos ou distintos.

E sim, eu vi fortes indicações durante os testes de que a determinação de spam estaria ligada a um Message-ID. Se fosse visto novamente mais tarde (mesmo usuário ou usuário diferente), seria muito mais provável ser marcado como spam.

Você pode mostrar algumas dessas instâncias? Porque os message-ids permitem a desduplicação no final do usuário. E tenha em mente que muitas medidas “antispam” são lixo enganoso. A quantidade de coisas que tive rejeitadas como spam potencial por razões totalmente espúrias… quebrar o e-mail para contornar a má filtragem de spam quebrada é uma má escolha.

Até hoje, nunca coloco pessoas com endereços GMail em cópia, porque a filtragem de spam do GMail me conhece e joga coisas fora. Se eu enviar apenas para a lista, eles recebem. Se eu colocar em cópia o endereço GMail deles, ele (a) o marca como spam e (b) também marca a mensagem da lista de e-mails como spam (mesmo message-id!). O usuário final não vê minha mensagem. Essa lógica é totalmente espúria e irrecuperável.

Os benefícios aqui, para ser justo, são inteiramente em torno do encadeamento correto dos e-mails em certos clientes de e-mail, em detrimento da entregabilidade.

Suspiro. Para todos os clientes de e-mail. E um grande motivo pelo qual as pessoas em Pythonland dizem que não vão para o Discourse é que o encadeamento do lado do e-mail está quebrado. Muitas pessoas não usam fóruns, porque cada fórum exige que elas o visitem. O e-mail chega até elas, elas podem usar seu leitor preferido e seu editor preferido, e o encadeamento permite que as pessoas vejam o fluxo da discussão claramente. Quando funciona.

O atual topic/#{topic_id}/#{post_id}.s#{sender_user_id}r#{receiver_user_id} pelo menos o torna consistente para um usuário em sua caixa de entrada. A suposição

Minha maior preocupação é a entregabilidade - já é difícil o suficiente entregar e-mails quando não há visibilidade dos principais provedores.

Eu gostaria de ver evidências. Listas de e-mail fazem isso corretamente em todo o planeta. O Discourse definitivamente e objetivamente quebra isso. Estou tentando consertar.

Deixe-me reiterar os dois problemas básicos aqui:

  • O In-Reply-To e References do OP citam um “message-id de tópico” “pré-OP” fictício, então nenhum usuário de e-mail tem um thread com uma mensagem inicial (o OP) - tudo, incluindo o OP, parece um seguimento.
  • Os e-mails recebidos via Discourse e os e-mails recebidos diretamente, por exemplo, via CC, têm message-ids diferentes, embora sejam a mesma mensagem semanticamente falando; isso quebra o encadeamento e a desduplicação.

Mas eu vejo um forte argumento para fazer o Discourse se comportar mais como software de lista de e-mails no modo lista de e-mails. @martin, acredito que não personalizamos o corpo da mensagem no modo lista de e-mails? Você acha que faz sentido adotar uma abordagem mais rigorosa em torno da preservação e reutilização de Message-IDs no modo lista de e-mails?

Existem pessoas em Pythonland que acharam o “modo lista de e-mails” muito intenso. Elas querem receber e-mails para tópicos direcionados, mas não para tudo. O manuseio do message-id deve ser o mesmo para todo o lado do e-mail.

Eu sou uma pessoa do tipo “modo lista de e-mails” em discuss.python.org. Mas ativei aqui (discourse.org) e _imediatamente desativei novamente. Preciso do modo direcionado aqui.

4 curtidas

Por Michael Brown via Discourse Meta em 27Jul2022 22:37:

ah! Pensei que já estávamos fazendo: topic/#{topic_id}/#{post_id}.s#{sender_user_id}r#{receiver_user_id}

{receiver_user_id} coloca você em IDs de mensagem distintas por usuário final para o mesmo post de origem. Isso é ruim assim que os usuários finais se comunicam fora do Discourse ou recebem cópias não via Discourse.

Eu tenderia a, no interesse de equilibrar as preocupações de exclusividade e entregabilidade de e-mail versus as do modo lista de e-mail, fazer \(2) para o modo lista de e-mail desativado e (3) para o modo lista de e-mail ativado.

E mencionado em minha postagem recente, o modo lista de e-mail cobre apenas um sabor de recepção de e-mail do Discourse. Todas as mesmas preocupações se aplicam se o destinatário do e-mail estiver no modo lista de e-mail ou apenas no modo e-mail-para-alguns-tópicos/tags.

Da mesma forma, com o cabeçalho References, eu tenderia a tê-lo ausente para o post nº 1 em um tópico.

Da mesma forma o In-Reply-To. nenhum deve estar presente, pois para estar presente eles têm que referenciar uma mensagem fictícia por-OP.

e fazê-lo referenciar o tópico (então topic/#{topic_id}) e o post

ao qual está respondendo, se houver.

Você não pode se referir ao ID da mensagem do “tópico” a menos que houvesse um post com esse ID de mensagem que foi enviado por e-mail. Se você quiser seguir esse caminho, trate o ID da mensagem do OP como o ID da mensagem do “tópico” em vez de ...../1.

3 curtidas

Isso deveria ser “prior-to-OP”. Desculpe, Cameron Simpson

Como você diz, este é exatamente o problema que nos frustra:

Concordo que isso deve ser alterado. O message-id do OP deve ser (na ausência de um vindo por e-mail) (simplificado) topic/1 e não referenciar outra mensagem.

O message ID não mudaria, mesmo que fosse apenas uma postagem do Discourse e nunca um e-mail.

Mensagens posteriores podem referenciar essa.

Por que deve existir um e-mail? Semanticamente, ter apenas a postagem atende aos critérios. A mensagem à qual está respondendo existe, apenas não na pasta de e-mail dessa pessoa. Acabamos de chegar à conclusão de que a mensagem é o que é importante, seja corpo da Postagem ou corpo do E-mail. Isso significa que topic/#{topic_id}/1@site é um ID de mensagem exclusivo que referencia essa postagem, quer esteja em uma mensagem de e-mail ou não.

Não é diferente de receber uma resposta a um e-mail que referencia um e-mail que não está na sua caixa de entrada. Ainda é uma resposta, então References é legítimo e correto.

Fundamentalmente, concordo com você. O purista em mim quer que isso esteja correto. Mas a praticidade de precisar que os e-mails cheguem às caixas de entrada das pessoas foi o que levou a isso. Para pior, muitas pessoas usam o gmail e nunca treinam seus filtros, o usam corretamente e “cancelam a inscrição” relatando como spam[1].

Concordo, acho que fomos um pouco literais ao ler

Um identificador de mensagem pertence a exatamente uma instanciação de uma mensagem específica

Depois de pensar sobre isso por um tempo, acho que deveríamos voltar ao que tínhamos antes (remover a aleatorização) e fixar um único message-id por postagem e deve ser:

  • message_id_do_email_de_entrada || topic/#{topic_id}/#{post_num}@site (post_num do OP é 1)

E sempre que enviarmos um e-mail, acho que é correto adicionar References aos pais até o OP e definir In-Reply-To para o message-ID estável apropriado (ou o OP se respondendo ao tópico), já que a Mensagem é a postagem. Mas esses campos para o OP devem estar em branco, sim.


  1. não que o gmail nos relate isso, apesar de termos implementado o feedback-loop. ↩︎

5 curtidas

Obrigado por suas respostas @supermathie e @cameron-simpson, acredito que chegamos a um consenso. Retirando os TODOs em uma única postagem, e espero poder começar a trabalhar neles em breve:

  1. Altere o formato gerado do Message-ID para ser sempre \u003cdiscourse/post/:post_id@:hostname\u003e, isso é único o suficiente, é basicamente o que fazíamos antes. Referir-se ao OP usará apenas o ID da primeira postagem agora, em vez de apenas o ID do tópico.
  2. Se uma postagem tiver um registro IncomingEmail associado, sempre usaremos esse Message-ID ao enviar e-mail, caso contrário, geraremos um usando o formato acima.
  3. Não use References ao enviar e-mails para o OP do tópico, ainda não há nada para Reference, pois é o primeiro e-mail no thread.
  4. Garanta que os cabeçalhos In-Reply-To e References corretos sejam gerados com base nos registros PostReply.

Isso tem o potencial de deixar as coisas um pouco confusas em termos de encadeamento para e-mails já enviados, mas farei o meu melhor para permitir o formato do qual estamos nos afastando por um período de transição também. Obrigado por nos aturar!

3 curtidas

Apenas para esclarecer… este não seria o hostname do servidor de onde se originou, mas sim o url do site? Se for hostname, então perdemos toda a estabilidade quando 3 hosts diferentes servem o mesmo site.

1 curtida

Desculpe, sim, quero dizer o domínio do site, por exemplo, meta.discourse.org, que vem de Email::Sender.host_for(Discourse.base_url), o que já usamos.

2 curtidas

Boa ideia, não pensei nas movimentações. O :post_id é o ID da postagem (post.id) ou o número (dentro do tópico)?

Se for o ID da postagem, podemos simplificar e apenas usar \u003cpost/:post_id@:hostname\u003e, pois isso nunca mudará, então não precisamos armazenar o Message-ID, a menos que seja substituído do padrão.

Se não for… podemos usar o ID da postagem aqui? não há razão para que esta parte tenha que ser longa, ela apenas tem que ser única.

2 curtidas

É o ID real, não o número do post.

Esse é um bom ponto, \u003cpost/:post_id@:hostname\u003e provavelmente funcionará bem e evita a necessidade de uma coluna extra. Talvez para torná-lo mais específico do Discourse, possamos adicionar discourse na frente, por exemplo, \u003cdiscourse/post/543563@meta.discourse.org\u003e (tendo em mente que muitos sites não terão menção ao Discourse no nome do host). É procurar pelos pelos nesse momento.

Vou tentar pensar em maneiras de isso dar errado. Acho que se você mover um post para outro tópico e, em seguida, alguém responder ao post por e-mail, a resposta acabará no novo tópico em vez do tópico original. Talvez isso esteja bem? Outro risco é que o post seja movido para uma categoria privada, mas acho que já temos esse mesmo risco e o gerenciamos.

Apenas pensando alto, deve ficar tudo bem, cobrirei essas coisas quando testar as mudanças de qualquer maneira :+1:

2 curtidas

O argumento para incluir topic_id é que você pode quebrar deliberadamente o encadeamento se as pessoas dividirem uma postagem de um tópico em outro tópico.

Estou dividido quanto a isso. Pode ir para qualquer lado. Mas essa seria a ideia.

1 curtida

O argumento para usar apenas o ID da postagem é que ele é mais estático, o que é o que queremos, pois se você mover uma postagem para outro tópico, o ID da postagem será o mesmo em Message-ID, mas o tópico não será o mesmo.

Eu acho que se acabarmos movendo a postagem e enviando e-mails do novo tópico, a nova thread será criada corretamente no cliente de e-mail, pois as cadeias de cabeçalho References e In-Reply-To serão diferentes. De qualquer forma, farei questão de testar esse cenário e ver se ele faz o que esperamos também. Nada será mesclado ao core até que os vários cenários funcionem como esperado.

1 curtida

Com base nessas discussões adicionais @cameron-simpson, atualizei os TODOs para isto, postando-os aqui para que você receba a atualização, já que as edições do Discourse não chegarão por e-mail:

  1. Altere o formato gerado do Message-ID para ser sempre \u003cdiscourse/post/:post_id@:hostname\u003e, isso é único o suficiente, é basicamente uma reversão do que costumávamos fazer. Referir-se ao OP usará agora o ID do primeiro post em vez de apenas o ID do tópico.
  2. Se um post tiver um registro IncomingEmail associado, sempre usaremos esse Message-ID ao enviar e-mail, caso contrário, geraremos um usando o formato acima.
  3. Adicione uma nova coluna outbound_message_id aos registros Post que será preenchida por a) o Message-ID do e-mail de entrada se ele estiver criando o post ou b) o Message-ID de saída que geramos no caso de posts criados pela interface web do Discourse.
  4. Não use cabeçalhos References ou In-Reply-To ao enviar e-mails para o OP do tópico, não há nada para Referenciar ou responder ainda porque é o primeiro e-mail na thread.
  5. Garanta que os cabeçalhos In-Reply-To e References corretos sejam gerados com base nos registros PostReply.
1 curtida

Isso cobre citações também (por exemplo, uma postagem citou 10 postagens diferentes, então ela as referencia?)

1 curtida

Por Sam Saffron via Discourse Meta em 29Jul2022 02:31:

Isso cobre citações também (por exemplo, uma postagem citou 10 outras postagens, então ela as referencia?)

In-Reply-To só pode citar um antecedente, então escolha um. References pode referenciar mais de um, mas o RFC explicitamente desaconselha isso porque nem todos os aplicativos cliente podem esperar outra coisa senão uma cadeia linear desta postagem de volta ao OP.

Eu estaria de acordo com qualquer um para References, mas tenderia para o conservador. O cálculo fácil é:

  • In-Reply-To: use o message-id da primeira mensagem citada (ou qualquer citação única que você escolher com base em alguma política)
  • References: o References do mesmo único antecedente escolhido acima mais o message-id dessa mesma postagem

Isso seria estável, previsível e correto.

Abraços,
Cameron Simpson cs@cskk.id.au

2 curtidas

References é desencorajado de ser usado desta forma:

Nota: Algumas implementações analisam o campo “References:” para exibir a “thread da discussão”. Essas implementações assumem que cada nova mensagem é uma resposta a um único pai e, portanto, que elas podem percorrer para trás o campo “References:” para encontrar o pai de cada mensagem listada lá. Portanto, tentar formar um campo “References:” para uma resposta que tenha vários pais é desencorajado; como fazê-lo não é definido neste documento.

2 curtidas

Por Martin Brennan via Discourse Meta em 29Jul2022 01:57:

Com base nessas discussões adicionais @cameron-simpson, atualizei os TODOs
para isto, postando-os aqui para que você receba a atualização, já que as
edições do Discourse não chegarão por e-mail:

  1. Altere o formato do Message-ID gerado para ser sempre \u003cdiscourse/post/:post_id@:hostname\u003e, isso é único o suficiente, é basicamente um retorno ao que costumávamos fazer. Referir-se ao OP usará o ID da primeira postagem agora em vez do ID do tópico nu.
  2. Se uma postagem tiver um registro IncomingEmail associado, sempre usaremos esse Message-ID ao enviar e-mail, caso contrário, geraremos um usando o formato acima.
  3. Não use References ao enviar e-mails para o OP do tópico, não há nada para Referenciar ainda, pois é o primeiro e-mail na thread.

Eu também omitiria o In-Reply-To nos e-mails do OP.

  1. Garanta que os cabeçalhos In-Reply-To e References corretos sejam
    gerados com base nos registros PostReply.

Sim.

Pessoalmente, eu iria um passo além, tendo uma coluna para o ID da mensagem do lado do e-mail. Dessa forma, uma vez que você alocou um ID de mensagem para a postagem (do e-mail de origem, se for de e-mail, ou gerado se for da interface web), ele permanece estável, independentemente de qualquer outra coisa que possa acontecer no código agora ou depois. Ou seja, mesmo que não haja IncomingEmail, a geração do ID da mensagem acontece apenas uma vez, em vez de ser recalculada (o que, portanto, poderia mudar).

Ou seja, torne-o estável uma vez feito, armazenando-o.

Você tem uma relação IncomingEmail, pelo que parece. Talvez você tenha (ou possa usar) uma relação OutgoingEmail para o estado adicional das mensagens de e-mail de saída, feita na primeira vez que uma postagem é encaminhada por e-mail.

Eu sei que o fluxo é basicamente que isso acontece quando uma postagem é feita, mas posso imaginar alguns recursos posteriores para o usuário, como:

  • por favor, encaminhe-me e-mails para todo este tópico, agora que estou interessado
  • se ocorrer uma edição em uma postagem, considere enviar a mensagem atualizada com o mesmo ID de mensagem

A razão pela qual o segundo exemplo vem à mente é que temos mais coisas para relatar :slight_smile: Uma delas é que o Discourse parece fazer um esforço para descartar a parte citada das respostas postadas no topo para manter a postagem concisa, ou algo assim. Escrevi um longo post em “Python land” algumas semanas atrás que foi severamente truncado. Eu entrei e editei no fórum com o texto original da minha cópia pessoal. Mas um destinatário disse que tinha a coisa toda, e eu estava me perguntando se o Discourse enviava atualizações de edição como mensagens de substituição com o mesmo ID. O que seria bastante interessante, dependendo do cliente do usuário final que lida com isso.

1 curtida

Por Martin Brennan via Discourse Meta em 29Jul2022 00:36:

  1. Adicionar um novo outbound_message_id à tabela Post, para que possamos ter certeza de que a thread sobrevive mesmo que uma postagem mude de tópico ou algo assim, armazene o Message-ID aqui para ambos os casos acima.

Sim, acho que isso é importante, independentemente de como for implementado (relação ou coluna do que quer que seja). Acho que eu disse isso em seus TODOs revisados.

  1. Não usar References ao enviar e-mails para o OP do tópico, não há nada para Reference ainda porque é o primeiro e-mail na thread.
  2. Garantir que os cabeçalhos In-Reply-To e References corretos sejam gerados com base nos registros PostReply e na nova coluna outbound_message_id na tabela Post.

Isso tem o potencial de deixar as coisas em um estado um tanto obscuro em termos de thread para e-mails já enviados, mas farei o meu melhor para permitir o formato do qual estamos nos afastando por um período de transição também. Obrigado por ter paciência conosco!

Não há nada que possamos fazer pelos e-mails existentes. Apenas garanta que as coisas estejam bem encadeadas daqui para frente!

Obrigado!
Cameron Simpson cs@cskk.id.au

1 curtida

Nós temos EmailLog, mas esses registros são limpos a cada 90 dias, e eu não acho que seria uma boa opção para isso. Farei apenas isto:

1 curtida

Era sobre evitar armazená-lo de qualquer forma… mas pensando bem, o ID da postagem nunca mudará, mas o nome do host pode. Então, devemos armazená-lo imediatamente após salvar em todos os casos.

Não faria mal ter messageid como uma propriedade de cada Post, imutável para sempre…

Isso não seria uma versão diferente da mensagem? Da especificação:

O campo “Message-ID:” fornece um identificador de mensagem exclusivo que se refere a uma versão específica de uma mensagem específica. … Um identificador de mensagem pertence a exatamente uma versão de uma mensagem específica; revisões subsequentes da mensagem recebem novos identificadores de mensagem.

Então, provavelmente nosso message-id gerado deveria ser: \u003cdiscourse/post/:post_id/rev/:revision_num\u003e (possivelmente omitindo /rev/:revision_num para a primeira revisão). Isso permitiria que os destinatários de e-mail recebessem as atualizações de edição em primeiro lugar, considerando que

1 curtida

Sim, farei isso. Quanto a essas outras discussões sobre edições e revisões, acho que isso é um outro grande problema que não deveríamos abordar agora… vamos consertar nossos pecados de encadeamento primeiro :slight_smile:

1 curtida