Files
email-assistant/openspec/specs/email-attachments/spec.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

101 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# email-attachments Specification
## Purpose
Скачивание вложений письма в каталог этого письма. Сейчас `mail_archive.py`
вызывает `himalaya attachment download --dir`, но правильный флаг в Himalaya —
`--downloads-dir`, из-за чего команда падает (exit 2), ошибка молча глотается
`except: pass`, и папка `attachments/` всегда пустая. Вложения теряются.
## Requirements
### Requirement: Вложения сохраняются в каталог письма
Для каждого письма с вложениями (флаг `has_attachment: true` в frontmatter)
вложения MUST быть сохранены в подкаталог `attachments/` каталога письма
(`/opt/hermes/email/<folder>/YYYY/MM/<uid>/attachments/`).
#### Scenario: Письмо с вложением архивировано
- **WHEN** `mail_archive.py` заархивировал письмо с `has_attachment: true`
- **THEN** файлы вложений лежат в `<msg_dir>/attachments/` и совпадают с вложениями на IMAP-сервере
### Requirement: Правильный флаг Himalaya
Скачивание вложений MUST использовать флаг `--downloads-dir` (а не несуществующий
`--dir`) команды `himalaya attachment download`, и передавать ему каталог письма.
#### Scenario: Вызов himalaya с корректным флагом
- **WHEN** `get_attachments()` выполняется для письма
- **THEN** используется `himalaya attachment download --folder <folder> --downloads-dir <msg_dir>/attachments <uid>`, exit code 0 при успехе
### Requirement: Учёт отсутствия вложений
Если письмо не имеет вложений (`has_attachment: false` или команда вернула
«нет вложений»), скрипт MUST NOT создавать пустую папку `attachments/` и MUST NOT
считать это ошибкой.
#### Scenario: Письмо без вложений
- **WHEN** `mail_archive.py` обрабатывает письмо без вложений
- **THEN** каталог `attachments/` не создаётся, ошибка не логируется
### Requirement: Повторная обработка существующих писем
Повторный запуск `mail_archive.py` MUST NOT повторно качать уже сохранённые
вложения (проверка по наличию каталога/файлов).
#### Scenario: Повторный запуск
- **WHEN** `mail_archive.py` запущен повторно на письме с уже скачанными вложениями
- **THEN** вложения не скачиваются повторно (идемпотентность)
### Requirement: Бэкфилл вложений для ранее заархивированных писем
Письма, заархивированные до внедрения `--downloads-dir` (пустой `attachments/`
при `has_attachment: true`), MUST поддерживать докачку вложений через флаг
`--attachments-backfill`. Бэкфилл MUST NOT трогать письма, где вложения уже
скачаны, и MUST корректно определять IMAP-папку из пути
(`/<folder>/YYYY/MM/<uid>/`, папка может быть вложенной, например
`INBOX/!Битрикс`).
#### Scenario: Запуск бэкфилла
- **WHEN** `mail_archive.py --attachments-backfill` запущен на архиве с письмами,
у которых `has_attachment: true`, но пустой `attachments/`
- **THEN** вложения скачиваются в эти папки; письма с уже скачанными вложениями пропускаются
#### Scenario: Нестандартная структура пути
- **WHEN** бэкфилл встречает путь, не соответствующий `/<folder>/YYYY/MM/<uid>/email.md`
- **THEN** письмо пропускается без ошибки
### Requirement: Скачивание вложений не помечает письмо прочитанным
Скачивание вложений MUST NOT выставлять IMAP-флаг `\Seen` (письмо не должно
становиться «прочитанным» в почтовом ящике).
Способ: `himalaya attachment download` ставит `\Seen` (использует `BODY[]`), и
флага `--preview` у него нет. Поэтому `get_attachments()` MUST использовать
сырой IMAP-запрос `BODY.PEEK[]` через stdlib `imaplib` (не ставит `\Seen` на
Microsoft Exchange, проверено) и распаковку MIME через stdlib `email`.
#### Scenario: Скачивание вложения у непрочитанного письма
- **GIVEN** письмо в INBOX с флагами `()` (непрочитанное)
- **WHEN** `get_attachments()` скачивает его вложения
- **THEN** файлы вложений сохранены в `attachments/`, а флаги письма на IMAP
остаются `()` (флаг `\Seen` не выставлен)
#### Scenario: Фолбэк при сбое сырого IMAP
- **WHEN** `fetch_attachments_imaplib()` не может получить письмо (ошибка IMAP)
- **THEN** вложения скачиваются через `himalaya attachment download`, после чего
флаг `\Seen` снимается через `himalaya flag remove` (письмо временно
помечается, но восстанавливается) ИЛИ операция помечается как недоступная —
письмо НЕ остаётся прочитанным навсегда
### Requirement: Пароль IMAP для скачивания вложений
`fetch_attachments_imaplib()` MUST брать учётные данные IMAP (host, port, login,
пароль) из конфига himalaya (`~/.config/himalaya/config.toml`, секция
`[accounts.<default>]`, `backend.*`, пароль — `backend.auth.raw`) и MUST NOT
логировать или выводить пароль.
#### Scenario: Доступ к конфигу
- **WHEN** `get_attachments()` запускается для письма
- **THEN** подключение к IMAP выполняется с учётными данными из конфига
himalaya, пароль никуда не выводится