# Design: не помечать письма прочитанными при архивации ## Контекст - Сервер: Microsoft Exchange IMAP4 (`mail.corpoffice.tech:143`, STARTTLS). - himalaya v1.2.0. - Проверено на живом письме (UID 320, INBOX): - `himalaya message read` — ставит `\Seen` (документировано; есть флаг `--preview`). - `himalaya attachment download` — флага против Seen НЕТ (help проверен). - Raw `FETCH ... BODY.PEEK[]` (сырой IMAP) — НЕ ставит `\Seen` (проверено: флаги `()` до и после чтения тела 16430 байт). ## Решение ### 1. Чтение тела письма (get_email_content, mail_archive.py) Было: ```python HIMALAYA_CMD + ["message", "read", str(uid), "--folder", folder] + header_args ``` Стало: ```python HIMALAYA_CMD + ["message", "read", str(uid), "--folder", folder, "--preview"] + header_args ``` `--preview` документирован: «Read the message **without** applying the "seen" flag». ### 2. Скачивание вложений (get_attachments, mail_archive.py) `himalaya attachment download` ставит Seen, а `--preview` у него нет. Обходные варианты: **A. Сырой IMAP через stdlib `imaplib` (выбрано).** Реализовать `fetch_attachments_imaplib(uid, folder, dest_dir)`: 1. Читать `account`/`password` из конфига himalaya (`~/.config/himalaya/config.toml`, секция `[accounts.]`, поля `backend.*`, пароль — `backend.auth.raw`). 2. `imaplib.IMAP4(host, 143)` + `starttls()` + `login()`. 3. `SELECT folder` (НЕ readonly — у Exchange readonly-режим может помешать корректному FETCH, проверить; PEEK работает в любом режиме). 4. `UID FETCH (BODY.PEEK[])` — uid = номер письма **в папке** (как у нас в структуре архива — он и есть UID, см. ниже). 5. Парсинг `email.message_from_bytes`, сбор частей с `get_filename()` или `content-disposition: attachment`, запись в `dest_dir`. Преимущества: убирает himalaya из критического пути (лечит и зависания), гарантированно не ставит Seen. Недостатки: дублируется логика himalaya (пароль в конфиге, parsing) — но конфиг-формат стабилен, парсётся stdlib tomllib. **Б. himalaya + выставление Seen обратно после скачивания.** `himalaya attachment download`, затем `himalaya flag remove --folder --flag seen`. Минусы: на время скачивания письмо становится прочитанным (мгновенно, но заметно на стороне Exchange-уведомлений); двойное обращение к IMAP; если скачивание упадёт — письмо останется Seen. Выбрано **А** (сырой IMAP): единственный вариант, который вообще не трогает флаги. При этом `--preview` для тела — совместимость с himalaya-чтением. ### 3. Мелочи - Оба места правятся в `mail_archive.py`; `email_classifier.py` и `email_handlers.py` не трогаем (они читают локальные `email.md`, не IMAP). - Пароль: НЕ логировать, НЕ выводить. Имя переменной — `imap_password`. - Фолбэк: если `fetch_attachments_imaplib` падает (сервер не отдаёт PEEK) — fallback на старый `himalaya attachment download` + `flag remove` (вариант Б). ## Проверка 1. `python3 -m py_compile scripts/mail_archive.py` 2. На живом письме UID 320 (непрочитанное, INBOX): - `himalaya message read 320 --preview` → флаги остаются `()`. - `fetch_attachments_imaplib(...)` → вложение скачано, флаги остаются `()`. 3. Прогнать `scripts/mail_archive.py --limit 2` на INBOX — флаги у обработанных писем не меняются (сравнить флаги в frontmatter email.md до/после). 4. Включить cron обратно; наблюдать 1 цикл — новых `Seen` в frontmatter у свежих писем нет. ## Rollback 1. `git checkout -- scripts/mail_archive.py` (если не закоммичено) или revert коммита. 2. Вернуть `--preview`/`fetch_attachments_imaplib` → исходные вызовы himalaya. 3. Флаги писем, уже помеченных Seen этим багом, НЕ восстанавливаются (вне scope; отдельная задача при необходимости).