Complemento Bounce Guard para 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 por 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 consulta de DNS puede fallar momentáneamente. 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 regresa un mensaje de rebote, ya sea como correo electrónico (VERP) o como webhook de tu proveedor de correo, y aquí el mensaje es rechazado 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 conserva 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 reciben 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) se cuentan como fallos duros.
  • Cualquier respuesta que coincida con una lista de frases configurable (“domain not found”, “user unknown”, …) se cuenta como fallo duro sin importar el código. Esto captura reenvíos que fallan suavemente condiciones permanentes con un 450.
  • El greylisting, las bandejas de entrada llenas y los límites de tasa se dejan solos. El comportamiento de reintento del núcleo no se toca.

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 no activas nada más.
  2. Después de una cantidad configurable de fallos distribuidos en un periodo configurable (por defecto: 2 fallos con al menos 48 horas de diferencia, para que una corta interrupción no desactive a nadie), el plugin actúa. La acción por defecto 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 te fíes de ello.
  3. La desactivación dirige al usuario a través del flujo de activación estándar en su próximo inicio de sesión, donde el cambio de 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 reintenta cada hora se cuenta como un solo fallo, gracias a una ventana de enfriamiento. Un fallo solo se 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 por defecto no captura.

2 Me gusta