Mailcow: адрес с тегом и сортировка почты по Sieve
АйТи Фреш
Linux, Docker и DevOps

Как в mailcow раскладывать письма user+проект по папкам без сотен алиасов: теги и Sieve

Автор: , директор ООО «АйТи-Фреш» · · ~14 мин чтения
Тегированный email-адрес в mailcow автоматически сортирует письмо по нужной папке без создания отдельного алиаса
Один ящик, один плюс в адресе — и письмо само находит свою папку.

Я перестаю заводить новый алиас под каждый проект клиента, если можно обойтись тегом в адресе — mailcow из коробки умеет раскладывать такие письма по подпапкам или помечать тему. Показываю, где это включается, что означает разделитель «+» с точки зрения RFC 5233 и какие грабли ждут, если тег путают с алиасом или с обычным Sieve-фильтром.

Кейс: «Рекламный цех» и 187 алиасов ради сортировки писем

«Рекламный цех» — рекламное агентство на 40 рабочих мест, mailcow у них на своём сервере, почту мы обслуживаем по регламенту больше года. У каждого менеджера — 4-6 активных проектов одновременно: Авито, Яндекс.Директ, ВК Реклама, прямые клиенты. Раньше, когда менеджеру нужно было получать письма по конкретному проекту отдельно от общего потока, админ заводил новый алиас вида ivan.proekt1@domain.ru, прописывал его получателем и вручную объяснял площадке, на какой адрес слать закрывающие документы.

К моменту, когда я впервые посмотрел на их почтовый сервер, в списке алиасов mailcow было 187 записей, из которых больше сотни существовали ровно ради одной задачи — разложить письма по папкам «Проект 1», «Проект 2» и так далее. Часть алиасов давно потеряла актуального владельца, часть дублировала друг друга с опечаткой в имени. Удалить их разом было страшно: непонятно, что отвалится.

Решение не в том, чтобы навести порядок в существующих алиасах, а в том, чтобы для новых проектов вообще не заводить алиас — использовать встроенную в mailcow сортировку по тегу в адресе. Менеджер продолжает получать письма на один и тот же ящик, но по нужной подпапке или пометке в теме они раскладываются автоматически, без единой новой записи в базе mailcow.

Сравнение алиаса и тегированного адреса в mailcow по числу записей и способу появления
187 алиасов ради сортировки против нуля новых записей на тегах.

Что такое тег в адресе и почему это не алиас

Адрес вида ivan+avito@domain.ru — это не отдельный почтовый ящик и не алиас в базе mailcow. Это тот же самый ящик ivan@domain.ru, к которому через символ «+» приписана деталь адреса — тег. Письмо на такой адрес физически приходит в тот же ящик, что и письмо на ivan@domain.ru. Сортировку в mailcow делают два компонента по очереди: Rspamd (скрипт rspamd.local.lua, символ TAG_MOO) смотрит, какой режим выбран у ящика, и либо дописывает тег в тему, либо ставит служебный заголовок X-Moo-Tag, а затем Dovecot по глобальному Sieve-скрипту global_sieve_after переносит письмо с таким заголовком в подпапку ещё до того, как оно легло в INBOX.

Механизм описан в RFC 5233 — расширении Sieve под названием subaddress. Стандарт вводит два новых аргумента для проверки адреса: :user — часть адреса до тега, и :detail — сам тег. Разделитель по умолчанию в примерах RFC — символ «+», хотя формально стандарт называет способ кодирования детали адреса зависимым от реализации (implementation-defined); в mailcow и подавляющем большинстве почтовых систем это именно «+».

Важное следствие: тег не создаёт нового получателя. Площадка, которая шлёт письма на ivan+avito@domain.ru, физически отправляет их тому же ivan@domain.ru — просто конверт содержит дополнительную деталь, которую сервер использует для маршрутизации внутри одного ящика. Поэтому заводить тег в mailcow не нужно вообще: он не существует как отдельная сущность, пока кто-то не отправит на него письмо.

