Files
admin/.wiki/concepts/snolla-smtp-mail-delivery-2026-08.md

5.9 KiB
Raw Blame History

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 (1214.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 форм с 23 админами — все такие письма падали. Фикс: @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.frome-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-стеков 1723 == канон (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).