Files
email-assistant/openspec/changes/archive/2026-09-14-no-mark-seen-on-archive/design.md
T
hermes 39df85b51c fix: архивация/вложения больше не помечают письма прочитанными (Seen)
Причина: himalaya message read и attachment download используют IMAP BODY[],
который по RFC 3501 выставляет \Seen на сервере (Microsoft Exchange).
Пользователь: письма в ящике после скачивания становятся прочитанными.

Фикс:
- чтение тела: himalaya message read --preview (не ставит Seen)
- вложения: fetch_attachments_imaplib() — сырой IMAP stdlib (socket+ssl),
  UID FETCH (BODY.PEEK[]), папки в modified UTF-7, литералы до 1.5МБ,
  MIME-encoded words, фолбэк himalaya + flag remove seen

Проверено живьём: UID 14200 (INBOX, docx 1.1МБ) флаги ()->() — Seen не выставлен.
Openspec: change no-mark-seen-on-archive заархивирован (2026-09-14-no-mark-seen-on-archive), 7/7 validate OK
2026-09-14 08:08:48 +00:00

5.0 KiB
Raw Blame History

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)

Было:

HIMALAYA_CMD + ["message", "read", str(uid), "--folder", folder] + header_args

Стало:

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.<default>], поля backend.*, пароль — backend.auth.raw).
  2. imaplib.IMAP4(host, 143) + starttls() + login().
  3. SELECT folder (НЕ readonly — у Exchange readonly-режим может помешать корректному FETCH, проверить; PEEK работает в любом режиме).
  4. UID FETCH <uid> (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 <uid> --folder <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; отдельная задача при необходимости).