Для «Рекламного цеха» это была ключевая разница по сравнению со старой схемой на алиасах. Алиас в mailcow — реальная запись: у неё есть получатель, она видна в списке администратора, её нужно создать до того, как на неё придёт первое письмо, и удалить, когда проект закрыт. Тег живёт ровно столько, сколько на него приходят письма, и не оставляет после себя мусора в конфигурации домена — с точки зрения администратора это разница между «завести и не забыть удалить» и «вообще ничего не делать».

Схема маршрутизации письма с тегированным адресом в mailcow от SMTP-конверта до автоматически созданной папки
Вся логика — внутри одного ящика, без новых записей в базе mailcow.

Где включается сортировка по тегу — Mailbox → Settings

Обработка тегированных адресов настраивается в панели пользователя mailcow, в разделе **Mailbox → Settings**, у каждого ящика отдельно. Там два независимых действия, оба применяются к части адреса после «+»:

# фрагмент data/conf/dovecot/global_sieve_after из репозитория mailcow
if allof (
  envelope :detail :matches "to" "*",
  header :contains "X-Moo-Tag" "YES"
  ) {
  set :lower :upperfirst "tag" "${1}";
  if mailboxexists "INBOX/${1}" {
    fileinto "INBOX/${1}";
  } else {
    fileinto :create "INBOX/${tag}";
  }
}

Первый вариант — переносить письмо в подпапку INBOX с именем тега, создавая её автоматически при первом письме с новым тегом. Именно так «Рекламный цех» получил папки «Avito», «Yandex-direct», «Vk-reklama» (про заглавную букву — в следующем разделе) без единого обращения к админу — папка появляется сама при первом письме на новый тег. Второй вариант мягче: письмо остаётся во «Входящих», но к теме добавляется префикс с тегом в квадратных скобках, например «[avito] Акт за август». Это удобно, когда менеджер хочет видеть источник письма, не переключаясь между папками.

Оба режима — переключатели в интерфейсе, никакого Sieve-кода писать руками не нужно. Важно понимать, что при сохранении настройки mailcow не генерирует новое правило: выбор ящика сохраняется в базе и Redis, его читает Rspamd при каждой доставке, а правило в data/conf/dovecot/global_sieve_after одно на весь сервер и одинаково для всех ящиков. Его фрагмент выше — это и есть вся «магия» режима подпапок.

На практике я советую комбинацию: подпапки — для менеджеров с большим числом параллельных проектов, где важно физическое разделение почты, а тег в теме — для тех, кто ведёт один-два проекта и предпочитает видеть всё во «Входящих», но с понятной меткой источника. Настройка не общая на домен, а персональная для каждого ящика, поэтому в одном и том же mailcow разные сотрудники спокойно используют разные режимы одновременно. Все новые подпапки, кстати, физически ложатся в тот же каталог vmail, что и основной ящик — это важно помнить, если позже придётся разбирать архив писем вручную в обход веб-интерфейса.

Регистр тега: :lower :upperfirst по умолчанию и как его отключить

Есть деталь, которая в «Рекламном цехе» сначала выглядела как баг: тег avito приходил и как AVITO, и как Avito от разных отправителей, а папка в итоге всегда называлась одинаково — «Avito». Это не баг, а сознательная нормализация: по умолчанию mailcow применяет к тегу правило :lower :upperfirst, то есть приводит тег к нижнему регистру и делает заглавной только первую букву. Так +AVITO, +avito и +Avito всегда попадают в одну и ту же папку, а не создают три похожие.

Для большинства сценариев это то, что нужно: без нормализации любая опечатка в регистре плодила бы новую подпапку. Но если вам принципиально важно сохранить исходное написание тега — например, тег используется как человекочитаемый идентификатор во внешней системе, — правило меняется вручную в файле data/conf/dovecot/global_sieve_after: строку set :lower :upperfirst "tag" нужно заменить на set "tag" без модификаторов, после чего перезапустить mailcow (как минимум контейнер dovecot-mailcow): при старте контейнер копирует скрипт в /var/vmail/sieve/ и заново компилирует его через sievec.

# после правки data/conf/dovecot/global_sieve_after
docker compose restart dovecot-mailcow

Я в проектах клиентов почти всегда оставляю нормализацию по умолчанию — она защищает от разрастания папок из-за банальной невнимательности отправителя, а человекочитаемость тега в 95 % случаев не критична.

