--- 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).