Задача пришла в одну строку: письма от конкретного внешнего адреса — партнёра, скажем partner@example.org — приходят нашему сотруднику, но стабильно попадают в «Спам». Нужно, чтобы шли во «Входящие».
Я взялся и час разбирал SPF, DKIM, репутацию адреса и прогрев IP.
Всё это вполне осмысленная работа. Только она отвечала на другой вопрос.
Коротко
- Я понял задачу как «наши исходящие попадают в спам у получателя». Она была про входящие: наш собственный rspamd считает чужие письма спамом.
- SPF, DKIM и репутация нашего IP к этому не имеют отношения вообще.
- Разбор начинается с заголовков письма и истории в
/rspamd/, а не с догадок.- Вайтлистить домен отправителя целиком — рискованно, его легко подделать.
Как я прочитал задачу задом наперёд
Фраза «письма попадают в спам» имеет два прочтения, и они зеркальны.
Первое: наши письма попадают в спам у получателей. Тогда виноваты наши настройки отправки — SPF, DKIM, репутация нашего адреса. Именно это я и пошёл чинить.
Второе: чужие письма попадают в спам у нас. Тогда виноват наш антиспам, и ни одна из перечисленных настроек ни при чём.
Правильным было второе. Час работы оказался безупречным и бесполезным одновременно — я аккуратно проверял вещи, которые не могли быть причиной.
Урок формулируется в одну строку: сначала уточни направление потока. Кто отправитель, кто получатель, чей антиспам выносит решение. Пока это не сказано вслух, любая техническая работа рискует оказаться работой над другой задачей.
Я записал это себе как правило, потому что случай не уникальный. Формулировки вида «не работает почта», «не приходят письма», «попадает в спам» симметричны по смыслу, и человек, который приносит задачу, обычно описывает её со своей стороны, не уточняя направление — для него оно очевидно.
С чего начинается настоящий разбор
С фактов, а не с гипотез. У письма, попавшего в спам, причина записана прямо в нём.
Заголовки письма — X-Spamd-Result и X-Spam. Там виден итоговый score и то, из чего он сложился. Выглядит это примерно так:
X-Spamd-Result: default: False [8.10 / 15.00];
BAYES_SPAM(5.10)[99.9%];
R_SPF_SOFTFAIL(0.00)[~all];
MIME_HTML_ONLY(0.20)[];
X-Spam: Yes
Читается просто: смотрим, какое правило дало больше всего баллов — оно и есть причина. В этом примере из 8,10 балла пять с лишним принёс BAYES_SPAM, то есть статистический фильтр, обученный на предыдущей почте. А R_SPF_SOFTFAIL, на который глаз цепляется первым делом, дал ноль.
Это очень наглядная иллюстрация того, почему нельзя чинить по названию правила. Увидев в заголовке слово SPF, легко пойти настраивать SPF — и потратить час на строку, которая не добавила ни балла.
История в веб-интерфейсе rspamd, по адресу /rspamd/. Там видно, какие именно правила сработали на конкретном письме.
Это и есть ответ на вопрос «почему», а не предположение о нём. Разница принципиальная: без этих данных вы будете гадать, какое из десятков правил сработало, и почти наверняка начнёте с неверного.
Варианты вайтлиста, по возрастанию охвата
Когда причина известна, дальше решается, насколько широко открывать дверь. Три уровня:
- на уровне конкретного ящика — самый узкий;
- на уровне домена в mailcow — шире;
- глобальная карта вайтлиста rspamd — самый широкий.
Правило выбора простое: берите самый узкий, который решает задачу. Широкий вайтлист решит её тоже, но заодно снимет защиту там, где её никто не просил снимать.
Два узких уровня настраиваются мышкой: в настройках спам-фильтра домена в админке либо в пользовательской панели самого ящика. Самый широкий — правкой карты и перезапуском rspamd:
cd /opt/mailcow-dockerized
echo 'partner@example.org' >> data/conf/rspamd/custom/global_smtp_from_whitelist.map
docker compose restart rspamd-mailcow
Первая строка подразумевает стандартный путь установки; у меня mailcow в /opt/mailcow_data/mailcow-dockerized, так что cd правится под себя.
Отдельное предостережение: вайтлистить домен отправителя целиком — рискованно. Домен в письме подделывается легко, и такой вайтлист открывает дверь ровно тем письмам, от которых антиспам и защищает. Адрес, которому вы доверяете, становится удобной маской.
Подвох на другом конце
Бывает так: score низкий, правила не сработали, а письмо всё равно лежит в Junk.
Тогда антиспам ни при чём, и дальше копать в нём бесполезно. Смотреть надо sieve-фильтры SOGo или правила в самом почтовом клиенте. Симптом тот же — письмо в «Спаме», — а слой совсем другой.
Это второй раз за одну задачу, когда одинаковый симптом имеет разные причины. И второй раз ответ находится не рассуждением, а проверкой фактов: если score низкий, значит решение принял не антиспам, значит ищем того, кто его принял.
Длинная мера, которая работает лучше точечных
Дообучение Bayes: переносить ложные срабатывания из Junk во «Входящие».
Можно и явно, конкретным письмом:
docker compose exec -T rspamd-mailcow rspamc learn_ham < letter.eml
Это не мгновенно и не даёт немедленного эффекта на конкретное письмо. Но это единственный вариант, который учит фильтр, а не обходит его. Вайтлист — исключение из правила, дообучение — исправление самого правила.
И если вернуться к заголовку из примера выше: раз основную часть баллов дал именно BAYES_SPAM, то дообучение бьёт ровно в причину, а вайтлист — мимо неё.
Чем закончилось
Порядок действий подготовлен. Какой именно вариант в итоге применили, я утверждать не буду — у меня это не зафиксировано, а память в таких вещах не свидетель. Написать «сделали вайтлист на ящик» было бы правдоподобно и, возможно, неправдой.
Лучше честно: разобрано, порядок есть, конкретика не записана.