diff --git a/.wiki/concepts/snolla-smtp-mail-delivery-2026-08.md b/.wiki/concepts/snolla-smtp-mail-delivery-2026-08.md new file mode 100644 index 0000000..dc4e360 --- /dev/null +++ b/.wiki/concepts/snolla-smtp-mail-delivery-2026-08.md @@ -0,0 +1,50 @@ +--- +title: snolla-smtp-mail-delivery-2026-08 +status: live +tags: [snolla, smtp, yandex, incident, mail, portainer, deploy] +related: [[snolla-mailer-per-recipient-send]], [[portainer-stack-management-vds]], [[vds-kzntsv]] +updated: 2026-08-14 +--- + +# SMTP-инцидент 2026-08-12..14: письма форм snolla-сайтов молча не доставлялись + +## TL;DR + +Яндекс отключил протокольный доступ к `noreply@snolla.com` (доменная почта = платный Яндекс 360) → формы отвечали «успешно», а письма заказов 2 дня не уходили (mailer глотает ошибку `catch { console.log }`). Заказы НЕ терялись (лежат в `FormSubmissions`, MSSQL). Переехали на новый ящик `e-16513832@yandex.ru` (app-password) + mailer переведён на **per-recipient отправку** (`@snolla/mailer@0.9.0`). Поставлен монитор SMTP-отправки на VDS. + +## Три слоя root cause (каждый чинился отдельно) + +### 1. 525 SMTP disabled (12–14.08, инцидент) +Yandex: `525 5.7.13 Error: SMTP disabled` на auth — протокольный доступ к ящику отключён (домен на платном 360, «you need a Yandex 360 for Business»). Код сайтов не менялся (git-чисто), старый пароль давал `535 invalid login` (пароль тоже сменили когда-то), новый — `525` (ящик жив, но SMTP выключен). +**Фикс**: новый ящик `e-16513832@yandex.ru` + пароль приложения «Почта» (создан 14.08 12:10 МСК). Прокатка кредов по 8 стекам. + +### 2. 554 SPAM на мульти-получателях (холодный ящик) +После переезда: письмо с **2+ получателями** от нового (холодного) ящика → `554 5.7.1 Message rejected under suspicion of SPAM`; с 1 получателем — проходит. Проверено изоляционно (msmtp): 1→250, 2→554 стабильно. Старый ящик был прогрет → мульти-получатели проходили. В БД **11 форм с 2–3 админами** — все такие письма падали. +**Фикс**: `@snolla/mailer@0.9.0` — цикл по получателям (по одному письму на каждого; dedupe; per-recipient try/catch; Cc/Bcc/Reply-To сохранены). Потребители перевыпущены: `forms-api@0.2.0` (^0.9.0), `web@0.44.0` (^0.9.0) — важно: старые пины (`^0.8.3`, exact `0.8.3`) НЕ пускали 0.9.0, деплой был заблокирован до бампов. + +### 3. 550 Sender not owned (запечённый from) +У env-сайтов в `config/production.json` был запечён `mailSettings.from: noreply@snolla.com` (мёртвый ящик) → `550 5.7.0 Sender address rejected: not owned by authorized user` (auth-юзер e-16513832@yandex.ru не владеет From). Шаблон pilorama98 `pilorama98checkoutconfirm` тоже имел жёсткий `From: noreply@snolla.com`. +**Фикс**: `mailSettings.from` → e-16513832@yandex.ru во всех 7 production.json + `UPDATE EmailTemplates SET From='e-16513832@yandex.ru' WHERE LoweredName='pilorama98checkoutconfirm'` (единственный шаблон с From; остальные NULL → fallback на mailSettings). + +## Монитор (живёт на VDS) + +`/root/snolla-smtp-monitor/monitor.py` (python3 stdlib) + cron `0 8 * * *`: +- smtp-auth тест (ловит 525/535/connection) — ежедневно; +- weekly real-send на `OPS_NOTIFY_EMAIL`; +- parity-чек: SMTP_USER/SMTP_PASSWORD в env Portainer-стеков 17–23 == канон (`pass snolla-smtp/full-env`). +Алерт: ntfy `vds-ops` (basic auth vitya). Негатив-тест пройден. Копия: `.admin/scripts/snolla-smtp-monitor/`. + +## Рецепт деплоя (что запомнить) + +1. **Креды** — только через `pass snolla-smtp/full-env` (канон) + root-only файл на VDS `/root/.snolla-smtp-monitor.env` (600). +2. **pilonuxt** (pilorama98): конфиг запечён в образ (сборка с workstation, тег = git-sha, `--secret id=verdaccio_token,env=VERDACCIO_TOKEN`). +3. **env-сайты** (7): конфиг production.json запечён, секреты через Portainer stack env (${VAR} + Env-массив). Сборка на VDS из gitea-архива по sha (`deploy/Dockerfile`, `--build-arg VERDACCIO_TOKEN=`). +4. **Portainer API**: JWT через `/api/auth` (admin password, API-ключ давал 401). Обновление стека: `GET /api/stacks/{id}/file` → regex-замена `image:` → `PUT /api/stacks/{id}?endpointId=1` с `env` preserve + `pullImage`. ⚠️ oldtag парсить **regex'ом**, не `split(':')` (в строке image идут комментарии). +5. **Гоча**: контейнер, созданный ad-hoc (`/tmp/-deploy` compose), НЕ принадлежит Portainer-стеку → PUT падает 500 «container name already in use» → перед деплоем снести старый контейнер. +6. **yarn bump по тиражу**: после релиза snolla-пакета проверить, что пины потребителей (caret/exact) пускают новую версию; data дедюпить `yarn up -R @snolla/data` (две копии = два sequelize-инстанса). + +## Ссылки + +- Таска: `snolla-mailer-per-recipient-send` (🟢), `snolla-smtp-send-monitor-vds` (🟢), pilorama98 `pilorama-forms-email-down` (🟢). +- Креды: `pass snolla-smtp/full-env`. +- Код: `@snolla/mailer@0.9.0` (emailSender.js, цикл по получателям, коммит bb8599d). diff --git a/.wiki/index.md b/.wiki/index.md index d00348d..2ba65a0 100644 --- a/.wiki/index.md +++ b/.wiki/index.md @@ -21,6 +21,7 @@ Catalog of all wiki pages. One line per page, organized by type. Updated on ever - [windows-recovery-host](entities/windows-recovery-host.md) — Windows Recovery Host (рабочий PC пользователя) ## Concepts +- [snolla-smtp-mail-delivery-2026-08](concepts/snolla-smtp-mail-delivery-2026-08.md) — SMTP-инцидент 12–14.08: 525 (Яндекс отключил протоколы) → 554 (холодный ящик, мульти-получатели) → 550 (from not owned); фиксы + монитор на VDS + рецепт деплоя - [keenetic-rci-api](concepts/keenetic-rci-api.md) — KeeneticOS RCI API: auth, чтение конфига, запись через CLI-parser, DNS-override против сломанного hairpin diff --git a/.wiki/log.md b/.wiki/log.md index 1066f09..58d2610 100644 --- a/.wiki/log.md +++ b/.wiki/log.md @@ -9,6 +9,7 @@ ## [2026-08-12] implement | NEW concepts/keenetic-rci-api.md — разобрана автоматизация KeeneticOS 5.1.3 (RCI: GET /rci/show/*, POST /rci/ массив parse-команд). Через API добавлены DNS-записи git.kreknin.site/kreknin.site → 192.168.1.43 (решение сломанного hairpin). Проверено с LAN: всё 200. Правила порт-форвардинга в 5.x лежат в ip.static (to-host=MAC). ## [2026-08-12] implement | UPDATE entities/kreknin-synology.md — Gitea 1.27.1 + postgres 16 на kreknin (для Алексея), traefik починен (restart policy), LE-серт git.kreknin.site, проброс 80/443→8000/4443 на Keenetic. KeeneticOS Viva API закрыт — правила в GUI. NAT loopback сломан → нужен DNS-override git.kreknin.site→192.168.1.43. Пароли: pass kreknin/keenetic + pass kreknin/gitea. # Wiki op-log +## [2026-08-14] ingest | NEW concepts/snolla-smtp-mail-delivery-2026-08.md — postmortem SMTP-инцидента 12–14.08: 525 SMTP disabled (Яндекс 360, доменка) → переезд на e-16513832@yandex.ru (app-password); 554 SPAM на 2+ получателях от холодного ящика → @snolla/mailer@0.9.0 per-recipient (forms-api 0.2.0, web 0.44.0); 550 from not owned → mailSettings.from во всех production.json + шаблон pilorama98checkoutconfirm. Монитор SMTP на VDS (cron 08:00, ntfy) + parity-чек Portainer env. Рецепт деплоя тиража (порядок, гочи: ad-hoc контейнер vs Portainer, regex-парсинг image, yarn up -R data). Append-only log of wiki operations (ingests, promotions, lints, migrations).