Когда двух встроенных режимов мало — свой Sieve-фильтр через envelope :detail

Встроенные режимы mailcow — это, по сути, готовая обёртка над одним конкретным Sieve-правилом. Если логика сложнее — например, нужно не просто разложить по папке, а ещё и переслать копию ответственному за проект менеджеру, — приходится писать Sieve-фильтр вручную, через тот же механизм subaddress.

Здесь важна деталь, на которую прямо указывает RFC 5233: для сортировки писем предпочтительно проверять тег в envelope-адресе получателя (envelope :detail "to"), а не в заголовке To целиком. Заголовок То может быть переписан рассылкой, алиасом или правилом пересылки на промежуточном сервере, а envelope-адрес — то, что реально использовалось при SMTP-транзакции для доставки конкретно в этот ящик. Для тегов, приходящих напрямую на mailbox без цепочки алиасов, разница чаще всего не проявляется, но при любой промежуточной пересылке — проявляется обязательно.

require ["envelope", "subaddress", "fileinto"];

if envelope :detail "to" "avito" {
  fileinto "INBOX/avito";
} elsif envelope :detail "to" "vk-reklama" {
  fileinto "INBOX/vk-reklama";
}

Такой фильтр можно добавить пользователю через SOGo (Настройки → Фильтры) или, для правил на весь сервер, через global_sieve_before/global_sieve_after на стороне администратора mailcow — тогда правило будет действовать для всех ящиков всех доменов сразу, без ручной настройки у каждого менеджера. Учтите разделитель папок: в Dovecot mailcow это «/», поэтому подпапка пишется как INBOX/avito, а не INBOX.avito.

Грабли: тег — не алиас, и не отдельная личность в SOGo

Первая путаница, с которой сталкивается почти каждый администратор: тег ошибочно воспринимают как облегчённую замену алиасу и пытаются завести на него отдельный пароль или отдельную видимость в списке ящиков mailcow. Ничего этого не требуется и не сработает — тегированный адрес не существует как сущность в базе данных, пока на него не пришло письмо, и не появится в списке ящиков администратора никогда.

Мы фиксируем такие правила в общем регламенте эксплуатации mailcow, которым пользуемся на всех клиентских серверах, — чтобы новый администратор не наступал на те же грабли повторно. Вторая грабля — ответ с тегированного адреса. Если менеджер получил письмо на ivan+avito@domain.ru и хочет ответить именно с этого адреса (чтобы у клиента в переписке сохранился нужный адрес получателя), по умолчанию SOGo ответит с основного ivan@domain.ru — сам тег в исходящих не подставляется автоматически. Чтобы это работало, в SOGo нужно завести отдельную «личность» (Identity) с email ivan+avito@domain.ru и выбирать её вручную при ответе на такие письма; после этого отправьте тестовое письмо и убедитесь, что Postfix вашей версии mailcow принимает такой адрес отправителя для этого ящика.

Третья, более редкая ситуация — конфликт с catch-all-алиасом на домене. Если на домене настроен catch-all, который перехватывает всё, что не сматчилось явным алиасом или ящиком, тегированные адреса всё равно долетают до конкретного ящика раньше, чем сработает catch-all — но при отладке маршрутизации это стоит держать в голове, чтобы не тратить время на ложный след.

Четвёртая грабля видна только в логах Rspamd. Скрипт TAG_MOO обрабатывает тег, только если у письма ровно один SMTP-получатель: если площадка шлёт одно письмо сразу на ivan+avito@ и на коллегу, тег игнорируется и письмо остаётся во «Входящих». Кроме того, обработка пропускается, если Rspamd уже вынес по письму действие строже «no action» или «greylist» — например, «add header» для подозрительного письма. Поэтому при жалобе «одно письмо разложилось, другое нет» я первым делом ищу строки TAG_MOO в логе: docker compose logs rspamd-mailcow | grep TAG_MOO.

Ещё один нюанс, который я объясняю каждому клиенту при внедрении: тег не защищает от спама и не заменяет фильтрацию по содержимому. Если рекламная площадка передаст адрес с тегом третьим лицам или его подберут перебором, письма на него точно так же попадут в общий поток проверки Rspamd, просто дальше лягут в свою папку. Сортировка по тегу решает задачу организации почты, а не задачу безопасности — это две разные настройки, и путать их не стоит.

