Plugin Bounce Guard de Discourse

Repositorio: GitHub - overgrow/discourse-bounce-guard · GitHub
Licencia: MIT

&tldr; Lo que obtienes: un registro de errores más limpio (dejan de realizarse reintentos horarios a direcciones inactivas). Discourse deja de enviar correos que no pueden ser entregados. Y usuarios más felices: pueden recuperar el acceso a su cuenta, ya que una dirección inactiva se reemplaza por una funcional a tiempo.

Si tu correo saliente pasa a través de tu propio reenvío (Postfix, Exim, la mayoría de las configuraciones autoalojadas), probablemente hayas visto que /logs se llena con 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

El dominio ha desaparecido y no va a volver. Pero el reenvío responde con un código de error temporal (450), porque en teoría una búsqueda de DNS puede fallar por un momento. Discourse interpreta cualquier error temporal como “inténtalo de nuevo en una hora”, y Sidekiq sigue intentándolo durante semanas. La detección de rebotes del núcleo nunca se activa. Solo reacciona cuando un mensaje de rebote regresa, ya sea como correo electrónico (VERP) o como webhook de tu proveedor de correo, y aquí el mensaje se rechaza en el acto, por lo que nunca existe un mensaje de rebote. La puntuación de rebote se mantiene en cero, los reintentos continúan y el usuario mantiene una dirección que no puede recibir nada, incluida una restablecimiento de contraseña.

Esto ha surgido aquí varias veces sin una respuesta integrada, por ejemplo Manejo de correos a dominios inexistentes, Desactivar usuario con rebote duro y Cómo desactivar cuentas de usuarios que no están recibiendo correos. Tuvimos el mismo problema en nuestro foro, así que construimos un plugin.

Qué hace

Bounce Guard se integra en la ruta de envío y clasifica cada rechazo de SMTP:

  • Las respuestas 5xx con un estado ampliado de destinatario incorrecto (5.1.1, 5.1.2, 5.2.1 y similares) cuentan como fallos duros.
  • Cualquier respuesta que coincida con una lista de frases configurable (“domain not found”, “user unknown”, …) cuenta como duro sin importar el código. Esto captura reenvíos que fallan de forma suave ante condiciones permanentes con un 450.
  • El greylisting, las bandejas de entrada llenas y los límites de tasa se dejan intactos. El comportamiento de reintento del núcleo no se modifica.

Los fallos duros registrados luego ascienden por una escalera:

  1. Cada uno alimenta la puntuación de rebote del núcleo como un rebote duro (activado por defecto). Después de dos, el propio umbral del núcleo se activa y Discourse deja de enviar correos al usuario. El ruido en el registro termina aquí, incluso si nunca activas nada más.
  2. Después de un número configurable (predeterminado 3) distribuido en un período configurable (predeterminado 48 horas, para que una interrupción breve no desactive a nadie), el plugin actúa. La acción predeterminada es log_only: una entrada en el registro de acciones del personal que registra que el usuario habría sido desactivado. Cambia a deactivate cuando confíes en ello.
  3. La desactivación dirige al usuario a través del flujo estándar de activación en su próximo inicio de sesión, donde cambiar la dirección está integrado. Verifican un correo electrónico funcional y continúan con su cuenta intacta. El punto es la recuperabilidad: una cuenta activa cuyo único canal de recuperación es una bandeja de entrada inactiva es un bloqueo a la espera de ocurrir.

Si tu sitio recibe rebotes (VERP o webhooks), también puedes permitir que el plugin actúe cuando la puntuación de rebote del núcleo supere un nivel que elijas, más alto que la puntuación a la que el núcleo deja de enviar. Se aplican algunas reglas de seguridad en todas partes. El personal y los bots nunca se tocan. Un correo atascado que se reintenta cada hora cuenta como un solo fallo, gracias a una ventana de enfriamiento. Un fallo solo cuenta contra un usuario cuando la dirección que el servidor rechazó es la dirección actual de ese usuario. Y cada fallo registrado se conserva durante 90 días para que puedas verificar qué sucedió (consulta de Data Explorer en el README).

Instalación

Instalación estándar 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

Activa bounce_guard_enabled, deja bounce_guard_action en log_only durante una o dos semanas, revisa lo que marca y luego decide sobre la desactivación. La referencia de configuración está en el README.

Solo del lado del servidor, sin componentes de tema o JS. Construido y probado contra el núcleo actual (2026.8), 31 especificaciones, CI ejecuta el flujo de trabajo estándar de discourse-plugin. Se agradece la retroalimentación, especialmente las frases de rechazo de otros reenvíos que la lista de frases predeterminada no captura.