2025личный проект

На главную

Letter Box

Несколько ящиков в одном окне, синхронизация продолжается с закрытым приложением, непрочитанные размечает модель, запущенная дома.

  • Electron
  • NestJS
  • BullMQ
  • PostgreSQL
  • Ollama

Задачазачем это вообще

Почему я за это взялся

У меня три ящика — рабочий, личный и тот, куда уходят все регистрации. Каждое утро уходило минут двадцать на то, чтобы понять, что из этого вообще требует ответа. Разметку хотелось отдать модели, но отдавать ради тегов всю переписку в облако — плохой обмен. Значит, модель должна работать на моём железе, а почта — жить не в приложении, а на сервере, который не выключается вместе с ноутбуком.

Архитектура4 слоя, сверху вниз

Как устроено

Клиент

  • Electron
  • React
  • TanStack Query

REST и Socket.IO

Сервер

  • NestJS
  • Prisma
  • PostgreSQL

задачи в очередь

Фон

  • BullMQ
  • Redis
  • worker

соединения и инференс

Снаружи

  • IMAP и SMTP
  • Ollama

Разборрешения и грабли

Разбор

Решения

Почтой владеет сервер, а не приложение

Первая версия держала IMAP прямо в Electron, и это разваливалось предсказуемо: закрыл окно — синхронизация встала, переустановил приложение — пароли приложений надо заводить заново, открыл на втором компьютере — состояние разъехалось.

Соединения, credentials и хранилище переехали в NestJS. Desktop стал тонким: он спрашивает данные по REST и слушает обновления по Socket.IO. Побочный выигрыш — веб-клиент теперь можно написать поверх того же API, ничего не трогая на сервере.

Разметка живёт в очереди, а не в интерфейсе

Соблазн был сделать «нажал на письмо — увидел тег». Но локальная модель отвечает секунды, а не миллисекунды, и на пачке в двести писем интерфейс превратился бы в спиннер.

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

Локальная модель — опция, а не требование

Ollama подключается через переменные окружения. Если её нет, Letter Box остаётся обычным почтовым клиентом: пропадают только теги и часть дашборда. Такую зависимость нельзя делать обязательной — иначе приложение перестаёт запускаться у всех, кто не готов держать модель.

Что оказалось сложным

Провайдеры по-разному врут про IMAP. Mail.ru, Яндекс и Gmail расходятся в именах системных папок, в поддержке CONDSTORE и в том, что считать «удалено». Логику пришлось выносить в отдельный слой с правилами на провайдера, а не надеяться на общий стандарт.

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

Экраны3 снимка

Как это выглядит

  • Обзор Letter Box — счётчики непрочитанных по аккаунтам, активность по дням и распределение писем по AI-тегам

    Обзор Letter Box — счётчики непрочитанных по аккаунтам, активность по дням и распределение писем по AI-тегам

  • Единый входящий с фильтрами по тегам и открытым письмом справа

    Единый входящий с фильтрами по тегам и открытым письмом справа

  • Окно написания письма с выбором аккаунта-отправителя

    Окно написания письма с выбором аккаунта-отправителя

Ссылкипосмотреть вживую