wiki: ingest concepts/snolla-smtp-mail-delivery-2026-08 (postmortem SMTP-инцидента + монитор + рецепт деплоя)
This commit is contained in:
50
.wiki/concepts/snolla-smtp-mail-delivery-2026-08.md
Normal file
50
.wiki/concepts/snolla-smtp-mail-delivery-2026-08.md
Normal file
@@ -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=<books-ci>`).
|
||||
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/<name>-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).
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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).
|
||||
|
||||
|
||||
Reference in New Issue
Block a user