Как я построил свой email-классификатор на n8n + LLM и почему не взял готовое решение
Контекст
За плечами у меня больше 20 лет то плотной, то фоновой связи с разработкой: от FoxPro и PHP до Python и DevOps-технологий. В начале 2026 года я целенаправленно пошёл в сторону автоматизации с ИИ, и этот проект стал первым реальным (не учебным) опытом в этой области.
Нужен был живой полигон, на котором можно пощупать связку n8n + LLM не на туториалах, а на задаче, которая болит каждый день. Такая задача нашлась под рукой: два личных почтовых ящика (Gmail и Rambler), куда ежедневно прилетает всё подряд — рассылки, уведомления, сервисные письма и редкие важные сообщения. За 3–4 дня там легко скапливаются сотни непрочитанных писем, из которых внимание действительно требуют единицы, а разбирать это вручную не хочется.
Отсюда и появился n8n в моём стеке — не как «модный no-code», а как удобный способ собрать оркестрацию: по расписанию забрать почту, прогнать через LLM, записать результат и вернуть категории обратно в почтовые ящики.

Что не устроило в готовых решениях
Перед тем как писать своё, я сознательно посмотрел, что уже есть на рынке. Сейчас существует как минимум несколько self-hosted почтовых клиентов с объединённым инбоксом и AI-категоризацией писем по папкам на основе эвристик и заголовков.
Идея «забирать почту по IMAP и классифицировать через LLM» не уникальна и легко гуглится, но мне не хватало нескольких вещей:
Данные под моим контролем. Категория должна жить в самой почте (IMAP labels/keywords), а не только в закрытой базе стороннего сервиса. Если завтра я перестану пользоваться своим инструментом, категоризация останется в почтовом ящике, и её увидит любой другой клиент.
Гибкий промпт. Мне нужно не зашитое в код правило классификации, а возможность править критерии категоризации через веб-интерфейс без пересборки и передеплоя сервиса.
Точная подгонка под два конкретных ящика. Gmail и Rambler ведут себя по-разному даже в базовых вещах: например, копия отправленного письма в Gmail создаётся автоматически, а в Rambler её нужно создавать вручную. Готовый инструмент не обязан учитывать эти детали, а мне пришлось решать их точечно.
Обучение стеку. Цель была не просто «закрыть задачу любой ценой», а прокачать связку n8n + LLM API + Python-сервис на реальном сценарии.
В итоге я пришёл к тому, что проще и полезнее собрать своё решение, чем пытаться адаптировать под себя чужое. Уникальность здесь не в самой идее классификации, а в том, как она реализована под конкретные требования и как она работает «изнутри».
Архитектура
В итоге система разбита на два независимых репозитория с чёткой границей ответственности:
smart-inbox— протокольный слой и интеграция с почтой (IMAP/SMTP, связь с LLM, запись в Postgres).smart-inbox-ui— backend и web-клиент, которые читают ту же БД и дают удобный интерфейс для работы с классифицированной почтой.

Три архитектурных решения в этой схеме, о которых имеет смысл рассказать подробнее.
1. Категория живёт в самой почте, а не только в БД
Классификация не только сохраняется в таблицах Postgres, но и параллельно проставляется прямо на почтовом сервере. Для Gmail это labels (через X-GM-LABELS), для Rambler — keywords и перенос в нужную IMAP-подпапку. PostgreSQL в этой схеме выступает быстрым индексом для поиска и фильтрации в UI, а не единственным источником истины.
Практический эффект: категория не заперта внутри моего интерфейса. В Gmail лейблы видны в веб-версии и мобильном приложении (хотя через обычный IMAP сторонние клиенты не всегда корректно работают с X-GM-LABELS), а в Rambler классификация — это вообще реальные папки, которые понимает любой почтовый клиент.
2. Жёсткая граница между «протоколом» и «продуктом»
mail_fetch_service ничего не знает о существовании веб-интерфейса. Его зона ответственности — только протокол: забрать письма по IMAP, применить категорию и метки, отправить письмо, отдать вложение по запросу.
Второй репозиторий (smart-inbox-ui) — это отдельный FastAPI-backend и React-фронтенд, которые работают поверх той же PostgreSQL и дёргают протокольный сервис по HTTP, но сами напрямую с IMAP не общаются.
Причина здесь не в «чистой архитектуре ради архитектуры». Я хочу иметь возможность добавить второй клиент (например, мобильное приложение) или переписать web-клиент, не трогая протокольный слой и не переламывая работу с почтовыми серверами.
3. n8n для оркестрации, код — для тонкой логики
Расписание, HTTP-вызовы и ветвления по результатам живут в n8n. Визуальный workflow можно открыть, посмотреть глазами и поменять без деплоя нового релиза Python-сервиса.
Всё, что требует точности и тестируемости, я оставил в коде: разбор MIME-вложений, работа с IMAP UID/Message-ID, идемпотентная запись в БД, применение меток и MOVE/COPY+EXPUNGE в нужных комбинациях. Такой баланс оказался не всегда очевидным и несколько раз приводил к интересным прод-инцидентам — о них я планирую рассказать подробно во второй статье.

На скрине выше видно, что в итоге получается не просто «скрипт, который иногда что-то перекладывает в почте», а полноценный интерфейс поверх Gmail и Rambler: объединённый список писем, фильтрация по ящику, категории, labels, признак «требует внимания», панель чтения и действия с письмом.
Что дальше
В проде система живёт совсем недавно — буквально около недели, и я продолжаю её дорабатывать прямо в процессе ежедневного использования. Даже за этот короткий срок разница с обычным Thunderbird заметна: разбор почты по категориям, важности и меткам стал ощутимо быстрее, а «мусор» не мешает глазами.
Отдельная история — прод-грабли. В следующей статье я разберу реальные инциденты, которые всплыли уже после запуска: как я умудрился 32 раза подряд прогнать одни и те же ~250 писем через платный LLM API, почему 174 из 193 «успешных» по логам запросов на самом деле не сработали и какие сюрпризы ждут при добавлении FK-миграции в уже работающую базу Postgres.
Код обоих репозиториев открыт:
smart-inbox — протокольный слой и интеграция с почтой;
smart-inbox-ui — backend и web-клиент.