Как в mailcow раскладывать письма user+проект по папкам без сотен алиасов: теги и Sieve
Я перестаю заводить новый алиас под каждый проект клиента, если можно обойтись тегом в адресе — mailcow из коробки умеет раскладывать такие письма по подпапкам или помечать тему. Показываю, где это включается, что означает разделитель «+» с точки зрения RFC 5233 и какие грабли ждут, если тег путают с алиасом или с обычным Sieve-фильтром.
Кейс: «Рекламный цех» и 187 алиасов ради сортировки писем
«Рекламный цех» — рекламное агентство на 40 рабочих мест, mailcow у них на своём сервере, почту мы обслуживаем по регламенту больше года. У каждого менеджера — 4-6 активных проектов одновременно: Авито, Яндекс.Директ, ВК Реклама, прямые клиенты. Раньше, когда менеджеру нужно было получать письма по конкретному проекту отдельно от общего потока, админ заводил новый алиас вида ivan.proekt1@domain.ru, прописывал его получателем и вручную объяснял площадке, на какой адрес слать закрывающие документы.
К моменту, когда я впервые посмотрел на их почтовый сервер, в списке алиасов mailcow было 187 записей, из которых больше сотни существовали ровно ради одной задачи — разложить письма по папкам «Проект 1», «Проект 2» и так далее. Часть алиасов давно потеряла актуального владельца, часть дублировала друг друга с опечаткой в имени. Удалить их разом было страшно: непонятно, что отвалится.
Решение не в том, чтобы навести порядок в существующих алиасах, а в том, чтобы для новых проектов вообще не заводить алиас — использовать встроенную в mailcow сортировку по тегу в адресе. Менеджер продолжает получать письма на один и тот же ящик, но по нужной подпапке или пометке в теме они раскладываются автоматически, без единой новой записи в базе mailcow.
- 187 алиасов на 40 рабочих мест, больше половины — только ради сортировки по папкам
- новый проект = новый алиас = ручная работа админа и путаница с площадками
- решение: адрес с тегом (sub-addressing), встроенный в mailcow, без плагинов
Что такое тег в адресе и почему это не алиас
Адрес вида 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 — реальная запись: у неё есть получатель, она видна в списке администратора, её нужно создать до того, как на неё придёт первое письмо, и удалить, когда проект закрыт. Тег живёт ровно столько, сколько на него приходят письма, и не оставляет после себя мусора в конфигурации домена — с точки зрения администратора это разница между «завести и не забыть удалить» и «вообще ничего не делать».
Где включается сортировка по тегу — 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, просто дальше лягут в свою папку. Сортировка по тегу решает задачу организации почты, а не задачу безопасности — это две разные настройки, и путать их не стоит.
Итог: что изменилось у «Рекламного цеха»
Мы перевели активных менеджеров на тегированные адреса за один рабочий день: включили сортировку по подпапкам в 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 для целей сортировки писем.
Источники
- mailcow docs: Sub-addressing — Разделитель «+», расположение настройки Mailbox → Settings, два режима (подпапка / тег в теме), нормализация :lower :upperfirst и её отключение через data/conf/dovecot/global_sieve_after. https://docs.mailcow.email/manual-guides/mailcow-UI/u_e-mailcow_ui-sub_addressing/
- RFC 5233 — Sieve Email Filtering: Subaddress Extension — Определение :user и :detail для address-part, пример envelope :detail "to", рекомендация проверять envelope, а не заголовок, для целей сортировки писем. https://www.rfc-editor.org/rfc/rfc5233.html
- mailcow community: Tagged mail handling — Обсуждение практического использования тегированных адресов и разницы с алиасами на форуме сообщества mailcow. https://community.mailcow.email/d/4037-tagged-mail-handling
- mailcow-dockerized: global_sieve_after, dovecot.conf, rspamd.local.lua — Фрагмент правила X-Moo-Tag/fileinto :create "INBOX/${tag}", разделитель папок «/», символ TAG_MOO (один получатель, действие no action/greylist). https://github.com/mailcow/mailcow-dockerized/tree/master/data/conf



