Design: Notification System
Date: 2026-05-18
Status: Draft — awaiting review
1. Overview
Система оповещений для craftshop: email для пользователей, email + Telegram для админа.
Архитектура — event-driven с in-memory очередью и retry. Задел на подключение новых каналов (WhatsApp, Viber, push).
2. Architecture
2.1 Components
2.2 Database (Prisma)
NotificationPreference (настройки пользователя)
| Field |
Type |
Description |
| id |
String (cuid) |
Primary key |
| userId |
String (cuid) |
FK → User (unique) |
| globalEnabled |
Boolean |
Главный переключатель |
| orderCreated |
Boolean |
Заказ создан |
| orderStatusChanged |
Boolean |
Статус заказа изменён |
| orderMessageReceived |
Boolean |
Новое сообщение в чате заказа |
| paymentStatusChanged |
Boolean |
Статус оплаты изменён |
| createdAt |
DateTime |
|
| updatedAt |
DateTime |
|
AdminNotificationSettings (настройки админа)
| Field |
Type |
Description |
| id |
String (cuid) |
Primary key |
| emailEnabled |
Boolean |
Email вкл/выкл |
| telegramEnabled |
Boolean |
Telegram вкл/выкл |
| telegramChatId |
String? |
ID чата админа с ботом |
| newOrder |
Boolean |
Новый заказ |
| newOrderMessage |
Boolean |
Новое сообщение в заказе |
| newReview |
Boolean |
Новый отзыв |
| authCodeDuplicate |
Boolean |
Дублировать код входа в TG |
| createdAt |
DateTime |
|
| updatedAt |
DateTime |
|
NotificationLog (лог отправки)
| Field |
Type |
Description |
| id |
String (cuid) |
Primary key |
| userId |
String? |
FK → User (null для админа) |
| eventType |
String |
Тип события |
| channel |
String |
'email' |
| status |
String |
'pending' |
| error |
String? |
Текст ошибки |
| payload |
Json |
Данные события |
| attempts |
Int |
Количество попыток |
| createdAt |
DateTime |
|
| updatedAt |
DateTime |
|
2.3 Queue
- In-memory массив задач
- Воркер:
setInterval каждые 2 секунды, максимум 5 параллельных отправок
- Retry: 3 попытки с задержкой 5с, 30с, 120с
- При рестарте сервера: все
pending записи помечаются как failed
3. Events
| Event |
Triggered in |
Payload |
Recipients |
order:created |
user-orders.js |
orderId, userId, orderData |
User (orderCreated), Admin (newOrder) |
order:statusChanged |
admin-orders.js |
orderId, userId, oldStatus, newStatus |
User (orderStatusChanged) |
orderMessage:sent |
user-messages.js |
orderId, authorType, messageId |
Admin (newOrderMessage) |
orderMessage:adminReply |
admin-orders.js |
orderId, userId, messageId |
User (orderMessageReceived) |
payment:statusChanged |
user-payments.js |
orderId, userId, paymentStatus |
User (paymentStatusChanged) |
auth:codeRequested |
auth.js |
email, code, isAdmin |
User (email), Admin (authCodeDuplicate if isAdmin) |
4. Data Flow
5. Channel Interface
Каждый канал реализует:
5.1 Email Channel
- Использует существующий nodemailer transporter из
email.js
sendNotificationEmail({ to, subject, html })
- HTML-шаблоны в
email-templates.js
5.2 Telegram Channel
- Telegram Bot API:
POST https://api.telegram.org/bot<TOKEN>/sendMessage
node-telegram-bot-api или прямой fetch
- Форматирование: HTML parse mode
- Шаблоны в
telegram-templates.js
6. Client-Side
6.1 User Notification Settings Page
- Route:
/me/notifications
- MUI переключатели:
- Главный toggle "Получать оповещения" (globalEnabled)
- При включённом: 4 toggles для каждого типа события
- При выключенном: все остальные toggles disabled
- Сохранение через
apiClient + @tanstack/react-query mutation + invalidate
6.2 Admin Notification Settings
- Встраивается в существующую админку
- Toggle email, toggle telegram
- Если telegram включён — поле telegramChatId (заполняется автоматически при /start бота)
- Toggle для каждого типа события + toggle дублирования кода входа
7. Error Handling
| Scenario |
Behavior |
| SMTP/Telegram недоступен |
Retry 3 раза (5с → 30с → 120с), затем failed |
| Невалидный email / chatId |
Сразу failed, без retry |
| Ошибка рендера шаблона |
failed, лог в NotificationLog.error |
| Сервер рестарт |
pending → failed при старте воркера |
8. Security
TELEGRAM_BOT_TOKEN — только в .dev_env, не коммитится
- Telegram chatId запоминается при
/start от админа
- Настройки пользователя — только через
fastify.authenticate
- Настройки админа — только через
fastify.verifyAdmin
9. Extensibility
Adding a new channel (WhatsApp, Viber, push)
- Новый файл в
channels/ с интерфейсом { name, send(job) }
- Регистрация в
queue.js
- Никакие другие файлы не меняются
Adding a new event type
- Добавить в константы типов событий
- Добавить поле в
NotificationPreference / AdminNotificationSettings
- Эмитить через
eventBus.emit() в нужном роуте
Adding new recipients (broadcasts)
NotificationLog.userId nullable — поддерживает системные события
- Очередь поддерживает batch-задачи
10. Environment Variables
| Variable |
Description |
Required |
| SMTP_HOST |
SMTP сервер |
Да (для email) |
| SMTP_PORT |
SMTP порт |
Да |
| SMTP_SECURE |
SSL/TLS |
Да |
| SMTP_USER |
SMTP логин |
Да |
| SMTP_PASS |
SMTP пароль |
Да |
| MAIL_FROM |
From address |
Да |
| TELEGRAM_BOT_TOKEN |
Токен Telegram бота |
Для Telegram канала |
11. Implementation Notes
eventBus декорируется на fastify instance (как slugify, parseMaterialsInput)
bootstrap-admin.js создаёт AdminNotificationSettings при создании админа
- При создании пользователя — создаётся
NotificationPreference с defaults (всё включено)
- Существующий
sendLoginCodeEmail остаётся, добавляется sendNotificationEmail
12. Telegram Bot — Setup Flow
- Админ запускает бота командой
/start
- Бот проверяет, что sender — админ (сверка email через webhook или ручной ввод
TELEGRAM_ADMIN_CHAT_ID в .dev_env)
- Если совпадает — сохраняет
chatId в AdminNotificationSettings.telegramChatId
- Если
telegramChatId уже установлен — бот просто подтверждает подписку
Fallback: если webhook не настроен, админ вручную вписывает свой chatId в настройки админки.