Чек-лист из пяти шагов для внедрения сортировки по тегированным адресам в mailcow
Пять шагов — и рутинная сортировка писем перестаёт требовать ручного вмешательства.

Итог: что изменилось у «Рекламного цеха»

Мы перевели активных менеджеров на тегированные адреса за один рабочий день: включили сортировку по подпапкам в Mailbox → Settings для восьми ящиков, у которых больше трёх проектов одновременно, и проговорили с площадками, что закрывающие документы и переписку теперь можно слать на ivan+название_проекта@domain.ru вместо создания нового алиаса под каждый новый проект.

За два месяца число алиасов в mailcow не выросло ни на одну запись, хотя за тот же период у агентства появилось 11 новых проектных направлений — все они разложились по автоматически созданным подпапкам без единого обращения к админу. Старые 187 алиасов мы почистили отдельным заходом: часть удалили, часть, где отправители всё ещё активно писали на старый адрес, оставили — но новых по этой схеме больше не заводим.

Это тот редкий случай, когда встроенная функция экономит не часы, а недели администраторского времени на дистанции — просто потому что рутинная задача перестаёт требовать ручного вмешательства вообще.

Отдельно проговорили с руководителем агентства ожидания на будущее: если появится сценарий, который требует не просто раскладки по папке, а, например, автоматической пересылки копии письма руководителю проекта, мы добавим точечный Sieve-фильтр через envelope :detail поверх встроенного механизма — без переделки уже настроенной схемы тегов. Это и есть тот случай, когда простое решение закрывает 90 % задач, а на оставшиеся 10 % у нас уже готов проверенный запасной вариант.

Частые вопросы

Чем адрес с тегом (user+tag@domain) отличается от алиаса в mailcow?

Алиас — отдельная запись в базе mailcow, которая перенаправляет письма на существующий ящик и требует явного создания администратором. Адрес с тегом — часть локального адреса после «+», которую Rspamd и Dovecot используют для раскладки писем внутри уже существующего ящика; в базе mailcow он никак не заводится и не отображается.

Нужно ли создавать тег в панели mailcow перед тем, как на него будут слать письма?

Нет. Тег — не сущность базы данных, он появляется в тот момент, когда приходит первое письмо на такой адрес; если в настройках ящика включена сортировка по подпапкам, папка с именем тега создастся автоматически при первом же письме.

Почему тег AVITO и avito попадают в одну и ту же папку?

По умолчанию mailcow нормализует тег правилом :lower :upperfirst — приводит к нижнему регистру с заглавной первой буквой. Это защищает от разрастания похожих папок из-за разного написания у отправителей; отключается правкой data/conf/dovecot/global_sieve_after.

Можно ли ответить клиенту так, чтобы в поле «От» остался тегированный адрес?

По умолчанию SOGo отвечает с основного адреса ящика. Чтобы ответ уходил с адреса user+tag@domain, нужно вручную завести в SOGo отдельную личность (Identity) с этим адресом и выбирать её при ответе на такие письма.

Почему в сложном Sieve-фильтре лучше проверять envelope, а не заголовок To?

Заголовок To может быть переписан алиасом, рассылкой или промежуточной пересылкой на пути письма, а envelope-адрес отражает то, что реально использовалось при SMTP-доставке в конкретный ящик. RFC 5233 прямо рекомендует envelope для целей сортировки писем.

Столкнулись с похожей задачей? Обращайтесь — решим

Если у вас происходит что-то из описанного в этой статье — или любая другая проблема с ИТ-инфраструктурой, — обращайтесь в любое время. Мои специалисты и я лично разберём ситуацию, найдём настоящую причину и доведём до решения.

Возьмёмся и за разовую задачу, и за постоянное обслуживание. Первичная консультация — бесплатно и без обязательств.

📞 +7 903 729-62-41 ✈ Telegram @ITfresh_Boss

С уважением, Семёнов Евгений Сергеевич, директор «АйТи Фреш» — IT-аутсорсинг для компаний до 50 рабочих мест, 15+ лет практики

Источники

© ООО «АйТи-Фреш» · Москва · Все статьи