Repositório: GitHub - overgrow/discourse-bounce-guard · GitHub
Licença: MIT
&tldr; O que você obtém: um log de erros mais limpo (as tentativas de reenvio horário para endereços inativos cessam). O Discourse deixa de enviar e-mails que não podem ser entregues. E usuários mais satisfeitos: eles conseguem acessar suas contas novamente, pois um endereço inativo é substituído por um funcional a tempo.
Se seu e-mail de saída passa por um retransmissor próprio (Postfix, Exim, a maioria dos setups auto-hospedados), você provavelmente já viu /logs se encher de pares como este:
SMTP Error Net::SMTPServerBusy with message: 450 4.1.2 <someone@gone-domain.com>: Recipient address rejected: Domain not found
Job exception: Net::SMTPServerBusy
O domínio desapareceu e não vai voltar. Mas o retransmissor responde com um código de erro temporário (450), porque, em teoria, uma consulta DNS pode falhar por um momento. O Discourse interpreta qualquer erro temporário como “tente novamente em uma hora”, e o Sidekiq continua tentando por semanas. A detecção de rejeições (bounce) do núcleo nunca é acionada. Ela só reage quando uma mensagem de rejeição retorna, seja como um e-mail (VERP) ou como um webhook do seu provedor de e-mail, e aqui a mensagem é recusada na hora, então nenhuma mensagem de rejeição jamais existe. A pontuação de rejeição permanece em zero, as tentativas continuam, e o usuário mantém um endereço que não pode receber nada, incluindo uma redefinição de senha.
Isso já surgiu aqui algumas vezes sem uma resposta integrada, por exemplo Handling emails to non-existent domain, Deactivate user with hard bounce e How to deactivate accounts of users who are not receiving emails. Tivemos a mesma dor no nosso fórum, então criamos um plugin.
O que ele faz
O Bounce Guard intercepta o caminho de envio em si e classifica cada rejeição SMTP:
- Respostas 5xx com um status aprimorado de destinatário inválido (
5.1.1,5.1.2,5.2.1e afins) contam como falhas permanentes (hard failures). - Qualquer resposta que corresponda a uma lista de frases configurável (“domain not found”, “user unknown”, …) conta como falha permanente, independentemente do código. Isso pega retransmissores que falham de forma temporária (soft-fail) em condições permanentes com um 450.
- Greylisting, caixas de entrada cheias e limites de taxa são deixados de lado. O comportamento de reenvio do núcleo permanece intacto.
As falhas permanentes registradas então sobem uma escada:
- Cada uma alimenta a pontuação de rejeição do núcleo como uma rejeição permanente (ativado por padrão). Após duas, o próprio limite do núcleo é acionado e o Discourse para de enviar e-mails para o usuário. O ruído no log termina aqui, mesmo que você nunca ative nada além disso.
- Após uma quantidade configurável de falhas distribuídas ao longo de um período configurável (padrão: 2 falhas com pelo menos 48 horas de intervalo, para que uma interrupção curta não desative ninguém), o plugin age. A ação padrão é
log_only: uma entrada no log de ações de equipe registrando que o usuário teria sido desativado. Alterne paradeactivatequando você confiar nisso. - A desativação direciona o usuário para o fluxo padrão de ativação em seu próximo login, onde a alteração do endereço é embutida. Eles verificam um e-mail funcional e continuam com sua conta intacta. O ponto é a recuperabilidade: uma conta ativa cujo único canal de recuperação é uma caixa de entrada inativa é um bloqueio à espera de acontecer.
Se seu site recebe rejeições (VERP ou webhooks), você também pode permitir que o plugin atue quando a pontuação de rejeição do núcleo ultrapassar um nível que você escolher, mais alto do que a pontuação na qual o núcleo para de enviar. Algumas regras de segurança se aplicam em todos os lugares. Staff e bots nunca são tocados. Um e-mail preso que tenta novamente a cada hora conta como uma única falha, graças a uma janela de resfriamento (cooldown). Uma falha só conta contra um usuário quando o endereço rejeitado pelo servidor é o endereço atual daquele usuário. E cada falha registrada é mantida por 90 dias para que você possa verificar o que aconteceu (consulta do Data Explorer no README).
Instalação
Instalação padrão de plugin:
hooks:
after_code:
- exec:
cd: $home/plugins
cmd:
- git clone https://github.com/discourse/docker_manager.git
- git clone https://github.com/overgrow/discourse-bounce-guard.git
Ative bounce_guard_enabled, deixe bounce_guard_action em log_only por uma ou duas semanas, revise o que ele sinaliza e, então, decida sobre a desativação. A referência de configurações está no README.
Apenas no lado do servidor, sem componentes de tema ou JS. Construído e testado contra o núcleo atual (2026.8), 31 especificações, o CI executa o fluxo padrão do discourse-plugin. Feedback é bem-vindo, especialmente formulações de rejeição de outros retransmissores que a lista de frases padrão não pega.