mailcow пишет, что карантин отключён, а Rspamd уже отклоняет письма: где на самом деле принимается решение
Если mailcow пишет, что карантин отключён, а Rspamd уже отклоняет письма, — это не противоречие: решение reject Rspamd принимает по своим порогам в actions.conf, а карантин лишь хранит копии. Разбираю, как эти механизмы связаны, почему без карантина отклонённое письмо исчезает бесследно и какие два поля включить, чтобы не терять легитимную почту.
Симптом: письма уже отклоняются, а карантин «выключен»
Классическая ситуация при настройке антиспама после перехода на корпоративную почту на mailcow: администратор видит в логах Rspamd, что письмо получило высокий спам-балл и было отклонено на SMTP-уровне — отправитель получил bounce, письмо не дошло. Он открывает раздел «Карантин», чтобы посмотреть отклонённое сообщение и убедиться, что это не ложное срабатывание, а там сообщение: «The current system configuration disables the quarantine functionality. Please set "retentions per mailbox" and a "maximum size" for quarantine elements».
Возникает логичный, но неверный вопрос: если карантин отключён, откуда вообще взялось решение отклонить письмо? Ответ в том, что карантин в mailcow — это не переключатель фильтрации, а отдельная витрина для хранения копий уже принятых решений. Отключённый карантин не значит, что Rspamd перестаёт фильтровать почту — значит только то, что решения Rspamd негде посмотреть и восстановить задним числом.
Кто на самом деле решает судьбу письма — Rspamd, а не карантин
В mailcow фильтрацию выполняет Rspamd: он присваивает письму балл по символам и сравнивает его с порогами действий. Глобальные пороги лежат в data/conf/rspamd/local.d/actions.conf, и в штатной поставке там ровно три строки: reject = 15;, add_header = 8;, greylist = 7;. От 7 баллов — greylist (soft reject: легитимные серверы повторят отправку через несколько минут, спам-боты обычно нет), от 8 — add_header (письмо принимается с пометкой и уходит в папку «Спам»), от 15 — reject (письмо не принимается вовсе). После правки файла нужен docker compose restart rspamd-mailcow. И учтите: каждый пользователь может сдвинуть свои пороги в личных настройках спам-фильтра, поэтому для конкретного ящика reject может наступать раньше.
Порог reject — решение уровня SMTP-диалога: Rspamd возвращает Postfix действие «reject», и письмо отклоняется до записи в ящик. Копию такого письма Rspamd отдельно передаёт в mailcow через модуль metadata_exporter: правило QUARANTINE отправляет письма с действиями reject, add header и rewrite subject в обработчик pipe.php, который и складывает их в таблицу quarantine базы. Исключение — письма, попавшие под глобальные чёрные списки mailcow: их копия не сохраняется. А уже pipe.php смотрит на настройки карантина — и если они нулевые, ничего не пишет. Решение отклонить письмо от этих полей не зависит никак.
Отсюда полезное следствие для диагностики. Если вы видите в истории Rspamd (раздел «Rspamd history» в панели или docker compose logs rspamd-mailcow) действие reject, а в карантине пусто — это не значит, что фильтр «глючит». Это значит, что звено «Rspamd → pipe.php → таблица quarantine» либо отключено настройками, либо отбросило письмо по размеру. В логе PHP-FPM (docker compose logs php-fpm-mailcow) во втором случае будет строка QUARANTINE: Message too large с размером письма и лимитом.
Что означают Retentions per mailbox, Maximum size и Maximum age на самом деле
Эти три поля — глобальные настройки администратора (раздел «Конфигурация» → «Карантин»), а не свойства отдельного ящика, и управляют они не фильтрацией, а хранением копий. Retentions per mailbox — это количество элементов, которое карантин держит на один ящик, а не срок в днях. При новом элементе сверх лимита самые старые копии этого получателя удаляются. Ноль, как прямо сказано во встроенной подсказке, означает «неактивно»: копии не хранятся, и отклонённое письмо исчезает безвозвратно — тела письма нигде не остаётся.
Maximum size задаётся в мебибайтах (MiB): письма крупнее значения в карантин не попадают. Подсказка в интерфейсе отдельно подчёркивает, что 0 не означает «без ограничения» — при нуле любое письмо оказывается «слишком большим», и карантин фактически выключен даже при ненулевом Retentions per mailbox. Именно поэтому сообщение о выключенном карантине просит задать оба поля. Maximum age задаётся в днях, и значение должно быть не меньше 1 дня; по этому сроку старые элементы вычищаются независимо от лимита по количеству.
Почему стоит включить карантин прежде, чем разбираться в весах Rspamd
Практический вывод из этой путаницы простой: карантин стоит включать заранее, ещё до того, как понадобится разбирать первую жалобу на пропавшее письмо. Без сохранённой копии отклонённого сообщения расследование ложного срабатывания превращается в гадание по одним только записям лога Rspamd — там будет видно, какие символы сработали и какой балл получило письмо, но самого текста и вложений уже не будет, и попросить отправителя переслать письмо повторно — единственный оставшийся вариант, а это не всегда возможно (например, если отправитель — автоматическая система уведомлений, которая не повторяет отправку).
Разумные стартовые значения для небольшой компании: Retentions per mailbox — 20–50 элементов на ящик (при ощутимом спам-потоке больше, иначе старые копии вытесняются за день-два), Maximum size — с запасом под типичные вложения, например 25–30 MiB, если лимит сообщения в Postfix не больше, Maximum age — 14–30 дней, чтобы успеть заметить жалобу пользователя и найти письмо. Заодно включите пользователям уведомления о карантине (категория «Rejected» — отклонённые, «Junk folder» — ушедшие в «Спам»). И только после этого, не путая с включением карантина, пересматривайте сами пороги reject/add_header/greylist, если ложных срабатываний много.
Если после включения карантина отклонённое письмо всё равно не находится
Отдельная категория обращений — карантин включили, Maximum size и Maximum age настроили, но конкретное письмо, отклонённое ещё до включения витрины, найти не удаётся. Это ожидаемо: включение карантина не восстанавливает задним числом уже потерянные до этого момента письма — сохраняются только те, что были отклонены Rspamd после того, как настройки вступили в силу.
Для проверки, что уведомления о новых карантинных письмах вообще доходят до пользователей (а не теряются где-то между Rspamd и почтовым ящиком администратора), в документации mailcow есть отдельная отладочная команда, принудительно сбрасывающая флаг уведомления и запускающая рассылку заново:
docker compose exec dovecot-mailcow bash
mysql -umailcow -p$DBPASS mailcow -e "update quarantine set notified = 0;"
redis-cli -h redis DEL Q_LAST_NOTIFIED
quarantine_notify.pyЭто действие — целиком отладочное, в норме уведомления о новых карантинных сообщениях запускаются автоматически по расписанию и настройкам конкретного ящика, вручную запускать скрипт нужно только при диагностике, а не как регулярную операцию.
Второй частый случай — письмо было отклонено уже после включения карантина, но его нет. Тут три проверки. Первая — размер: если письмо крупнее Maximum size, в логе php-fpm-mailcow будет QUARANTINE: Message too large. Вторая — вытеснение: при маленьком Retentions per mailbox и активном спам-потоке нужное письмо могло уже уйти, уступив место более новым. Третья — глобальный чёрный список: письма, отклонённые по нему, в карантин не сохраняются по замыслу. Если все три проверки чистые, смотрите историю Rspamd — возможно, письмо вообще не было отклонено, а ушло в «Спам» или потерялось у отправителя.
Кейс «СтартХаб»: спам-фильтр резал письма от партнёров без следа
У клиента — инкубатор стартапов «СтартХаб», 36 рабочих мест. После перехода на mailcow администратор сразу увидел в истории Rspamd регулярные отклонения писем от одного из партнёрских фондов: рассылка с обзором грантов набирала балл выше порога reject из-за массовой рассылочной инфраструктуры отправителя (общие IP-пулы, ссылки с трекингом). Карантин при этом был выключен — Retentions per mailbox и Maximum size стояли на нуле с момента установки, потому что администратор решил: «раз спама пока немного, хранить нечего».
Когда сотрудник пожаловался, что письмо с приглашением на питч-сессию от того же фонда не дошло, восстановить его было уже нечем — только запись в истории Rspamd о факте отклонения и балле, без текста письма. Пришлось просить отправителя переслать письмо повторно, что заняло полдня и создало неловкость перед партнёром. После этого включили карантин: 50 элементов на ящик, Maximum size 25 MiB, Maximum age 30 дней. Для домена фонда добавили отправителя в whitelist спам-фильтра домена в панели mailcow — переписку с известным партнёром больше не режет, а общий порог reject для остального потока остался прежним.
За полтора месяца с момента включения карантина через витрину нашли и восстановили ещё три легитимных письма (два счёта от подрядчиков с крупными PDF-вложениями и одно приглашение на демо-день), которые без сохранённых копий тоже потерялись бы бесследно. Отдельно завели привычку — раз в две недели пробегать по разделу карантина глазами, а не только реагировать на жалобы постфактум.
Чек-лист: настройка карантина и порогов Rspamd без потери почты
Порядок, который я прохожу при настройке антиспама на новой инсталляции mailcow: сначала включить карантин (Retentions per mailbox — ненулевое число элементов, Maximum size с запасом под вложения, Maximum age от двух недель) и уведомления пользователям, и только потом переходить к тонкой настройке порогов reject/add_header/greylist в actions.conf под реальный поток писем. Включать карантин после настройки порогов — значит терять часть отклонённых писем без возможности их найти.
Второй пункт — не путать отсутствие витрины карантина с отсутствием фильтрации: если письмо отклоняется, а найти его негде, проблема не в том, что «Rspamd работает неправильно», а в том, что карантин не сохраняет копии. И третий — держать под рукой команду принудительного сброса уведомлений о карантине на случай, если пользователь утверждает, что писем от Rspamd не получал: часто дело не в потерянном письме, а в непрошедшем уведомлении, которое легко перезапустить вручную. Такую последовательность проверки антиспама я включаю в общий регламент эксплуатации mailcow, по которому веду клиентские инсталляции.
Отдельно проверяю согласованность лимитов. Maximum size карантина не имеет смысла ставить больше максимального размера сообщения в Postfix — такие письма всё равно не пройдут. А если в компании часто пересылают большие сканы и чертежи, наоборот, держу его близко к лимиту Postfix: крупные письма с вложениями — как раз те, что чаще всего ложно набирают баллы и которые жальче всего потерять. Раз в квартал смотрю статистику карантина: если элементов на ящик почти всегда под потолком, увеличиваю Retentions per mailbox.
Частые вопросы
Почему mailcow пишет, что карантин отключён, если письма уже отклоняются?
Потому что отклонение (reject) принимает Rspamd по порогам в actions.conf — это отдельный механизм. Карантин только хранит копии уже отклонённых писем; если Retentions per mailbox или Maximum size равны нулю, mailcow показывает, что карантин выключен, но фильтрация продолжает работать.
Если включить карантин, отклонённые письма перестанут теряться навсегда?
Да, но только те, что будут отклонены после включения карантина — задним числом уже потерянные до этого письма витрина не восстанавливает. Также важно, чтобы Maximum size был задан с запасом: письма крупнее этого лимита в карантин не попадают даже при включённой витрине.
Что означает ноль в поле Maximum size — снят ли лимит на размер письма в карантине?
Нет. Встроенная подсказка mailcow прямо говорит, что 0 не означает «без лимита». При нуле любое письмо считается слишком большим и в карантин не попадает — фактически карантин выключен. Задайте реальное значение в MiB, например 25.
Что означает Retentions per mailbox — это срок хранения?
Нет, это количество элементов карантина на один ящик. При переполнении самые старые копии удаляются. Срок хранения задаёт отдельное поле Maximum age в днях, и оно должно быть не меньше 1.
Зачем нужна команда quarantine_notify.py, если уведомления и так должны приходить автоматически?
Это отладочный инструмент mailcow для случаев, когда нужно убедиться, что уведомления о новых карантинных письмах реально доходят до пользователей. Команда сбрасывает флаг notified в базе данных и ключ Q_LAST_NOTIFIED в Redis, после чего принудительно запускает рассылку заново — в норме такой ручной запуск не требуется, уведомления идут по расписанию.
Можно ли настроить, чтобы легитимные письма от конкретного партнёра никогда не отклонялись, не трогая общий порог reject?
Да. В панели mailcow у домена и ящика есть whitelist спам-фильтра: добавленный туда отправитель не будет отклоняться по баллам, а общий порог reject для остального потока не меняется.
Источники
- mailcow docs — Resend Quarantine Notifications — Проверена точная отладочная команда: docker compose exec dovecot-mailcow, обновление notified=0 в таблице quarantine, удаление ключа Q_LAST_NOTIFIED в Redis, запуск quarantine_notify.py; контейнер dovecot-mailcow как хост скрипта. https://docs.mailcow.email/troubleshooting/debug-resend-quarantine-notifications/
- mailcow docs — Rspamd Tweaks — Путь data/conf/rspamd/local.d/actions.conf, штатные пороги reject = 15, add_header = 8, greylist = 7, перезапуск rspamd-mailcow; пользователи могут менять свои пороги. https://docs.mailcow.email/manual-guides/Rspamd/u-e-rspamd-tweaks/
- Rspamd docs — Actions and scores — Официальное описание действий Rspamd (no action, greylist/soft reject, add header, rewrite subject, reject) и того, что reject происходит на уровне SMTP-диалога, до фактического приёма письма. https://docs.rspamd.com/configuration/metrics/
- GitHub mailcow-dockerized — Issue #2327 — Реальное сообщение интерфейса «текущая конфигурация системы отключает функциональность карантина» при нулевых настройках Retentions per mailbox, подтверждающее существование этого предупреждения в панели. https://github.com/mailcow/mailcow-dockerized/issues/2327
- GitHub mailcow-dockerized — lang.en-gb.json и meta_exporter/pipe.php — Подсказки: «Retentions per mailbox: 0 indicates inactive», «Maximum size in MiB, 0 does not indicate unlimited», «Maximum age in days, ≥1»; pipe.php отбрасывает письма больше Q_MAX_SIZE и удаляет старые элементы сверх Q_RETENTION_SIZE. https://github.com/mailcow/mailcow-dockerized/blob/master/data/conf/rspamd/meta_exporter/pipe.php


