Код входа уже виден в Rspamd, а в ящике mailcow появляется через полчаса: как ускорить greylisting для важных писем
Если в истории Rspamd письмо с кодом входа появляется почти сразу, а в ящике оно появляется через 20–40 минут — дело не в mailcow и не в SOGo, а в greylisting: сервер-отправитель получил временный отказ и должен сам повторить попытку. Разбираю механизм, пороги actions.conf и рабочий способ ускорить доставку для нужного отправителя, не выключая защиту целиком.
Симптом: письмо «принято» в Rspamd — это не то же самое, что «доставлено в ящик»
Служба доставки «Шаговая доставка», 38 рабочих мест, обратилась с жалобой на почту, которую мы у них же и администрируем: диспетчеры каждое утро входят в личный кабинет платёжного агрегатора, через который курьеры закрывают наличные и карточные платежи, а вход там — по одноразовому коду, который агрегатор присылает на корпоративную почту. Код живёт 10 минут. Раз в несколько дней код не успевал: диспетчер запрашивал повторную отправку, потом ещё раз, а через полчаса в ящике вдруг появлялись сразу два-три «просроченных» письма с кодом. Первая версия у их же штатного админа была логичной — «завис SOGo» или «тормозит IMAP», — но я к тому моменту уже три года администрируем корпоративную почту на mailcow у этого клиента, и знал, что дело не там.
Первым делом я открыл не почтовый ящик, а веб-интерфейс Rspamd — раздел History. И увидел ровно то, что обычно вижу в таких жалобах: письмо от агрегатора было принято сервером и оценено движком антиспама практически мгновенно после отправки, с действием soft reject. А в ящик оно попало только следующей попыткой отправителя, спустя те самые 20–40 минут. Это и есть та путаница, из-за которой такие тикеты почти всегда сначала уходят не туда: администратор смотрит в SOGo или в лог Dovecot, ищет тормоза на стороне доставки в ящик, хотя на самом деле письмо в этот момент ещё физически не покидало сервер отправителя — оно сидит у него в очереди на повторную отправку.
Дальше в статье — как устроен greylisting в связке mailcow и Rspamd, почему задержка почти никогда не зависит от вашего сервера, и как я закрыл эту жалобу конкретно у «Шаговой доставки»: не отключением защиты для всех, а точечным исключением для одного отправителя.
Что такое greylisting технически и почему это не баг, а расчётливая задержка
Greylisting в Rspamd — это не блокировка и не спам-фильтр в привычном смысле: модуль отвечает отправителю временным отказом (soft reject), который на уровне SMTP обычно выглядит как код 4xx — часто это что-то в духе 451 4.7.1, хотя точный код и текст различаются в зависимости от MTA и версии стека. Смысл в следующем: письмо ещё не принято окончательно, сервер-отправитель должен сам повторить попытку через некоторое время. По документации модуля greylisting Rspamd считает два хэша: «мета» — из envelope-from, отсортированного списка получателей и IP отправителя с маской — и хэш данных — из тела письма (до max_data_len), темы, получателей и MIME-From. Если ни один из них ещё не встречался и не «выдержан» дольше timeout, письмо получает мягкий отказ. Для простоты дальше я называю мета-хэш «триплетом» IP/отправитель/получатель.
По умолчанию у модуля в Rspamd два ключевых параметра: timeout — сколько нужно выждать перед тем, как повторная попытка будет принята (5 минут по умолчанию), и expire — как долго Redis хранит запись о том, что этот триплет уже видели (1 день по умолчанию). У mailcow в поставке эти два параметра не переопределены — в файле data/conf/rspamd/local.d/greylist.conf заданы только whitelisted_ip (внутренняя карта для собственных хостов пересылки mailcow, не для клиентских исключений), более узкая маска ipv4_mask = 24 вместо стокового 19 у чистого Rspamd, ipv6_mask = 64 и текст сообщения об отказе «Greylisted, please try again later» — сами тайминги timeout/expire остаются стоковыми.
Смысл механизма прагматичный, а не карательный: примитивные спам-боты почти никогда не реализуют полноценную очередь повторной отправки — они выстрелили письмом один раз и забыли, поэтому временный отказ отсекает заметную часть мусора почти бесплатно, ещё до того, как письмо дошло до более тяжёлых проверок вроде байесовского классификатора или антивируса. Честный почтовый сервер, наоборот, обязан по стандарту SMTP уметь ставить письмо в очередь и повторять попытку — именно на этом свойстве и держится вся идея greylisting.
Пороги в actions.conf решают, попадёт письмо под задержку вообще или нет
Greylisting срабатывает не для всех писем подряд — а только для тех, что набрали достаточно баллов по оценке Rspamd. В файле data/conf/rspamd/local.d/actions.conf у mailcow заданы три порога: greylist = 7, add_header = 8 и reject = 15. То есть письмо со счётом ниже 7 вообще минует greylisting — доставляется сразу, без задержки. Всё, что набрало от 7 до 15 баллов, получает мягкий отказ: модуль greylist сравнивает счёт с порогом greylist (или greylist_min_score, если он задан) и откладывает любое письмо выше него, кроме отклоняемых. Разница между коридорами проявляется уже после повторной попытки: письмо на 7–8 баллов ляжет во «Входящие», а на 8–15 — с заголовком спама, то есть в mailcow в папку Junk. Всё, что набрало 15 баллов и больше, отклоняется окончательно, независимо от истории greylisting.
Проблема в том, что транзакционные письма от финансовых и платёжных сервисов — коды входа, уведомления о списаниях, ссылки на подтверждение операций — довольно часто оказываются чуть выше порога 7, а не заметно ниже. Причины типовые: у отправителя используется относительно новый или редко засвечивающийся почтовый релей (репутация IP ещё не «прогрета»), в письме есть внешние ссылки и HTML-разметка, характерная скорее для рассылок, чем для личной переписки, а SPF/DKIM выравнивание настроено не идеально строго. Ни одного из этих признаков по отдельности недостаточно для блокировки, но в сумме они вполне могут дотянуть письмо до порога greylist.
У «Шаговой доставки» именно так и оказалось: письма с кодом от платёжного агрегатора стабильно набирали 7–8 баллов, то есть чуть выше порога greylist — не мусор, не спам, но и не «чистое» письмо с нулевым счётом. Это объясняло, почему проблема не была постоянной: часть писем проходила без задержки, а часть — с задержкой, в зависимости от небольших колебаний в оценке конкретного письма.
Почему задержка почти всегда на стороне отправителя, а не вашего mailcow
Ключевой момент, который стоит объяснять клиенту сразу, чтобы не искали проблему не там: после того как Rspamd отправил soft reject, дальнейшая судьба письма целиком в руках сервера-отправителя. Именно он решает, когда повторить попытку и сколько раз. На форуме поддержки mailcow разбирался почти идентичный случай — администратор видел в истории Rspamd, что письмо было принято практически мгновенно, но в почтовом ящике оно появлялось только через 30 с лишним минут. В обсуждении справедливо заметили: это не проблема mailcow, а особенность конкретного отправляющего сервера, у которого свой график повторных попыток — например, стандартная очередь Postfix по умолчанию повторяет отправку примерно через 5 минут, но далеко не все MTA в мире настроены так же аккуратно, и часть из них ждёт значительно дольше.
Отдельно замечу: в том же треде хорошо видно и второе следствие порогов — письмо со счётом 12 сначала отлёживалось в greylisting, а после повтора ещё и уходило в «Спам». Правильность настройки SPF, DKIM и DMARC у отправителя не отменяет greylisting сама по себе — эти механизмы решают задачу подлинности письма и репутации домена, а не задержки первой попытки. Я разбирал эту связку отдельно в статье про SPF, DKIM и DMARC для бизнеса: аутентификация снижает итоговый балл письма и в перспективе может увести его ниже порога greylist, но моментального эффекта на уже отправленное письмо она не даёт — задержка первой попытки всё равно зависит от того, как быстро отправитель повторит запрос.
У «Шаговой доставки» была ещё одна деталь, которая объясняла именно повторяемость проблемы день за днём, а не разовый сбой: expire в greylisting по умолчанию равен суткам и продлевается при каждом успешном прохождении. Диспетчеры заходили в кабинет платёжного агрегатора один раз в начале смены, примерно в одно и то же время. Это значит, что запись о вчерашнем «триплете» (IP агрегатора, адрес отправителя, адрес диспетчера) к утру оказывалась на самой границе суток — если вход случался на полчаса позже вчерашнего, она успевала истечь, и Rspamd воспринимал утренний код входа как письмо от нового, ранее не встречавшегося отправителя — снова greylisting, снова задержка на стороне их сервера. Проблема повторялась не потому, что что-то было настроено криво, а потому что типичный паттерн использования (один вход в сутки) идеально совпадал с типичным сроком жизни записи о триплете.
Как проверить, что дело именно в greylisting, а не в потерянном письме
Прежде чем что-либо менять в конфиге, я всегда сначала подтверждаю диагноз, а не гадаю. Открываю веб-интерфейс Rspamd (в mailcow он доступен по адресу https://<hostname>/rspamd/, пароль к нему задаётся в админке mailcow) и смотрю раздел History — там видно каждое обработанное письмо, набранный счёт и итоговое действие. Если по нужному письму стоит действие greylist, значит диагноз подтверждён: письмо не потеряно и не заблокировано, оно просто ждёт повторной попытки от отправителя.
Второй источник — логи самого контейнера rspamd, которые можно поднять прямо из консоли сервера:
cd /opt/mailcow-dockerized
docker compose logs --tail=200 rspamd-mailcow | grep -i greylistВ логе видно и сам факт greylisting конкретного письма, и, если сервер отправителя уже успел повторить попытку, запись о том, что тот же триплет был распознан и принят. Это различие принципиально: если по письму вообще нет записи в истории Rspamd — значит проблема раньше, на уровне сетевой доступности или DNS, и разбираться нужно совсем в другом месте, а не в настройках greylisting.
У «Шаговой доставки» история Rspamd подтвердила диагноз за пять минут: письма с кодом от агрегатора действительно уходили под soft reject, и по логам было видно, что повторная попытка от их стороны приходила не через стандартные 5 минут, а заметно позже — иногда через 25–35 минут, что и съедало весь запас времени жизни одноразового кода.
Точечное ускорение: whitelist для конкретного отправителя, а не отключение защиты для всех
У mailcow есть официально задокументированный способ полностью отключить greylisting: в файле data/conf/rspamd/local.d/greylist.conf добавить строку enabled = false; и перезапустить сервис docker compose restart rspamd-mailcow. Но сама документация прямо предупреждает: «We do NOT recommend deactivating greylisting» — не рекомендуем отключать. И это разумно: греилистинг отсекает заметную часть примитивного спама почти бесплатно, ещё до того, как письмо доходит до более тяжёлых и дорогих проверок. Отключать его для всего сервера ради одного капризного отправителя — значит открывать более широкую дыру, чем нужно для решения конкретной проблемы.
Правильный масштаб решения — не «сервер», а «конкретный отправляющий почтовый сервер». В самом модуле greylisting у Rspamd для этого предусмотрен отдельный, независимый от whitelisted_ip механизм: параметр whitelist_domains_url в базовой конфигурации модуля уже смотрит на два файла — local.d/greylist-whitelist-domains.inc и local.d/maps.d/greylist-whitelist-domains.inc. По умолчанию эти файлы отсутствуют и ни на что не влияют. Важная тонкость, которую я проверил по исходному коду модуля: карта сверяется не с доменом в адресе From, а с именем хоста отправляющего сервера (его rDNS/PTR) — полностью или по домену второго уровня. Если агрегатор шлёт коды с mx-out.agregator-domen.example, в файл пишется agregator-domen.example, и письма с его серверов перестанут попадать под greylisting — при этом сама защита продолжает работать для всего остального почтового потока. Важно не путать этот файл с уже существующим whitelisted_ip в local.d/greylist.conf — тот занят служебной картой собственных хостов пересылки mailcow, трогать его не нужно.
Поэтому сначала смотрю в History Rspamd или в заголовке Received, какое имя хоста у отправляющего сервера. Если коды уходят через крупный сервис рассылок, в файл попал бы домен этого сервиса — и исключение получили бы все его клиенты, включая спамеров; в такой ситуации я лучше выясняю у отправителя выделенный IP или хост. Дальше — одна короткая команда и один файл:
cd /opt/mailcow-dockerized
echo "agregator-domen.example" >> data/conf/rspamd/local.d/greylist-whitelist-domains.inc
docker compose restart rspamd-mailcowПосле перезапуска я всегда проверяю результат не «на глаз», а по факту: отправляю тестовое письмо с нужного домена (или прошу источник прислать реальный код повторно) и смотрю в History Rspamd, что по новому письму больше нет действия greylist. Остальные модули — RBL, DKIM/SPF, байесовский классификатор, антивирус — продолжают проверять это письмо как обычно, меняется только одна конкретная задержка для одного конкретного отправителя. Это и есть разница между «выключить защиту» и «убрать конкретную помеху», о которой я подробно писал, разбирая уязвимость в шаблоне mailcow: точечное исключение почти никогда не стоит того риска, который несёт отключение целого защитного слоя.
Кейс «Шаговая доставка»: как я закрыл жалобу диспетчеров за один день, не трогая остальную почту
После подтверждения диагноза по истории Rspamd весь фикс занял меньше часа, вместе с проверкой. Я проверил, что PTR отправляющих серверов агрегатора лежит в его собственном домене, и добавил этот домен в local.d/greylist-whitelist-domains.inc, перезапустил rspamd-mailcow, и попросил диспетчера смены запросить код входа ещё раз для контроля. Письмо появилось в ящике практически сразу — задержки, которую видели раньше, не было. За следующую неделю мониторил историю Rspamd по этому домену отдельно: ни одного действия greylist, при этом остальной входящий поток «Шаговой доставки» продолжал проходить через полный набор проверок как обычно.
Отдельно я объяснил их штатному администратору, почему проблема была именно повторяющейся, а не разовой: ежедневный однократный вход означал, что суточный expire записи о триплете каждый раз успевал истечь до следующей попытки входа, и Rspamd заново воспринимал легитимного отправителя как незнакомого. Это частый паттерн именно для сервисов с редкими, но критичными по времени письмами — банковские и платёжные уведомления, коды входа, разовые ссылки подтверждения — и стоит держать его в голове при любом похожем разборе, а не считать совпадением.
Полное отключение greylisting мы не рассматривали вообще — это было бы решением не той проблемы: у «Шаговой доставки» ни разу не было массового спама через этот сервер именно потому, что защита работала штатно для всех остальных отправителей. В регулярный регламент обслуживания клиента я добавил ежеквартальную сверку файла greylist-whitelist-domains.inc — чтобы список исключений не разрастался бесконтрольно и не превращался со временем в тот же самый полный отказ от защиты, только оформленный построчно. Этот пункт идёт в одном списке с остальной рутиной, которую я описывал в статье про регламент эксплуатации mailcow — бэкапы, обновления, антиспам-настройки. А за самим фактом доступности почтового сервера и связанных сервисов я слежу через self-hosted мониторинг доступности — отдельная проверка на то, что подобные истории с задержками не превращаются в полноценный простой.
Частые вопросы
Это ошибка настройки mailcow, которую нужно исправлять массово?
Нет, это штатный механизм антиспама, а не дефект. Официальная документация mailcow прямо не рекомендует отключать greylisting целиком: он дёшево отсекает часть спам-ботов, не умеющих повторять отправку. Задержка для конкретного легитимного отправителя решается точечным исключением, а не отключением всей защиты.
Почему один и тот же отправитель греилистится снова и снова, а не один раз навсегда?
Потому что запись о связке «IP отправителя (с маской) + адрес отправителя + получатели» в Redis хранится ограниченное время (по умолчанию сутки с последнего прохождения, параметр expire в конфигурации модуля). Если отправитель присылает письма реже, чем раз в сутки, — например, диспетчер входит в сервис раз в смену, — каждая следующая попытка снова воспринимается как письмо от незнакомого сервера.
Можно просто поднять порог greylist в actions.conf, чтобы меньше писем попадало под задержку?
Можно, но это грубее, чем нужно: порог greylist в actions.conf действует глобально для всех входящих писем, а не для конкретного отправителя. Поднимая его, вы одновременно ослабляете greylisting для всего потока, а не только для нужного сервиса. Для точечной задачи правильнее внести домен отправляющего сервера (по его rDNS) в greylist-whitelist-domains.inc, а не менять общий порог.
Опасно ли добавлять отправителя в whitelist_domains для greylisting?
Это снимает только один конкретный защитный слой для серверов с этим доменом в rDNS (не для адреса From!) — остальные проверки Rspamd (RBL, репутация, SPF/DKIM/DMARC, байесовский классификатор, антивирус) продолжают работать как обычно. Риск невелик при условии, что список исключений периодически пересматривается и не превращается в бесконтрольно растущий перечень.
Нужно ли останавливать весь mailcow, чтобы применить изменение?
Нет. Достаточно перезапустить один контейнер — docker compose restart rspamd-mailcow. Полная остановка стека (docker compose down) для этой правки не требуется, в отличие, например, от операций с файлами репозитория при обновлении.
Как отличить greylisting от реально потерянного письма?
Смотрите историю Rspamd (раздел History в веб-интерфейсе) или лог контейнера rspamd-mailcow. Если по письму есть запись с действием greylist — оно не потеряно, а ждёт повторной попытки от сервера отправителя. Если записи о письме нет вообще, причина в другом месте — сетевой доступности, DNS или настройках самого отправителя.
Источники
- mailcow docs — Disable Greylisting — Проверил официально документированный способ полного отключения: путь data/conf/rspamd/local.d/greylist.conf, строка enabled = false;, команда docker compose restart rspamd-mailcow, и прямую цитату документации против отключения («We do NOT recommend deactivating greylisting»). https://docs.mailcow.email/manual-guides/Rspamd/u_e-rspamd-disable-greylisting/
- mailcow-dockerized (GitHub) — data/conf/rspamd/local.d/actions.conf и local.d/greylist.conf — Проверил напрямую по исходным файлам репозитория точные пороги actions.conf (greylist = 7; add_header = 8; reject = 15;) и фактическое содержимое поставляемого greylist.conf (whitelisted_ip = forwardinghosts.php, ipv4_mask = 24, ipv6_mask = 64, message). https://github.com/mailcow/mailcow-dockerized/blob/master/data/conf/rspamd/local.d/actions.conf
- Rspamd docs — Greylisting module — Проверил принцип работы (soft reject как временный SMTP-отказ, механизм триплета IP/From/To), параметры timeout (5 минут по умолчанию) и expire (1 день по умолчанию), а также назначение whitelisted_ip и whitelist_domains_url. https://docs.rspamd.com/modules/greylisting/
- rspamd (GitHub) — conf/modules.d/greylist.conf и src/plugins/lua/greylist.lua — Проверил стоковые значения (expire = 1d, timeout = 5min, ipv4_mask = 19, action = soft reject), пути whitelist_domains_url, а по коду модуля — что карта сверяется с hostname (rDNS) отправителя или его eSLD, а greylisting применяется к любому письму со счётом не ниже порога greylist, кроме reject. https://github.com/rspamd/rspamd/blob/master/src/plugins/lua/greylist.lua
- mailcow docs — Tweaks: Spam filter thresholds — Сверил контекст, в котором mailcow описывает работу с actions.conf и порогами score для greylist/add_header/reject, и предупреждение о том, что персональные настройки пользователей не перезаписываются глобальными изменениями. https://docs.mailcow.email/manual-guides/Rspamd/u-e-rspamd-tweaks/
- mailcow community forum — Slow inbox delivery (тред #3668) — Проверил реальный кейс с точно такой же симптоматикой: письмо видно в Rspamd почти мгновенно, а в ящике появляется через 30+ минут; в обсуждении подтверждено, что скорость повторной отправки зависит от настроек MTA отправителя (пример — стандартная очередь Postfix с повтором примерно через 300 секунд), а не от сервера mailcow. https://community.mailcow.email/d/3668-slow-inbox-delivery



