Репозиторий: GitHub - overgrow/discourse-bounce-guard · GitHub
Лицензия: MIT
&tldr; Что вы получаете: более чистый журнал ошибок (останавливаются почасовые повторные попытки отправки на несуществующие адреса). Discourse перестаёт отправлять почту, которая не может быть доставлена. И довольные пользователи: они могут получить доступ к своему аккаунту, потому что мёртвый адрес вовремя заменяется на рабочий.
Если ваша исходящая почта проходит через собственный релей (Postfix, Exim, большинство self-hosted конфигураций), вы, вероятно, замечали, как /logs заполняется парами вроде этой:
SMTP Error Net::SMTPServerBusy with message: 450 4.1.2 <someone@gone-domain.com>: Recipient address rejected: Domain not found
Job exception: Net::SMTPServerBusy
Домен исчез и не вернётся. Но релей отвечает временным кодом ошибки (450), потому что теоретически поиск в DNS может временно завершиться ошибкой. Discourse трактует любую временную ошибку как «повторить через час», и Sidekiq продолжает попытки в течение недель. Встроенное обнаружение отскоков (bounce) в ядре никогда не срабатывает. Оно реагирует только тогда, когда возвращается сообщение об отскоке, либо в виде письма (VERP), либо в виде вебхука от вашего почтового провайдера. Но в данном случае сообщение отклоняется на месте, поэтому сообщение об отскоке никогда не существует. Очки отскока остаются нулевыми, повторные попытки продолжаются, и пользователь остаётся с адресом, который не может получать ничего, включая сброс пароля.
Эта тема поднималась здесь несколько раз без встроенного решения, например, Обработка писем на несуществующий домен, Деактивация пользователя при жёстком отскоке и Как деактивировать аккаунты пользователей, которые не получают почту. У нас на форуме была та же проблема, поэтому мы создали плагин.
Что он делает
Bounce Guard подключается к самому пути отправки и классифицирует каждый отказ SMTP:
- Ответы 5xx с расширенным статусом «неверный получатель» (
5.1.1,5.1.2,5.2.1и аналогичные) считаются жёсткими сбоями. - Любой ответ, соответствующий настраиваемому списку фраз («домен не найден», «пользователь неизвестен», …), считается жёстким сбоем независимо от кода. Это позволяет ловить релей, которые отвечают 450 на постоянные условия.
- Грейлистинг, переполненные ящики и ограничения частоты не затрагиваются. Поведение ядра при повторных попытках остаётся неизменным.
Записанные жёсткие сбои затем поднимаются по лестнице:
- Каждый из них добавляется к очкам отскока ядра как жёсткий отскок (включено по умолчанию). После двух срабатывает собственный порог ядра, и Discourse перестаёт отправлять письма пользователю. Шум в журнале заканчивается здесь, даже если вы ничего больше не включите.
- После настраиваемого количества сбоев, распределённых на настраиваемый период (по умолчанию: 2 сбоя с интервалом не менее 48 часов, чтобы кратковременный сбой не мог «выключить» никого), плагин действует. Действие по умолчанию —
log_only: запись в журнале действий персонала, фиксирующая, что пользователь был бы деактивирован. Переключитесь наdeactivate, когда будете уверены в этом. - Деактивация направляет пользователя через стандартный процесс активации при следующем входе, где смена адреса встроена в процесс. Они подтверждают рабочую почту и продолжают работу со своим аккаунтом, который остаётся целым. Суть в восстанавливаемости: активный аккаунт, единственным каналом восстановления которого является мёртвый почтовый ящик, — это ожидаемая блокировка.
Если ваш сайт действительно получает отскоки (VERP или вебхуки), вы также можете позволить плагину действовать, когда очки отскока ядра превысят уровень, который вы выберете, более высокий, чем тот, при котором ядро перестаёт отправлять. Везде действуют несколько правил безопасности. Персонал и боты никогда не затрагиваются. Одно застрявшее письмо, которое повторяется каждый час, считается одним сбоем благодаря окну охлаждения. Сбой учитывается для пользователя только в том случае, если адрес, отклонённый сервером, является текущим адресом этого пользователя. И каждый записанный сбой хранится в течение 90 дней, чтобы вы могли проверить, что произошло (запрос Data Explorer в README).
Установка
Стандартная установка плагина:
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
Включите bounce_guard_enabled, оставьте bounce_guard_action на log_only на неделю-две, просмотрите, что он помечает, а затем решите вопрос о деактивации. Справочник по настройкам находится в README.
Только серверная часть, без тем или JS-компонентов. Собран и протестирован на текущем ядре (2026.8), 31 спецификация, CI запускает стандартный workflow discourse-plugin. Будем рады обратной связи, особенно фразам об отказе от других релей, которые пропускает список фраз по умолчанию.