# Tasks: Потоковая синхронизация почты (IMAP IDLE) и онлайновая копия ящика ## Задачи ### Фаза 0: Подготовка и проверка авторизации - [ ] 1. Установить/проверить `aioimaplib` (pip) — библиотека для IDLE-потока; если не ставится — зафиксировать fallback на сырой socket. (2026-09-15: `aioimaplib` НЕ установлен; на следующем шаге — решить ставить или идти на сыром socket, т.к. imap_client.py уже это умеет.) - [x] 2. **Проверить авторизацию** для stream-процесса: himalaya логинится успешно (эталон), чистый IMAP-скрипт — нет. Отработать AUTH=PLAIN (SASL-IR), формат login; зафиксировать рабочий вариант в коде. (Блокер — без него stream не запустится.) **Выполнено 2026-09-15**: создан `scripts/imap_client.py` с отдельной функцией `imap_connect()` (STARTTLS + LOGIN + re-try против rate-limit). Реальная проверка: LOGIN как e.storozhenko прошёл, `fetch_attachments_imaplib` через неё скачал `Переместить стол.docx` 1.1 МБ (UID 14200) без \Seen. Rate-limit Exchange: после серии быстрых попыток сервер молчит (timeout), поэтому в `imap_connect()` re-try с экспоненциальной паузой. - [x] 2b. **Логирование авторизации + метрики доступности/сессии**: `imap_log()` пишет JSON-строки в `/opt/hermes/email/logs/imap_client.log` (conn_ok / conn_error / auth_ok / auth_failed / session_started / session_ended); `imap_metrics()` считает auth_success_rate, conn_error, sessions_active; `imap_session` — контекстный менеджер (гарантирует session_ended). Проверено: `python3 imap_client.py --metrics` показывает метрики. Пароль никогда не логируется. **TODO (позже)**: полноценный мониторинг — Prometheus-формат/статус-эндпоинт для stream-сервиса, алерты при падении auth_success_rate / conn_error. ### Фаза 1: Скелет сервиса imap_stream.py - [ ] 3. `scripts/imap_stream.py`: connect + STARTTLS + login; folder list (get_inbox_subfolders); SELECT INBOX; IDLE-цикл с обработкой untagged (EXISTS/EXPUNGE/FETCH FLAGS); reconnect при обрыве. - [ ] 4. Реализовать reconcile: UID FETCH новых писем (>last_uid) → событие `added` + архивация (переиспользовать mail_archive.py); сравнение FLAGS → `flag_changed`; отсутствие после EXPUNGE → `deleted` (soft). - [ ] 5. SQLite schema: `mailbox_state` + `mailbox_events` (ChangeLog) — создать `/opt/hermes/email/state/mailbox.db`, функции init/insert/read. ### Фаза 2: Change Analyzer - [ ] 6. `scripts/change_analyzer.py`: чтение mailbox_events от offset; на `added` — классификация (email_classifier) + обработчики (email_handlers); на `moved`/`deleted`/`flag_changed` — обновление mailbox_state, RAG-метка. **Уведомления в ЛС Telegram уже проверены живьём (2026-09-15)**: письмо 3216 помечено urgent → доставка в private chat 281328953 (kpa39l), msg_id=60, подтверждено пользователем. Для ЛС нужен явный `TELEGRAM_CHAT_ID=281328953` (дефолт в email_handlers.py — канал @dedinit_vesti). - [ ] 7. Обработка `replied`: \Answered или новое письмо с In-Reply-To на известный Message-ID → событие `replied`; обновить thread в state. ### Фаза 3: systemd и интеграция - [ ] 8. systemd user units (`email-imap-stream.service`, `email-change-analyzer.service`), автозапуск; проверка auto-restart. - [ ] 9. Hermes cron `mail-archive-every-5min` → fallback (идемпотентен с stream; не дублирует). Проверить, что при работающем stream cron не архивирует повторно (last_uid / state). - [ ] 10. Живой тест: новое письмо (отправить себе/ждущее), перемещение (через IMAP MOVE/клиент), удаление, ответ — всё фиксируется в ChangeLog и mailbox_state; \Seen не ставится (проверка флагов до/после). ### Фаза 4: Документация и доводка - [ ] 11. STATUS.md / README / WALKTHROUGH: сервисы, порты (нет новых внешних), как смотреть ChangeLog (`sqlite3 mailbox.db 'select * from mailbox_events'`), как перезапускать. - [ ] 12. RAG-интеграция (опционально, Фаза 2 проекта): индексация событий ChangeLog в Qdrant — чтобы агент мог ответить «что случилось с письмом». ## Верификация - `systemctl --user status email-imap-stream email-change-analyzer` — active (running) - `sqlite3 /opt/hermes/email/state/mailbox.db 'select count(*) from mailbox_state'` — растёт - `sqlite3 ... 'select event, count(*) from mailbox_events group by event'` — есть added/moved/deleted/flag_changed - Новое письмо в INBOX → архив появляется в течение ~1-2 мин (не 5) - Перемещение письма в клиенте → в mailbox_state folder обновлён, в ChangeLog `moved` - Удаление письма → `deleted` в ChangeLog, файл email.md на месте (soft-delete) - Ответ → `replied` или `flag_changed` (\Answered) - Флаги непрочитанного письма на IMAP после архивации: `()` → `()` (Seen нет)