MVP Spec 10 — Notifications System
Обновлён 1 апр. 2026 г., 12:41 · 0 комментариев
MVP Spec 10 — Notifications System
Паспорт документа
- Статус документа: working spec
- Актуально на: 28 марта 2026 года
- Владелец: backend/platform-команда
- Пересмотр: перед реализацией, при изменении domain rules или при смене source-of-truth по контракту
- Область применения: детальные feature-спеки для product backlog и реализации MVP/v1 направлений
- Связанные документы:
Version: MVP · Priority: P0 · Phase: A (Supply) Status: Draft v1 Sync note, 28 Mar 2026:
- live API prefix is
/api/v1/, not/api/;- частично реализована только Telegram binding часть:
POST /api/v1/seller/telegram/verifyиDELETE /api/v1/seller/telegram;- notification settings endpoints, delivery pipeline, webhook и fallback email ещё не являются текущим production contract.
1. Контекст и цель
Уведомления — критическая часть петли ценности для продавцов: продавец должен узнавать о новой заявке мгновенно, пока покупатель ещё «горячий». Медленная реакция на лид = потерянный клиент.
Цель модуля: доставлять уведомления продавцу по предпочтительному каналу (Telegram как первичный, Email как резервный), давать продавцу контроль над тем, что он получает, и обеспечивать надёжность доставки даже при сбоях одного канала.
Telegram — основной канал. Email — только резервный/дополнительный.
Что не входит в этот модуль:
- Привязка Telegram-аккаунта (верификационный поток) → Spec 01 UC-06 (реализация там, здесь только reference)
- Создание лидов → Spec 06 (Buyer Flow) + Spec 09
- Управление профилем продавца → Spec 01
- Push-уведомления в браузере или мобильном приложении → вне скоупа MVP
- Уведомления для покупателей → TBD (не определено в MVP)
2. Роли пользователей
| Роль | Действие в этом модуле |
|---|---|
| Seller (Owner) | Подключает Telegram, настраивает предпочтения уведомлений, отключает Telegram |
| Seller Staff | Получает уведомления только если у Staff есть собственный Telegram (TBD v1.0); в MVP уведомления идут только Owner |
| Система (Backend) | Отправляет уведомления в Telegram и Email при триггерных событиях |
| Admin | Может видеть статус Telegram-подключения продавца; не управляет уведомлениями |
| Telegram Bot (@qadam_notify_bot) | Принимает /start, генерирует код, доставляет уведомления |
3. Use Cases
UC-01: Продавец подключает Telegram
Актор: Seller (Owner) Предусловие: Продавец авторизован. Telegram НЕ подключён (Seller.telegram_chat_id = null) Триггер: Продавец хочет получать уведомления о лидах в Telegram
Реализация верификационного флоу описана в Spec 01 UC-06. Настоящий UC — это точка входа из раздела уведомлений и описание UX-связки.
Полный поток:
[Точка входа]
→ Продавец авторизован, переходит на /seller/settings/notifications
→ Раздел "Telegram-уведомления" показывает статус "Не подключён"
→ Кнопка [Подключить Telegram]
───────────────────────────────────────────────────────
ШАГ 1 — Запуск верификации
───────────────────────────────────────────────────────
→ Продавец нажимает [Подключить Telegram]
→ Открывается модальное окно (или inline-блок) с инструкцией:
┌───────────────────────────────────────────────────┐
│ Подключение Telegram │
│───────────────────────────────────────────────────│
│ 1. Откройте Telegram-бот @qadam_notify_bot │
│ [Открыть бота →] (ссылка t.me/qadam_notify_bot)│
│ 2. Нажмите /start в боте │
│ 3. Бот отправит вам 6-значный код │
│ 4. Введите код здесь: │
│ [ _ _ _ _ _ _ ] (6-цифровое поле) │
│ Код действителен 10 минут │
│ │
│ [Отмена] [Подтвердить] │
└───────────────────────────────────────────────────┘
───────────────────────────────────────────────────────
ШАГ 2 — Действия в Telegram-боте
───────────────────────────────────────────────────────
→ Продавец открывает бот @qadam_notify_bot в Telegram
→ Нажимает /start
→ Бот регистрирует chat_id продавца и генерирует 6-значный код
→ Бот отправляет сообщение:
"Ваш код верификации для Qadam: 847291
Код действителен 10 минут.
Введите его на сайте в разделе Настройки → Уведомления."
→ Возвращается на сайт
───────────────────────────────────────────────────────
ШАГ 3 — Ввод кода
───────────────────────────────────────────────────────
→ Продавец вводит 6 цифр в поле
→ Auto-submit при вводе 6-го символа
→ POST /api/v1/seller/telegram/verify { code: '847291' }
→ Система находит TelegramVerificationCode по коду
→ Проверяет: код не истёк, не использован, chat_id не привязан к другому seller_id
→ Сохраняет: Seller.telegram_chat_id = chat_id, Seller.telegram_verified_at = now()
→ Помечает код как used = true
───────────────────────────────────────────────────────
ШАГ 4 — Подтверждение
───────────────────────────────────────────────────────
→ Модальное окно закрывается
→ Раздел "Telegram-уведомления" обновляется: статус "Подключён ✓"
→ Toast (зелёный): "Telegram подключён! Вы будете получать уведомления о заявках."
→ Бот немедленно отправляет продавцу в Telegram:
"✅ Telegram успешно подключён к вашему аккаунту Qadam!
Теперь вы будете получать уведомления о новых заявках здесь."
UC-01 — Альтернативные потоки и обработка ошибок
1a. Неверный код (CODE_INVALID):
Триггер: Продавец ввёл код, которого нет в БД
UI-реакция:
→ Поле кода: красная обводка + ⚠ справа
→ Под полем: "Неверный код. Убедитесь, что вводите код именно из бота @qadam_notify_bot."
→ Поле очищается, фокус возвращается в поле
→ Кнопка [Подтвердить] разблокирована для повтора
1b. Код истёк (CODE_EXPIRED):
Триггер: Прошло > 10 минут с момента генерации кода в боте
UI-реакция:
→ Поле кода: красная обводка + ⚠
→ Под полем: "Код устарел. Вернитесь в бота @qadam_notify_bot и нажмите /start для получения нового кода."
→ Ссылка: "Открыть бота" (t.me/qadam_notify_bot)
→ Поле очищается
1c. Этот Telegram уже привязан к другому аккаунту (TELEGRAM_ALREADY_BOUND):
Триггер: chat_id уже существует в другой записи Seller
UI-реакция:
→ Toast (красный): "Этот Telegram-аккаунт уже используется другой организацией. Используйте другой аккаунт Telegram."
→ Поле очищается
→ Инструкция остаётся видимой
1d. Сеть недоступна или сервер вернул 5xx при верификации кода:
UI-реакция:
→ Toast (красный): "Не удалось выполнить запрос. Проверьте интернет-соединение и попробуйте снова."
→ Поле кода не очищается — продавец может повторить без повторного ввода
→ Кнопка [Подтвердить] разблокирована
1e. Продавец закрыл модальное окно до завершения верификации:
→ Модальное окно закрывается, код аннулируется (но в БД остаётся до истечения TTL)
→ При следующем открытии — продавец начинает заново (нажимает /start в боте)
1f. Продавец уже верифицирован (Telegram подключён):
→ Кнопка [Подключить Telegram] заменяется на кнопку [Отключить Telegram] и статус "Подключён ✓"
→ При попытке POST /api/v1/seller/telegram/verify когда уже подключён: 400 ALREADY_VERIFIED
UC-02: Новый лид создан → Telegram-уведомление отправляется продавцу
Актор: Система (Backend), триггер — событие создания лида
Предусловие: Продавец имеет Seller.telegram_chat_id != null AND NotificationSettings.notify_new_lead_telegram = true
Триггер: Покупатель оставил заявку на курс продавца → Lead создан со статусом new
Полный поток:
[Точка входа]
→ Buyer Flow (Spec 06): покупатель заполнил форму заявки
→ Backend создаёт Lead { lead_status: 'new' }
→ Backend вызывает NotificationService.onNewLead(lead)
───────────────────────────────────────────────────────
ШАГ 1 — Проверка настроек уведомлений
───────────────────────────────────────────────────────
→ Запрос NotificationSettings для seller_id
→ Если notify_new_lead_telegram = true И telegram_chat_id != null:
→ Переходим к отправке Telegram (ШАГ 2)
→ Если notify_new_lead_telegram = false:
→ Пропускаем Telegram
→ Если notify_new_lead_email = true: переходим к Email (UC-06)
→ Если оба false: уведомление не отправляется (продавец отписался от всего)
───────────────────────────────────────────────────────
ШАГ 2 — Формирование и отправка Telegram-уведомления
───────────────────────────────────────────────────────
→ Backend формирует сообщение:
🎓 Новая заявка на курс!
Курс: {item_name}
Имя: {lead_name}
Телефон: {lead_phone}
Тип: Пробное занятие / Запись на курс
Комментарий: {lead_comment или "-"}
[Открыть в кабинете →] (deep link: qadam.uz/seller/leads?lead={lead_id})
→ Telegram Bot API: POST https://api.telegram.org/bot{TOKEN}/sendMessage
{ chat_id: seller.telegram_chat_id, text: ..., parse_mode: 'HTML', reply_markup: { inline_keyboard: [["Открыть в кабинете", url]] } }
───────────────────────────────────────────────────────
ШАГ 3 — Результат отправки
───────────────────────────────────────────────────────
→ Telegram API вернул 200 OK:
→ Уведомление считается доставленным
→ Запись в NotificationLog { channel: 'telegram', status: 'sent' }
→ Telegram API вернул ошибку (любую):
→ Переходим к UC-03 (Email fallback)
UC-02 — Альтернативные потоки
2a. Telegram API временно недоступен (timeout, 5xx):
→ Backend делает 2 повторных попытки с задержкой (retry: 5s, 30s)
→ Если все 3 попытки неудачны: переходим к Email fallback (UC-03)
→ Запись в NotificationLog { channel: 'telegram', status: 'failed', error: '...' }
2b. Покупатель не указал комментарий (lead_comment = null):
→ В сообщении: "Комментарий: -"
→ Остальные поля без изменений
2c. Тип лида = 'buy' (не trial):
→ В сообщении: "Тип: Запись на курс"
→ Тип лида = 'trial': "Тип: Пробное занятие"
2d. lead_email указан покупателем:
→ В Telegram-сообщении email не отображается (лаконичность)
→ Email доступен продавцу в деталях лида (UC-05 Spec 09)
UC-03: Telegram недоступен → Email fallback
Актор: Система (Backend) Предусловие: Telegram-уведомление не удалось отправить (все retry провалились) И NotificationSettings.notify_new_lead_email = true Триггер: Сбой Telegram-доставки
Полный поток:
[Точка входа]
→ UC-02 завершился с ошибкой после всех retry
───────────────────────────────────────────────────────
ШАГ 1 — Проверка Email-настроек
───────────────────────────────────────────────────────
→ Проверяем NotificationSettings.notify_new_lead_email
→ Если true: отправляем Email (ШАГ 2)
→ Если false: уведомление не отправляется; пишем в лог
───────────────────────────────────────────────────────
ШАГ 2 — Отправка Email
───────────────────────────────────────────────────────
→ Email отправляется на адрес из профиля продавца (SchoolProfile.email / contributor.email)
→ Тема: "Новая заявка на курс «{item_name}» — Qadam"
→ Тело: HTML-письмо (UC-06)
───────────────────────────────────────────────────────
ШАГ 3 — Результат
───────────────────────────────────────────────────────
→ Email успешно поставлен в очередь SMTP:
→ NotificationLog { channel: 'email', status: 'sent', fallback_reason: 'telegram_failed' }
→ Email тоже провалился:
→ NotificationLog { channel: 'email', status: 'failed' }
→ Нет дальнейших попыток в MVP
→ Алерт в мониторинг (Sentry/alerting — TBD)
UC-03 — Альтернативные потоки
3a. Продавец отключил Email-уведомления (notify_new_lead_email = false):
→ Даже при сбое Telegram — email не отправляется
→ Продавец не получает уведомление
→ Запись: NotificationLog { channel: 'none', status: 'skipped', reason: 'all_channels_disabled' }
→ Лид при этом создан в базе нормально; продавец увидит его при следующем визите в кабинет
3b. Email-адрес продавца не указан или пустой:
→ Пропускаем email-отправку
→ NotificationLog { channel: 'email', status: 'skipped', reason: 'no_email_address' }
UC-04: Продавец настраивает предпочтения уведомлений
Актор: Seller (Owner) Предусловие: Продавец авторизован Триггер: Продавец хочет изменить какие уведомления получать и по какому каналу
Полный поток:
[Точка входа]
→ Продавец авторизован
→ Переходит: /seller/settings → вкладка "Уведомления"
→ Открывается /seller/settings/notifications
───────────────────────────────────────────────────────
ШАГ 1 — Страница настроек
───────────────────────────────────────────────────────
→ Страница загружает: GET /api/v1/seller/notification-settings
→ Отображается форма:
┌───────────────────────────────────────────────────────┐
│ 📱 Telegram │
│ Статус: Подключён ✓ (@username) [Отключить] │
│ ─────────────────────────────────────────────────── │
│ □ Уведомлять о новых заявках [переключатель]│
│ □ Уведомлять при смене статуса (TBD) [переключатель]│
│ │
│ ✉ Email │
│ Резервный канал: school@example.com │
│ ─────────────────────────────────────────────────── │
│ □ Уведомлять о новых заявках [переключатель]│
│ │
│ [Сохранить настройки] │
└───────────────────────────────────────────────────────┘
───────────────────────────────────────────────────────
ШАГ 2 — Изменение настройки
───────────────────────────────────────────────────────
→ Продавец переключает тоггл "Уведомлять о новых заявках (Telegram)"
→ Изменение сохраняется немедленно (auto-save) или через кнопку [Сохранить]
→ PATCH /api/v1/seller/notification-settings { notify_new_lead_telegram: false }
→ Toast: "Настройки уведомлений сохранены."
───────────────────────────────────────────────────────
ШАГ 3 — Telegram не подключён
───────────────────────────────────────────────────────
→ Если Seller.telegram_chat_id = null:
- Telegram-секция показывает статус "Не подключён"
- Тогглы Telegram-уведомлений задизейблены (серые)
- Баннер (синий): "Подключите Telegram чтобы получать уведомления в мессенджере. [Подключить →]"
- Клик на "Подключить →" → запускает UC-01
───────────────────────────────────────────────────────
ШАГ 4 — Предупреждение об отключении всех каналов
───────────────────────────────────────────────────────
→ Если продавец отключает последний активный канал:
- После переключения: баннер (жёлтый): "Вы отключили все уведомления. Вы будете видеть заявки только при входе в кабинет."
- Кнопка [Включить обратно] в баннере
UC-04 — Альтернативные потоки
4a. Сеть недоступна при сохранении настроек:
UI-реакция:
→ Тоггл возвращается в предыдущее состояние (оптимистичный UI откатывается)
→ Toast (красный): "Не удалось сохранить настройки. Проверьте соединение."
4b. Сервер вернул 5xx:
UI-реакция:
→ Тоггл возвращается в предыдущее состояние
→ Toast (красный): "Ошибка на сервере. Попробуйте через несколько минут."
UC-05: Продавец отключает Telegram
Актор: Seller (Owner) Предусловие: Seller.telegram_chat_id != null (Telegram подключён) Триггер: Продавец хочет отвязать Telegram-аккаунт
Полный поток:
[Точка входа]
→ Продавец на /seller/settings/notifications
→ В секции Telegram: статус "Подключён ✓" + кнопка [Отключить]
───────────────────────────────────────────────────────
ШАГ 1 — Подтверждение отключения
───────────────────────────────────────────────────────
→ Продавец нажимает [Отключить]
→ Появляется диалог подтверждения:
"Отключить уведомления в Telegram?
Вы перестанете получать уведомления о новых заявках через Telegram."
[Отмена] [Отключить]
───────────────────────────────────────────────────────
ШАГ 2 — Отключение
───────────────────────────────────────────────────────
→ Продавец нажимает [Отключить] в диалоге
→ DELETE /api/v1/seller/telegram
→ Сервер: Seller.telegram_chat_id = null, Seller.telegram_verified_at = null
→ Тогглы Telegram-уведомлений задизейблены
→ Статус меняется на "Не подключён"
→ Toast: "Telegram отключён. Включите email-уведомления чтобы не пропустить заявки."
→ Если Email-уведомления отключены: баннер (жёлтый) с предупреждением (UC-04, ШАГ 4)
UC-05 — Альтернативные потоки
5a. Сеть недоступна при отключении:
UI-реакция:
→ Toast (красный): "Не удалось отключить Telegram. Проверьте соединение и попробуйте снова."
→ Статус остаётся "Подключён ✓"
5b. Продавец нажал [Отмена] в диалоге:
→ Диалог закрывается
→ Telegram остаётся подключённым
→ Никаких изменений
UC-06: Email-уведомление о новой заявке
Актор: Система (Backend) Предусловие: NotificationSettings.notify_new_lead_email = true; email-адрес продавца заполнен Триггер: Новый лид создан (и Telegram недоступен или отключён)
Полный поток:
[Точка входа]
→ UC-02 (Telegram) завершён (успешно или с ошибкой)
→ Если нужен Email: Backend вызывает EmailService.sendNewLeadNotification(lead, seller)
───────────────────────────────────────────────────────
ШАГ 1 — Формирование письма
───────────────────────────────────────────────────────
→ Backend формирует HTML-письмо:
От кого: noreply@qadam.uz (Qadam Уведомления)
Кому: {seller_email из профиля}
Тема: 🎓 Новая заявка на курс «{item_name}»
HTML тело письма (примерный контент):
┌──────────────────────────────────────────────────────┐
│ [Логотип Qadam] │
│ │
│ Новая заявка на ваш курс │
│ ────────────────────────────────────────────────── │
│ Курс: {item_name} │
│ Имя: {lead_name} │
│ Телефон: {lead_phone} │
│ Email: {lead_email или "не указан"} │
│ Тип: Пробное занятие / Запись на курс │
│ Комментарий:{lead_comment или "—"} │
│ Дата: 25 марта 2026, 14:35 │
│ │
│ [Кнопка: Открыть заявку в кабинете] │
│ qadam.uz/seller/leads?lead={lead_id} │
│ │
│ ────────────────────────────────────────────────── │
│ Управление уведомлениями: qadam.uz/seller/settings/ │
│ notifications │
└──────────────────────────────────────────────────────┘
───────────────────────────────────────────────────────
ШАГ 2 — Отправка через SMTP
───────────────────────────────────────────────────────
→ EmailService отправляет через SMTP-провайдер (TBD — Postmark/SendGrid/AWS SES)
→ Письмо попадает в очередь SMTP
───────────────────────────────────────────────────────
ШАГ 3 — Получение продавцом
───────────────────────────────────────────────────────
→ Продавец видит письмо в почтовом клиенте
→ Нажимает кнопку → открывается /seller/leads с развёрнутой деталью нужного лида
UC-06 — Альтернативные потоки
6a. SMTP-провайдер временно недоступен:
→ EmailService ставит письмо в retry-очередь (max 3 попытки: через 1м, 5м, 30м)
→ Если все попытки провалились: NotificationLog { channel: 'email', status: 'failed' }
→ Алерт в мониторинг
6b. Email продавца не существует (bounce):
→ SMTP-провайдер возвращает bounce
→ NotificationLog { channel: 'email', status: 'bounced' }
→ TBD: автоматически ли отключать email-уведомления после bounce? (не определено для MVP)
4. Бизнес-правила и валидации
Таблица бизнес-правил
| Правило | Описание | Обработка нарушения |
|---|---|---|
| Telegram — первичный канал | Если подключён и включён: всегда пробуем Telegram первым | — |
| Email — только fallback или дополнение | Email отправляется если: Telegram не подключён / отключён / упал | Email не дублирует Telegram при успешной доставке |
| Только Owner получает уведомления (MVP) | Staff не получает уведомления — только на аккаунт Owner | TBD в v1.0 |
| Уникальность Telegram chat_id | Один chat_id — один seller | 400 TELEGRAM_ALREADY_BOUND |
| TTL кода верификации | 10 минут с момента /start в боте | 400 CODE_EXPIRED |
| Код одноразовый | После использования: used = true | 400 CODE_INVALID |
| Retry для Telegram | 3 попытки (immediate, +5s, +30s) | После 3-й неудачи → Email fallback |
| Retry для Email | 3 попытки в очереди (1м, 5м, 30м) | После провала: NotificationLog failed + алерт |
| Уведомление только при notify=true | Если продавец отключил тип уведомлений — не отправляем | — |
| По умолчанию уведомления включены | При регистрации: notify_new_lead_telegram = true, notify_new_lead_email = true | Настраивается в UC-04 |
| Без дублирования | Один лид = одно уведомление (Telegram ИЛИ Email, не оба если Telegram успешен) | Атомарность через NotificationLog |
Таблица валидаций API
| Поле | Правило | Ошибка |
|---|---|---|
code (Telegram verify) | Строго 6 цифр | 422: "Код должен содержать 6 цифр" |
notify_new_lead_telegram | Boolean | 422 |
notify_new_lead_email | Boolean | 422 |
notify_status_change_telegram | Boolean | 422 |
| Telegram chat_id | Не занят другим seller_id | 400 TELEGRAM_ALREADY_BOUND |
| Telegram код | Существует, не истёк (TTL 10 мин), not used | 400 CODE_INVALID / CODE_EXPIRED |
5. Модель данных
Seller (telegram_chat_id, telegram_verified_at) определена в Spec 01. Здесь — только новые сущности.
NotificationSettings
| Атрибут | Тип | Описание |
|---|---|---|
| settings_id | UUID | PK |
| seller_id | UUID FK | → Seller (Spec 01), unique |
| notify_new_lead_telegram | boolean | Уведомлять о новом лиде в Telegram. Default: true |
| notify_new_lead_email | boolean | Уведомлять о новом лиде по Email. Default: true |
| notify_status_change_telegram | boolean | Уведомлять при смене статуса в Telegram. TBD MVP, default: false |
| updated_at | DateTime |
NotificationLog (аудит-лог всех попыток отправки)
| Атрибут | Тип | Описание |
|---|---|---|
| log_id | UUID | PK |
| seller_id | UUID FK | → Seller |
| lead_id | UUID FK? | → Lead (Spec 09), если уведомление о лиде |
| event_type | NotificationEventType | new_lead / status_changed |
| channel | NotificationChannel | telegram / email |
| status | NotificationStatus | sent / failed / skipped / bounced |
| fallback_reason | string? | Причина fallback (например: 'telegram_failed') |
| error_message | string? | Текст ошибки от API/SMTP |
| attempt_number | int | Номер попытки (1, 2, 3) |
| sent_at | DateTime | Время попытки |
6. Технические контракты
6.1 Prisma Schema
enum NotificationEventType {
new_lead
status_changed
}
enum NotificationChannel {
telegram
email
}
enum NotificationStatus {
sent
failed
skipped
bounced
}
model NotificationSettings {
settings_id String @id @default(uuid())
seller_id String @unique
notify_new_lead_telegram Boolean @default(true)
notify_new_lead_email Boolean @default(true)
notify_status_change_telegram Boolean @default(false)
updated_at DateTime @updatedAt
seller Seller @relation(fields: [seller_id], references: [seller_id])
}
model NotificationLog {
log_id String @id @default(uuid())
seller_id String
lead_id String?
event_type NotificationEventType
channel NotificationChannel
status NotificationStatus
fallback_reason String?
error_message String?
attempt_number Int @default(1)
sent_at DateTime @default(now())
seller Seller @relation(fields: [seller_id], references: [seller_id])
lead Lead? @relation(fields: [lead_id], references: [lead_id])
@@index([seller_id, sent_at])
@@index([lead_id])
}
TelegramVerificationCodeопределена и описана в Spec 01 (раздел 5). Здесь не дублируется.
6.2 TypeScript DTO
// ─── Настройки уведомлений ────────────────────────────────────────────────
export class UpdateNotificationSettingsDto {
@IsOptional() @IsBoolean()
notify_new_lead_telegram?: boolean
@IsOptional() @IsBoolean()
notify_new_lead_email?: boolean
@IsOptional() @IsBoolean()
notify_status_change_telegram?: boolean
}
// ─── Telegram верификация (переиспользуется из Spec 01) ───────────────────
export class TelegramVerifyDto {
@IsString() @Length(6, 6, { message: 'Код должен содержать 6 цифр' })
@Matches(/^\d{6}$/, { message: 'Код должен содержать только цифры' })
code: string
}
// ─── Ответы ───────────────────────────────────────────────────────────────
export interface NotificationSettingsResponse {
notify_new_lead_telegram: boolean
notify_new_lead_email: boolean
notify_status_change_telegram: boolean
telegram_connected: boolean
telegram_username: string | null // из ответа Telegram API, если доступен
seller_email: string | null // email для fallback уведомлений
}
export interface TelegramVerifyResponse {
success: true
username: string | null // Telegram username, если у пользователя есть @
}
// ─── Внутренний интерфейс NotificationService ─────────────────────────────
export interface SendNewLeadNotificationPayload {
lead_id: string
seller_id: string
item_name: string
lead_name: string
lead_phone: string
lead_comment: string | null
lead_type: 'trial' | 'buy'
}
6.3 API Endpoints
────────────────────────────────────────────────────────────────
НАСТРОЙКИ УВЕДОМЛЕНИЙ
────────────────────────────────────────────────────────────────
GET /api/v1/seller/notification-settings
Auth: Bearer (seller)
→ 200: NotificationSettingsResponse
→ 401: { error: 'UNAUTHORIZED' }
PATCH /api/v1/seller/notification-settings
Auth: Bearer (seller)
Body: UpdateNotificationSettingsDto
→ 200: NotificationSettingsResponse
→ 422: { errors: [{ field: string, message: string }] }
→ 500: { error: 'INTERNAL_ERROR', message: 'Что-то пошло не так. Попробуйте позже.' }
────────────────────────────────────────────────────────────────
TELEGRAM ВЕРИФИКАЦИЯ
────────────────────────────────────────────────────────────────
POST /api/v1/seller/telegram/verify
Auth: Bearer (seller)
Body: TelegramVerifyDto
→ 200: TelegramVerifyResponse
→ 400: { error: 'CODE_INVALID', message: 'Неверный код. Убедитесь, что вводите код именно из бота @qadam_notify_bot.' }
→ 400: { error: 'CODE_EXPIRED', message: 'Код устарел. Вернитесь в бота и нажмите /start для получения нового кода.' }
→ 400: { error: 'TELEGRAM_ALREADY_BOUND', message: 'Этот Telegram-аккаунт уже используется другой организацией.' }
→ 400: { error: 'ALREADY_VERIFIED', message: 'Telegram уже подключён к этому аккаунту.' }
→ 422: { errors: [{ field: 'code', message: 'Код должен содержать 6 цифр' }] }
DELETE /api/v1/seller/telegram
Auth: Bearer (seller)
→ 204
→ 400: { error: 'NOT_CONNECTED', message: 'Telegram не подключён.' }
────────────────────────────────────────────────────────────────
ВНУТРЕННИЙ: ОТПРАВКА УВЕДОМЛЕНИЯ (не public API)
────────────────────────────────────────────────────────────────
NotificationService.sendNewLeadNotification(payload: SendNewLeadNotificationPayload): Promise<void>
→ Вызывается внутри LeadService после создания лида
→ Не является HTTP-эндпоинтом, это внутренний сервисный вызов
→ Результат пишется в NotificationLog
────────────────────────────────────────────────────────────────
TELEGRAM BOT WEBHOOK (внутренний)
────────────────────────────────────────────────────────────────
POST /api/internal/telegram/webhook
Auth: Telegram secret token в заголовке X-Telegram-Bot-Api-Secret-Token
Body: Telegram Update объект
→ Обрабатывает /start команду:
1. Извлекает chat_id из message.chat.id
2. Генерирует 6-значный код
3. Сохраняет TelegramVerificationCode { code, chat_id, expires_at: now()+10min }
4. Отправляет код обратно в чат
→ 200 (всегда, чтобы Telegram не делал retry)
7. Edge Cases
| Сценарий | Поведение |
|---|---|
| Продавец написал /start боту дважды | Второй /start инвалидирует старый код (used=true) и генерирует новый |
| Продавец ввёл код, который принадлежит другому seller_id | CODE_INVALID — не раскрываем что код существует для другого аккаунта |
| Telegram заблокировал бота (bot was blocked by user) | Telegram API возвращает 403 "Forbidden: bot was blocked by user"; система фиксирует в NotificationLog.status = 'failed'; fallback на Email |
| Продавец удалил переписку с ботом | Telegram API возвращает 400 "Bad Request: chat not found"; то же поведение что выше |
| Telegram API недоступен > 24 часов | Email fallback работает; Admin видит alert (мониторинг TBD); продавец не замечает если Email работает |
| Продавец отключился от Telegram, пока уведомление уже в retry-очереди | Retry проверяет актуальный telegram_chat_id перед каждой попыткой; если null — переходим к Email |
| NotificationSettings не созданы (новый продавец) | При регистрации автоматически создаётся запись с дефолтными значениями (notify_new_lead_telegram=true, notify_new_lead_email=true) |
| Email продавца изменился с момента создания настроек | Email берётся из профиля в момент отправки, не кешируется в NotificationSettings |
| Два лида созданы одновременно для одного продавца | Два независимых уведомления; каждый лид → своё уведомление; очередь обрабатывает последовательно |
| Продавец отключил все уведомления и забыл об этом | Лиды создаются нормально; продавец видит их при входе в /seller/leads; уведомления не отправляются |
| SMTP bounce при вводе неверного email в профиле | NotificationLog bounce; автодеактивация email-уведомлений — TBD |
| Telegram API вернул успешный ответ, но сообщение не дошло | Внешняя проблема вне контроля системы; NotificationLog = sent; пользователь обращается в поддержку |
8. TBD / Сознательно опущено
| Тема | Статус | Примечание |
|---|---|---|
| SMTP-провайдер (Postmark/SendGrid/AWS SES) | TBD | Не выбран. Метод sendEmail() — абстракция над провайдером |
| Email-шаблоны (дизайн и HTML) | TBD | Дизайнер не утвердил шаблон; в MVP — функциональный plain/simple HTML |
| Уведомление покупателя при смене статуса лида | TBD | Продукт не определился: нужно ли уведомлять buyer? Исключено из MVP |
| Уведомления для Seller Staff | Вне скоупа MVP | В MVP уведомления только на аккаунт Owner; Staff-уведомления в v1.0 |
| Push-уведомления (браузер / iOS / Android) | Вне скоупа | Требует мобильного приложения или PWA; не планируется в MVP |
| SMS-уведомления | Вне скоупа MVP | Избыточно при наличии Telegram + Email; TBD для v1.5 |
| Уведомление продавца при смене статуса покупателем (реверс) | Вне скоупа | Покупатель не меняет статусы в MVP |
| Digest-уведомления (сводка за день/неделю) | Вне скоупа MVP | Запланировано в v1.5 |
| Шаблоны уведомлений редактируемые продавцом | Вне скоупа | Продавец не может кастомизировать текст уведомлений |
| In-app уведомления (колокольчик в кабинете) | TBD v1.0 | В MVP только Telegram + Email; in-app bell — v1.0 |
| Автодеактивация email при bounce | TBD | Механизм не определён; bounce просто логируется в MVP |
| Rate limiting для Telegram webhook | Реализовать | Защита от DDoS на /api/internal/telegram/webhook через secret token + IP whitelist Telegram |
| Аналитика доставляемости уведомлений | TBD | Дашборд delivery rate для Admin — v1.5 |
9. Зависимости
| Модуль | Связь |
|---|---|
| Spec 01 (Seller Onboarding) | Seller.telegram_chat_id, Seller.telegram_verified_at; TelegramVerificationCode; UC-06 (привязка Telegram) реализована там |
| Spec 02 (Items) | Item.item_name — используется в тексте уведомления |
| Spec 06 (Buyer Flow) | Создание лида → триггер для уведомления; LeadService вызывает NotificationService |
| Spec 09 (Lead Management) | Lead.lead_id, Lead.lead_status — данные в уведомлении; deep link в кабинет |
| Telegram Bot API | Внешняя зависимость: @qadam_notify_bot должен быть создан и настроен до MVP-запуска |
| SMTP-провайдер | Внешняя зависимость: TBD (Postmark / SendGrid / AWS SES); нужна настройка DNS (SPF, DKIM, DMARC) |
| Spec 15 (Billing) | Уведомления не связаны с биллингом напрямую; но новый лид = начисление $30 |