# BulkArchiveMailer (outlook-bulk-sender) VSTO-плагин классического Outlook (Win32/C2R, **не** "новый Outlook" — тот не поддерживает VSTO и локальный доступ к файловой системе) для массовой рассылки зашифрованных `.7z`-архивов, которые генерирует [`ssh-access-tui`](../README.md) для каждого пользователя jump-хоста. ## Как это связано с ssh-access-tui `ssh-access-tui` (см. `internal/keymgr/pack.go` и `internal/keymgr/configonly.go` в корне репозитория) складывает в общий выходной каталог по одному архиву на пользователя: - `{login}_ssh_access.7z` — новая пара ключей + конфиг + инструкция (выдаётся при первом доступе или полной переиздаче ключа). - `{login}_config_update.7z` — только обновлённый конфиг + инструкция, без ключа (выдаётся, когда у пользователя поменялся список разрешённых сервисов, а сам ключ остался прежним). `ssh-access-tui` только *генерирует* эти архивы — раздача их получателям по почте раньше делалась вручную. BulkArchiveMailer автоматизирует именно эту последнюю часть: сканирует папку, по имени файла восстанавливает логин и адрес получателя, подставляет письмо по шаблону, показывает предпросмотр со статусами и рассылает. BulkArchiveMailer **не порождает архивы и не знает пароль от них** — пароль общий на партию экспорта и сообщается получателям отдельно, вне этого инструмента (см. баннер-напоминание в окне предпросмотра). Инструмент только прикладывает уже готовый файл архива к письму. ## Как логин превращается в адрес Правило простое и единственное: в логине из имени файла (например, `a-makarov`) каждый дефис заменяется на точку, и добавляется домен — получаем `a.makarov@<домен>`. Отдельной таблицы соответствий нет и не планируется. **Известное ограничение:** если у реального логина дефис не является разделителем инициала/фамилии (то есть входит в исходное системное имя как есть, а не как искусственный разделитель), адрес получится неправильным. Механизм переопределения (per-user override) сознательно не реализован — такие логины считаются исключением, которое разбирается вручную (переслать письмо оператором с указанием правильного адреса). ## Структура решения ``` BulkArchiveMailer.sln BulkArchiveMailer.Core/ net48, class library, БЕЗ ссылок на Outlook/COM/реестр — можно собрать и протестировать без Windows/Office. FilenameParser.cs парсинг "{login}_(ssh_access|config_update).7z" EmailAddressBuilder.cs login -> "login.with.dots@domain" SendJob.cs модель строки предпросмотра (Login/Email/Kind/ArchivePath/…/Status) ArchiveScanner.cs сканирование папки, группировка по логину, конфликт "оба архива сразу" TemplateEngine.cs подстановка {Login}/{Email}/{Date} в шаблон MailTemplates.cs два именованных шаблона (ssh_access / config_update) SendLogStore.cs CSV-журнал отправок + дедуп по (Email, Kind, SHA-256 архива) AppSettings.cs %APPDATA%\BulkArchiveMailer\config.json, засев из config.default.json config.default.json шаблоны писем и домен по умолчанию (встроен как Embedded Resource) BulkArchiveMailer.Core.Tests/ xUnit-заготовка (один smoke-тест) — полное покрытие добавляется отдельно BulkArchiveMailer.AddIn/ VSTO-плагин, net48, Windows-only, ссылается на Core ThisAddIn.cs / .Designer.cs запуск/остановка плагина Ribbon.xml + RibbonController.cs одна кнопка ленты "Массовая рассылка" SignatureReader.cs чтение подписи Outlook из %APPDATA%\Microsoft\Signatures через реестр MailSender.cs создание/отправка Outlook.MailItem, тестовый режим (Черновики) UI/SettingsForm.cs папка, домен, подпись, тестовый режим по умолчанию, оба шаблона UI/PreviewForm.cs таблица предпросмотра, баннер про пароль, отправка, итоговый журнал docs/BUILD.md как собрать и подключить плагин в Visual Studio на Windows ``` Почему граница именно такая: всё, что в принципе можно проверить без Windows/Office (парсинг имён, вывод email, шаблоны, дедуп-журнал, JSON-настройки), живёт в `BulkArchiveMailer.Core` и покрывается обычными xUnit-тестами. Всё, что обязано трогать Outlook (COM-объекты писем, реестр для подписи, лента) — в `BulkArchiveMailer.AddIn`, который без установленного Outlook в принципе не запустить. ## Настройка При первом запуске плагин создаёт `%APPDATA%\BulkArchiveMailer\config.json` из встроенного `config.default.json`. Дальше всё редактируется через окно "Настройки" (открывается кнопкой ленты, если папка с архивами ещё не настроена или не существует): - **Папка с архивами** — тот самый общий выходной каталог `ssh-access-tui`. - **Домен электронной почты** — по умолчанию `redlab.lo` (реальный домен организации), обычное редактируемое поле, а не зашитая логика. - **Файл подписи (.htm)** — можно оставить пустым (тогда при каждой рассылке подпись определяется автоматически по умолчанию Outlook для новых писем) либо указать конкретный файл вручную — например, если у оператора настроена не та подпись, которую нужно вставлять в такие письма. - **Отправлять от** — выпадающий список аккаунтов текущей сессии Outlook (`Session.Accounts`), полезно, если у оператора несколько ящиков (например, отдельный служебный/shared-ящик именно для этой рассылки). Пункт "(по умолчанию Outlook)" — прежнее поведение без явного выбора аккаунта. - **Шаблоны писем** — тема и HTML-тело отдельно для `ssh_access` и `config_update`, с плейсхолдерами `{Login}`, `{Email}`, `{Date}`. - **Тестовый режим по умолчанию** — включён по умолчанию (письма сохраняются в Черновики, а не отправляются) до первого осознанного отключения оператором в окне предпросмотра. Журнал отправок пишется в `send-log.csv` прямо в папке с архивами (`TimestampUtc,Login,Email,Kind,ArchivePath,FileSha256,Status,Error`). Строка считается "уже отправлено" (`AlreadySent` в предпросмотре), только если для той же пары (email, вид архива) в журнале есть строка `Status=Sent` с тем же SHA-256 файла — то есть если архив перегенерировали (новый ключ или новый список сервисов), это будет новый хэш, и строка снова станет `Ready`. ## Поведение, которое нельзя менять не глядя - Файл с "неправильным" именем не блокирует остальную рассылку — он просто попадает в список нераспознанных со статусом "Пропущено". - Если для одного логина одновременно лежат оба вида архива — это ошибка данных выше по конвейеру (в норме такого не должно случаться), и строка помечается как `Error`, заблокирована для отправки, без автоматического выбора одного из двух файлов. - Одна неудачная отправка не прерывает пачку — журналируется, и цикл идёт дальше. - Пароль от архива этот инструмент никогда не формирует, не хранит и не отправляет — только статичное напоминание в интерфейсе, что пароль нужно сообщить получателю отдельно. ## Статус сборки **Ничего из этого решения не скомпилировано и не запущено** — код написан в среде без Windows, Visual Studio, Office и .NET SDK. Первую сборку и проверку нужно сделать на машине с Windows + Visual Studio (workload "Office/SharePoint development") — см. [docs/BUILD.md](docs/BUILD.md).