Представим, что вы разрабатываете SaaS-приложение для управления проектами.
Пользователи создают задачи, приглашают сотрудников, оставляют комментарии и отслеживают изменения проектов.
В какой-то момент появляется новое требование: пользователи должны получать уведомления.
Например:
Когда их назначают исполнителями задачи.
Когда кто-то оставляет комментарий.
Когда заканчивается пробный период.
Когда изменяется статус платежа.
Когда коллега приглашает их в организацию.
Когда завершается генерация отчёта.
Сначала кажется, что достаточно отправлять Email после каждого события.
Но вскоре возникают вопросы.
Что делать, если SMTP-сервер недоступен?
Как не замедлять HTTP-запросы отправкой писем?
Как показывать уведомления в React без обновления страницы?
Как доставлять Push-уведомления, когда вкладка закрыта?
Как избежать дублирования сообщений?
Как учитывать пользовательские настройки?
И как построить систему, которая выдержит сотни тысяч уведомлений?
Для решения этих задач нужна продуманная архитектура системы уведомлений.
В этой статье разберём, как реализовать уведомления в веб-приложении, сравним Email, WebSocket, Server-Sent Events и Web Push, создадим Backend на FastAPI, подключим Celery и Redis, спроектируем PostgreSQL и подготовим систему к Production.
1. Что такое система уведомлений
Notification System — подсистема приложения, которая информирует пользователей о важных событиях.
Например:
User A creates a task
|
v
Backend
|
v
Notification System
|
+--> In-App Notification
|
|
+--> Web Push
Одна бизнес-операция может запускать несколько каналов доставки.
При этом не каждое событие должно отправляться во все каналы.
Например, комментарий к задаче можно показать внутри приложения, а критическое предупреждение о безопасности — дополнительно отправить по Email.
2. Какие типы уведомлений существуют
Типы уведомлений в SaaS:
Тип | Пример | Основная задача |
Transactional | Подтверждение Email | Подтверждение операции |
Security | Новый вход в аккаунт | Безопасность |
Collaboration | Новый комментарий | Совместная работа |
Billing | Ошибка оплаты | Управление подпиской |
System | Завершение отчёта | Информирование |
Marketing | Новая функция | Вовлечение пользователей |
Разные категории требуют разных правил доставки.
Например, пользователь может отключить маркетинговые письма, но важные сообщения о безопасности обычно обрабатываются отдельно.
3. Чем отличаются Email, In-App и Push
Email vs Push vs In-App Notifications:
Канал | Преимущества | Ограничения |
Работает вне приложения, удобен для подробных сообщений | Задержки, Spam Filters | |
In-App | История уведомлений, удобная навигация | Требует открытия приложения |
Web Push | Может уведомлять вне открытой вкладки | Разрешение браузера, поддержка платформы |
WebSocket | Быстрая доставка в активное приложение | Требует активного соединения |
SSE | Простой поток событий от сервера к браузеру | Односторонняя передача |
На практике часто используют несколько каналов одновременно.
4. Как выглядит архитектура уведомлений
Notification Service Architecture:
Business Event
|
v
Notification Service
|
v
PostgreSQL
|
v
|
+----------+----------+
| | |
v v v
Email Web Push Real-Time
Worker Worker Gateway
| | |
v v v
SMTP/API Push Service WebSocket
| | |
+----------+----------+
|
v
User
Здесь важно разделить три процесса:
Возникновение бизнес-события.
Создание уведомления.
Доставка уведомления по каналам.
Это позволяет повторять доставку, не повторяя исходную бизнес-операцию.
5. Почему нельзя отправлять Email прямо из Endpoint
Асинхронная отправка уведомлений нужна для снижения задержек и повышения надёжности.
Плохой пример:
@app.post("/tasks")
async def create_task(payload: CreateTask):
task = await create_task_in_db(payload)
await send_email(
recipient=task.assignee_email,
subject="New task",
)
return task
Если Email Provider отвечает пять секунд, пользователь будет ждать завершения запроса.
Если отправка завершится ошибкой, может возникнуть неоднозначная ситуация: задача уже создана, но HTTP-запрос вернул ошибку.
Лучше отделить создание задачи от отправки уведомления.
6. Почему уведомления должны быть Event-Driven
Event-Driven Notifications позволяют реагировать на бизнес-события.
Например:
task.created
task.assigned
comment.created
invoice.payment_failed
report.completed
Каждое событие содержит данные, необходимые для обработки.
Пример:
{
"event_id": "evt_123",
"event_type": "task.assigned",
"organization_id": "org_42",
"task_id": "task_100",
"recipient_user_id": "user_7"
}
Обработчик уведомлений определяет, нужно ли создать уведомление и какие каналы использовать.
7. Нужно ли сразу создавать отдельный микросервис
Notification Microservice vs Modular Monolith — архитектурный выбор.
Для небольшого приложения достаточно отдельного модуля внутри FastAPI:
backend/
modules/
tasks/
users/
billing/
notifications/
Уведомления могут использовать общую базу данных и очередь задач.
Отдельный микросервис имеет смысл, когда появляются независимые требования к масштабированию, эксплуатации или обслуживанию нескольких продуктов.
На старте модульный монолит часто проще.
8. Как спроектировать таблицу уведомлений
PostgreSQL Notifications Schema:
CREATE TABLE notifications (
id UUID PRIMARY KEY,
organization_id UUID NOT NULL,
recipient_user_id UUID NOT NULL,
type TEXT NOT NULL,
title TEXT NOT NULL,
body TEXT NOT NULL,
payload JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
read_at TIMESTAMPTZ
);
Эта таблица хранит уведомления, которые пользователь видит внутри приложения.
Поле payload может содержать безопасные структурированные данные для навигации.
Например:
{
"project_id": "project_123",
"task_id": "task_456"
}
Не следует помещать туда пароли, токены или другие секреты.
9. Как создать индексы для уведомлений
PostgreSQL Index Notifications:
CREATE INDEX idx_notifications_user_created
ON notifications (
recipient_user_id,
created_at DESC,
id DESC
);
Это помогает получать последние уведомления пользователя.
Для Multi-Tenant SaaS можно использовать индекс с organization_id, если запросы всегда фильтруются по организации.
10. Как хранить статус прочтения
Read Unread Notifications обычно реализуются через read_at.
Например:
UPDATE notifications
SET read_at = NOW()
WHERE id = :notification_id
AND recipient_user_id = :user_id
AND organization_id = :organization_id
AND read_at IS NULL;
Важно проверять владельца уведомления.
Нельзя разрешать пользователю помечать чужие уведомления прочитанными.
11. Как получить количество непрочитанных уведомлений
Unread Notifications Count:
SELECT COUNT(*)
FROM notifications
WHERE recipient_user_id = :user_id
AND organization_id = :organization_id
AND read_at IS NULL;
Для небольшого количества уведомлений такой запрос может быть достаточным.
При высокой нагрузке можно рассмотреть частичные индексы, кеширование или отдельные агрегированные счётчики.
Но счётчики требуют корректной синхронизации при изменениях.
12. Как реализовать Notification API на FastAPI
FastAPI Notifications API может включать следующие Endpoint:
GET /api/notifications
GET /api/notifications/unread-count
PATCH /api/notifications/{id}/read
POST /api/notifications/mark-all-read
GET /api/notifications/preferences
PUT /api/notifications/preferences
Все операции должны выполняться в контексте авторизованного пользователя.
Для SaaS также необходимо учитывать текущую организацию.
13. Как получить список уведомлений
FastAPI Notification Endpoint:
from fastapi import Depends, Query
@app.get("/api/notifications")
async def list_notifications(
limit: int = Query(20, ge=1, le=100),
current_user=Depends(get_current_user),
):
return await notification_repository.list_for_user(
user_id=current_user.id,
limit=limit,
)
Это упрощённый пример.
Для Production нужно добавить Cursor Pagination и проверку Tenant Context, если уведомления принадлежат организациям.
14. Почему Cursor Pagination лучше для длинной истории
Cursor Pagination Notifications полезна, когда история уведомлений постоянно растёт.
Offset Pagination:
GET /notifications?offset=1000&limit=20
может становиться менее эффективной на больших объёмах.
Cursor Pagination использует последнюю позицию:
GET /notifications?cursor=...&limit=20
Для устойчивого порядка удобно использовать комбинацию created_at и id.
15. Как реализовать Mark All as Read
Mark All Notifications Read:
UPDATE notifications
SET read_at = NOW()
WHERE recipient_user_id = :user_id
AND organization_id = :organization_id
AND read_at IS NULL;
Если необходимо пометить прочитанными только уведомления, существовавшие на момент нажатия кнопки, можно дополнительно передавать проверенную временную границу или Cursor.
16. Как реализовать Email Notifications
Email Notifications FastAPI можно отправлять через SMTP или API Email Provider.
Например:
Amazon SES;
SendGrid;
Mailgun;
Postmark;
собственный SMTP-сервер.
Выбор зависит от стоимости, требований к доставляемости, региональной доступности и объёма писем.
17. Как работает SMTP
SMTP Email Sending Python:
FastAPI / Worker
|
v
SMTP Server
|
v
Recipient Mail Server
|
v
Mailbox
SMTP-сервер принимает письмо для дальнейшей доставки.
Но успешная передача SMTP-серверу не означает, что письмо обязательно появилось во входящих получателя.
Оно может быть отклонено, задержано или классифицировано как Spam.
18. Как отправить Email через Python
Python SMTP Email Example:
import os
import smtplib
from email.message import EmailMessage
def send_email(
recipient: str,
subject: str,
body: str,
):
message = EmailMessage()
message["From"] = os.environ["EMAIL_FROM"]
message["To"] = recipient
message["Subject"] = subject
message.set_content(body)
with smtplib.SMTP(
os.environ["SMTP_HOST"],
int(os.environ.get("SMTP_PORT", "587")),
timeout=15,
) as smtp:
smtp.starttls()
smtp.login(
os.environ["SMTP_USERNAME"],
os.environ["SMTP_PASSWORD"],
)
smtp.send_message(message)
Этот пример предполагает SMTP-сервер с поддержкой STARTTLS на соответствующем порту.
В Production необходимо проверять конфигурацию TLS, обрабатывать ошибки и не хранить учётные данные в исходном коде.
19. Как создавать HTML Email Templates
HTML Email Templates позволяют отправлять письма с единым дизайном.
Например:
<h1>Вас назначили на задачу</h1>
<p>Откройте приложение, чтобы посмотреть детали.</p>
<a href="https://app.example.com/tasks/123">
Открыть задачу
</a>
Для генерации шаблонов можно использовать Jinja2.
Но данные пользователя необходимо безопасно экранировать.
Также полезно создавать текстовую версию письма.
20. Как настроить SPF, DKIM и DMARC
Email Deliverability SPF DKIM DMARC — важная часть Production-инфраструктуры.
SPF помогает определить, какие серверы могут отправлять почту от имени домена.
DKIM позволяет криптографически подписывать письма.
DMARC задаёт политику обработки сообщений, которые не проходят соответствующие проверки и выравнивание доменов.
Без корректной настройки почтовой аутентификации доставляемость может ухудшаться.
21. Как обрабатывать Bounce и Complaint
Email Bounce Handling необходимо для поддержания репутации отправителя.
Bounce означает, что письмо не удалось доставить.
Complaint может означать жалобу получателя на Spam.
Email Provider обычно предоставляет Webhooks или события доставки.
Система должна учитывать:
Permanent Bounce;
Temporary Bounce;
Spam Complaint;
Unsubscribe;
Delivered;
Deferred.
При постоянных ошибках адрес может потребоваться исключить из дальнейших отправок.
22. Зачем использовать Celery
Celery Email Notifications позволяет выполнять отправку писем вне HTTP-запроса.
Архитектура:
FastAPI
|
v
Task Queue
|
v
Celery Worker
|
v
Email Provider
Теперь Endpoint может завершиться сразу после надёжной регистрации события для последующей обработки.
Однако сама постановка задачи в очередь тоже может завершиться ошибкой.
Для критичных событий нужна продуманная стратегия сохранения и доставки.
23. Как настроить Celery и Redis
Celery Redis Notification Queue:
from celery import Celery
celery_app = Celery(
"notifications",
broker="redis://redis:6379/0",
)
Пример задачи:
@celery_app.task
def send_notification_email(
recipient: str,
subject: str,
body: str,
):
send_email(
recipient=recipient,
subject=subject,
body=body,
)
В Production параметры подключения должны задаваться через конфигурацию.
Также необходимо учитывать повторную доставку, мониторинг очереди и безопасность Redis.
24. Как настроить Retry
Celery Retry Notifications:
@celery_app.task(
bind=True,
autoretry_for=(TimeoutError, ConnectionError),
retry_backoff=True,
retry_jitter=True,
retry_kwargs={"max_retries": 5},
)
def send_email_task(
self,
recipient: str,
subject: str,
body: str,
):
send_email(
recipient,
subject,
body,
)
В этом примере повторяются только указанные типы ошибок.
Реальные библиотеки SMTP могут генерировать другие исключения, которые необходимо классифицировать.
Нельзя автоматически повторять абсолютно любую ошибку.
Например, неверный адрес получателя может быть постоянной ошибкой.
25. Почему Email может отправиться дважды
Duplicate Email Prevention — одна из сложностей асинхронной обработки.
Например:
Worker sends Email
|
v
Email Provider accepts message
|
v
Worker crashes
|
v
Queue retries task
|
v
Email sent again
Идеальную Exactly-Once Delivery для внешнего Email Provider обычно нельзя гарантировать только средствами очереди.
Поэтому полезно хранить Delivery Records, использовать Idempotency Keys там, где они поддерживаются провайдером, и проектировать обработку с учётом возможных повторов.
26. Как спроектировать Notification Deliveries
Notification Delivery Tracking:
CREATE TABLE notification_deliveries (
id UUID PRIMARY KEY,
notification_id UUID NOT NULL
REFERENCES notifications(id),
channel TEXT NOT NULL,
status TEXT NOT NULL,
attempts INTEGER NOT NULL DEFAULT 0,
provider_message_id TEXT,
last_error TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
delivered_at TIMESTAMPTZ
);
Возможные статусы:
pending
processing
sent
delivered
failed
canceled
Важно определить точную семантику каждого статуса.
Например, sent может означать принятие сообщения провайдером, а delivered — подтверждение доставки провайдером.
Это не одно и то же.
27. Что такое Dead Letter Queue
Dead Letter Queue Notifications — место для сообщений, которые не удалось обработать после допустимого количества попыток.
Например:
Notification
|
v
Retry 1
|
v
Retry 2
|
v
Retry 3
|
v
Dead Letter Queue
Такие сообщения можно анализировать и повторно обрабатывать после исправления проблемы.
Но автоматический повтор без ограничений может создавать бесконечную нагрузку.
28. Как реализовать Real-Time Notifications
Real-Time Notifications React FastAPI позволяют показывать уведомления без обновления страницы.
Например:
User A comments
|
v
FastAPI
|
v
WebSocket
|
v
User B sees notification
Для реализации можно использовать WebSocket или Server-Sent Events.
29. WebSocket vs SSE для уведомлений
WebSocket vs Server Sent Events:
Критерий | WebSocket | SSE |
Направление | Двустороннее | Сервер → клиент |
Протокол | WebSocket | HTTP |
Поддержка браузеров | Широкая | Широкая |
Автоматическое переподключение | Обычно реализуется клиентом | Есть встроенный механизм EventSource |
Подходит для уведомлений | Да | Да |
Подходит для чата | Да | Ограниченно |
Если приложение только отправляет события от сервера к браузеру, SSE может быть проще.
Если уже используется WebSocket для чата или совместной работы, уведомления можно передавать через существующее соединение.
30. Как создать WebSocket Endpoint в FastAPI
FastAPI WebSocket Notifications:
from fastapi import WebSocket, WebSocketDisconnect
@app.websocket("/ws/notifications")
async def notifications_socket(
websocket: WebSocket,
):
user = await authenticate_websocket(websocket)
if user is None:
await websocket.close(code=1008)
return
await websocket.accept()
try:
while True:
await websocket.receive_text()
except WebSocketDisconnect:
pass
authenticate_websocket — функция приложения, которую необходимо реализовать отдельно.
Аутентификация WebSocket должна учитывать способ передачи Credentials и модель угроз.
Нельзя доверять произвольному User ID из Query Parameters.
31. Как подключить WebSocket в React
React WebSocket Notifications:
import { useEffect } from "react";
export function useNotifications(
onNotification: (data: unknown) => void
) {
useEffect(() => {
const socket = new WebSocket(
"wss://app.example.com/ws/notifications"
);
socket.onmessage = (event) => {
const data: unknown = JSON.parse(event.data);
onNotification(data);
};
return () => {
socket.close();
};
}, [onNotification]);
}
В реальном приложении необходимо добавить:
проверку структуры сообщений;
Authentication;
Reconnection;
Heartbeat;
повторную синхронизацию после разрыва.
Также onNotification лучше стабилизировать через useCallback, чтобы не создавать новое соединение при каждом рендере.
32. Почему WebSocket не гарантирует доставку
WebSocket Message Delivery зависит от состояния соединения.
Например:
Notification created
|
v
WebSocket disconnected
|
v
Message not received
Поэтому WebSocket не должен быть единственным хранилищем уведомлений.
Лучше сначала сохранить уведомление в PostgreSQL.
Затем отправить Real-Time событие.
После переподключения клиент должен синхронизировать историю через REST API.
33. Как организовать Reconnection
WebSocket Reconnection Strategy может использовать Exponential Backoff.
Например:
1 second
2 seconds
4 seconds
8 seconds
16 seconds
Дополнительно полезен Jitter, чтобы тысячи клиентов не переподключались одновременно после сбоя.
После восстановления соединения необходимо повторно получить непрочитанные уведомления.
34. Как масштабировать WebSocket через Redis
Redis Pub Sub WebSocket Scaling:
Redis Pub/Sub
|
+----------+----------+
| |
v v
FastAPI 1 FastAPI 2
| |
v v
Client A Client B
Если уведомление создаётся на FastAPI 1, Redis может передать событие другим экземплярам.
Каждый Backend отправляет событие своим подключённым клиентам.
35. Почему Redis Pub/Sub не является очередью доставки
Redis Pub/Sub Limitations важно учитывать.
Redis Pub/Sub не хранит сообщения для отключённых подписчиков.
Если экземпляр FastAPI временно недоступен, он может пропустить событие.
Поэтому Pub/Sub подходит для оперативного распространения сигналов.
Но надёжную историю уведомлений нужно хранить отдельно.
Для более сложных сценариев можно использовать Redis Streams, RabbitMQ или Kafka.
36. Redis Pub/Sub vs Redis Streams
Redis PubSub vs Streams:
Критерий | Pub/Sub | Streams |
Хранение сообщений | Нет | Да |
История событий | Нет | Да |
Consumer Groups | Нет | Да |
Подтверждение обработки | Нет | Да |
Типичный сценарий | Live Broadcasting | Обработка событий |
Для обычного обновления UI Pub/Sub может быть достаточным.
Для надёжной обработки задач лучше использовать механизм с сохранением сообщений.
37. Как реализовать Server-Sent Events
FastAPI SSE Notifications можно использовать для однонаправленной передачи событий.
Пример на основе StreamingResponse:
import asyncio
from fastapi.responses import StreamingResponse
@app.get("/api/notifications/stream")
async def notification_stream(
current_user=Depends(get_current_user),
):
async def event_generator():
while True:
yield ": heartbeat\n\n"
await asyncio.sleep(15)
return StreamingResponse(
event_generator(),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"X-Accel-Buffering": "no",
},
)
Этот пример демонстрирует формат SSE и Heartbeat, но пока не передаёт реальные уведомления.
Для полноценной реализации нужно подключить источник событий, обработать отключение клиента и обеспечить безопасную аутентификацию.
38. Как подключить SSE в React
React EventSource Notifications:
useEffect(() => {
const events = new EventSource(
"/api/notifications/stream"
);
events.onmessage = (event) => {
const notification = JSON.parse(event.data);
console.log(notification);
};
return () => {
events.close();
};
}, []);
Стандартный EventSource не предоставляет произвольную настройку Authorization Header.
Поэтому для Cookie Authentication или другой схемы доступа нужно заранее продумать безопасность соединения.
39. Что такое Web Push Notifications
Web Push Notifications позволяют отправлять уведомления через браузерную Push-инфраструктуру.
В отличие от WebSocket, Push может доставляться, когда вкладка приложения закрыта, если браузер и платформа поддерживают соответствующий сценарий.
Но доставка не гарантируется.
Она зависит от:
разрешений пользователя;
браузера;
операционной системы;
Push Service;
состояния устройства;
ограничений энергосбережения.
40. Как работает Web Push API
Web Push API Architecture:
FastAPI
|
v
Web Push Service
|
v
Browser Push Service
|
v
Service Worker
|
v
System Notification
Для Web Push обычно используются:
Service Worker;
Push API;
Notifications API;
VAPID;
HTTPS.
41. Что такое Service Worker
Service Worker Push Notifications — браузерный Worker, который может обрабатывать Push Events.
Например:
self.addEventListener("push", (event) => {
const data = event.data
? event.data.json()
: {};
event.waitUntil(
self.registration.showNotification(
data.title || "Новое уведомление",
{
body: data.body || "",
icon: "/icon.png",
}
)
);
});
Этот код показывает системное уведомление.
Но в Production необходимо проверять формат Payload и избегать небезопасного использования недоверенных URL.
42. Как зарегистрировать Service Worker в React
React Service Worker Registration:
async function registerServiceWorker() {
if (!("serviceWorker" in navigator)) {
return null;
}
return await navigator.serviceWorker.register(
"/service-worker.js"
);
}
Для Push также нужно проверить поддержку PushManager и разрешение на уведомления.
Важно не запрашивать разрешение сразу при первом посещении без объяснения пользы.
Лучше предложить пользователю включить уведомления в подходящем контексте.
43. Что такое VAPID
VAPID Web Push Authentication — механизм идентификации сервера приложения при взаимодействии с Push Service.
VAPID использует криптографические ключи.
Публичный ключ передаётся браузеру при создании подписки.
Приватный ключ остаётся на Backend.
Нельзя включать приватный VAPID Key в React Bundle.
44. Как создать Push Subscription
Push API Subscription React:
async function subscribeToPush(
registration: ServiceWorkerRegistration,
publicKey: Uint8Array
) {
return await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: publicKey,
});
}
Перед вызовом необходимо получить разрешение пользователя.
На некоторых платформах действуют дополнительные ограничения, например требования к установке веб-приложения.
Также необходимо корректно преобразовать VAPID Public Key в формат, принимаемый браузером.
45. Как хранить Push Subscriptions
Push Subscriptions PostgreSQL:
CREATE TABLE push_subscriptions (
id UUID PRIMARY KEY,
user_id UUID NOT NULL,
endpoint TEXT NOT NULL UNIQUE,
p256dh TEXT NOT NULL,
auth_secret TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
Один пользователь может иметь несколько устройств.
Поэтому нельзя хранить только одну Push Subscription на пользователя.
Endpoint и криптографические параметры подписки являются чувствительными данными, доступ к которым нужно ограничивать.
46. Как отправить Web Push через Python
Python Web Push pywebpush:
import json
import os
from pywebpush import webpush
def send_web_push(subscription, payload):
webpush(
subscription_info=subscription,
data=json.dumps(payload),
vapid_private_key=os.environ[
"VAPID_PRIVATE_KEY"
],
vapid_claims={
"sub": "mailto:admin@example.com"
},
)
Пример предполагает установленную библиотеку pywebpush.
В Production нужно обрабатывать ошибки Push Service и удалять недействительные подписки при соответствующих ответах провайдера.
47. Почему Push Subscription может перестать работать
Web Push Subscription Expiration может происходить из-за:
отзыва разрешения;
смены браузерного профиля;
удаления данных сайта;
изменений на устройстве;
недействительного Endpoint.
Поэтому необходимо регулярно очищать неработающие подписки.
Но нельзя удалять подписку после любой временной сетевой ошибки.
48. Как реализовать пользовательские настройки
Notification Preferences SaaS позволяют пользователю управлять каналами.
Например:
Событие | In-App | Push | |
Новая задача | Да | Да | Да |
Комментарий | Да | Нет | Да |
Завершение отчёта | Да | Да | Нет |
Маркетинговые новости | Нет | Нет | Нет |
Настройки лучше хранить на Backend.
Их необходимо учитывать при создании Delivery Records.
49. Как спроектировать таблицу Preferences
Notification Preferences Database:
CREATE TABLE notification_preferences (
user_id UUID NOT NULL,
event_type TEXT NOT NULL,
channel TEXT NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
PRIMARY KEY (
user_id,
event_type,
channel
)
);
Для Multi-Tenant SaaS можно дополнительно учитывать organization_id, если настройки должны различаться между организациями.
50. Что такое Quiet Hours
Notification Quiet Hours — период, когда пользователь не хочет получать некритичные уведомления.
Например:
22:00 - 08:00
Но нужно учитывать часовой пояс пользователя.
Также необходимо определить, какие категории уведомлений могут обходить Quiet Hours.
Например, предупреждение о компрометации аккаунта может требовать отдельной политики.
51. Как избежать Notification Fatigue
Notification Fatigue — ситуация, когда пользователь получает слишком много сообщений.
Например, в проекте оставили 50 комментариев за десять минут.
Отправлять 50 Email может быть плохой идеей.
Вместо этого можно создать Digest:
В вашем проекте 50 новых комментариев.
Для снижения количества уведомлений используют:
Aggregation;
Debouncing;
Batching;
Daily Digest;
Weekly Digest;
Priority Levels.
52. Как реализовать Notification Digest
Email Digest Notifications:
Events
|
v
Aggregation Window
|
v
Digest Builder
|
v
Email Worker
|
v
User
Например, система собирает события за час и отправляет одно письмо.
Но критичные уведомления не должны задерживаться только ради агрегации.
53. Как реализовать приоритеты уведомлений
Notification Priority Levels:
low
normal
high
critical
Приоритет может влиять на:
выбор канала;
скорость обработки;
Retry Policy;
Quiet Hours;
отображение в интерфейсе.
Но приоритет должен определяться серверной бизнес-логикой.
Клиент не должен иметь возможность произвольно отправлять критичные уведомления другим пользователям.
54. Как защитить систему от спама
Notification Spam Prevention включает:
Rate Limiting;
Quotas;
Permission Checks;
ограничения массовых рассылок;
защиту Invitation Endpoint;
фильтрацию дублирующихся событий;
мониторинг аномалий.
Особенно важно защищать события, инициируемые обычными пользователями.
Например, злоумышленник не должен иметь возможность отправить тысячи приглашений через API.
55. Как использовать Transactional Outbox
Transactional Outbox Notifications решает проблему согласованности между базой данных и очередью.
Представим:
Create Task
|
v
Save to PostgreSQL
|
v
Publish Event
Если запись задачи прошла успешно, а публикация события завершилась ошибкой, уведомление может потеряться.
Transactional Outbox позволяет записать бизнес-операцию и событие в одной транзакции.
56. Как выглядит Outbox Pattern
Outbox Pattern FastAPI PostgreSQL:
Database Transaction
|
+--> INSERT task
|
+--> INSERT outbox_event
|
v
COMMIT
|
v
Outbox Worker
|
v
Message Broker
Пример таблицы:
CREATE TABLE outbox_events (
id UUID PRIMARY KEY,
event_type TEXT NOT NULL,
payload JSONB NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
published_at TIMESTAMPTZ
);
Worker читает неопубликованные события и отправляет их в Broker.
После подтверждения публикации обновляет published_at.
Но возможна повторная публикация после сбоя.
Поэтому Consumers должны быть идемпотентными.
57. Что такое At-Least-Once Delivery
At Least Once Notification Delivery означает, что сообщение может быть доставлено один или несколько раз.
Это распространённая модель надёжной обработки событий.
Поэтому система должна быть готова к дубликатам.
Например, можно использовать уникальный ключ:
event_id + recipient_id + channel
Такой ключ помогает предотвращать создание нескольких одинаковых Delivery Records.
58. Как реализовать Idempotency
Notification Idempotency Key:
ALTER TABLE notification_deliveries
ADD COLUMN idempotency_key TEXT UNIQUE;
Пример:
task-assigned-123:user-42:email
Но для разных повторных назначений одной задачи необходимо использовать уникальный идентификатор конкретного события, а не только Task ID.
Иначе легитимные новые уведомления могут быть ошибочно подавлены.
59. Как обеспечить безопасность уведомлений
Notification System Security включает:
Authentication;
Authorization;
Tenant Isolation;
безопасные Templates;
защиту от XSS;
ограничение Push Payload;
защиту Email Addresses;
безопасное хранение Subscriptions;
Rate Limiting;
Audit Logs.
Особенно важно проверять, что пользователь получает только собственные уведомления.
60. Как защитить Multi-Tenant Notifications
Multi Tenant Notification Isolation:
Organization A
|
+--> User A
|
+--> Notifications A
Organization B
|
+--> User B
|
+--> Notifications B
Backend должен проверять организацию и получателя.
Например:
SELECT *
FROM notifications
WHERE organization_id = :organization_id
AND recipient_user_id = :user_id
ORDER BY created_at DESC;
Это помогает предотвратить утечки между организациями.
61. Почему нельзя отправлять секреты в Push Payload
Web Push Payload Security требует осторожности.
Push-уведомления могут отображаться на заблокированном экране.
Поэтому не стоит отправлять:
Access Tokens;
пароли;
финансовые реквизиты;
чувствительные документы;
конфиденциальные сообщения целиком.
Лучше отправить короткое уведомление и предложить открыть приложение для просмотра деталей после авторизации.
62. Как тестировать Notification System
Notification Testing FastAPI должно покрывать:
создание уведомления;
правильного получателя;
Tenant Isolation;
настройки каналов;
повторную доставку;
Retry;
ошибки Email Provider;
недействительные Push Subscriptions;
WebSocket Reconnection;
Mark as Read;
конкурентные события.
Особенно важны тесты на отсутствие дублирования.
63. Как тестировать Email без реальной отправки
Email Testing Python можно выполнять через Mock Provider или локальный SMTP Test Server.
Например:
def test_task_assignment_creates_notification(
client,
notification_repository,
):
response = client.post(
"/api/tasks",
json={
"title": "Prepare report",
"assignee_id": "user-42",
},
)
assert response.status_code == 201
assert notification_repository.exists_for_event(
"task.assigned"
)
Это иллюстрация тестового сценария.
В реальном проекте необходимо подготовить Fixtures, Authentication и Test Database.
64. Как тестировать WebSocket
WebSocket Integration Testing должно проверять:
подключение авторизованного пользователя;
отказ неавторизованному клиенту;
доставку события нужному пользователю;
отсутствие доставки чужому Tenant;
корректное отключение;
повторную синхронизацию.
WebSocket-тесты особенно важны при горизонтальном масштабировании.
65. Как мониторить доставку уведомлений
Notification Delivery Metrics:
Метрика | Что показывает |
Notifications Created | Количество созданных уведомлений |
Delivery Success Rate | Долю успешных доставок |
Delivery Failure Rate | Долю ошибок |
Queue Lag | Задержку обработки |
Retry Count | Количество повторов |
Email Bounce Rate | Долю недоставленных писем |
Push Subscription Failures | Ошибки Push-подписок |
WebSocket Connections | Активные соединения |
Мониторинг помогает выявлять проблемы раньше пользователей.
66. Как измерять Notification Latency
Notification Latency Monitoring — время между бизнес-событием и доставкой.
Например:
Task assigned:
12:00:00
Notification created:
12:00:00.050
WebSocket delivered:
12:00:00.180
В данном примере задержка Real-Time доставки составляет примерно 180 миллисекунд.
Это иллюстративные значения, а не гарантированный результат конкретной инфраструктуры.
Для Email задержки могут быть значительно больше.
67. Как масштабировать Notification Service
Notification Service Scaling включает:
несколько Celery Workers;
разделение очередей по каналам;
Redis Pub/Sub или другой Broker;
горизонтальное масштабирование WebSocket;
оптимизацию PostgreSQL;
Batch Processing;
Rate Limiting внешних провайдеров.
Например:
Notification Events
|
v
Message Broker
|
+----------------+----------------+
| | |
v v v
Email Queue Push Queue Real-Time Queue
| | |
v v v
Email Workers Push Workers WebSocket Nodes
Так можно независимо масштабировать разные каналы.
68. Как избежать перегрузки Email Provider
Email Provider Rate Limits могут ограничивать количество отправляемых писем.
Поэтому Worker должен учитывать:
скорость отправки;
ограничения аккаунта;
временные ошибки;
Retry-After;
Backoff;
лимиты получателей.
Если провайдер возвращает ошибку ограничения скорости, не стоит немедленно повторять запрос тысячами Workers.
69. Как организовать Production-инфраструктуру
Production Notification Architecture React FastAPI:
React Frontend
|
v
Nginx / Traefik
|
v
FastAPI
|
+-----------+-----------+
| |
v v
PostgreSQL Redis
| |
v v
Notification Data Pub/Sub
| |
v v
Outbox Worker WebSocket Nodes
|
v
Task Broker
|
+------+------+
| |
v v
Email Worker Push Worker
| |
v v
Email Provider Push Service
В этой архитектуре:
FastAPI создаёт бизнес-события.
PostgreSQL хранит уведомления и Outbox Events.
Outbox Worker обеспечивает публикацию событий.
Task Broker распределяет задачи доставки.
Email Worker отправляет письма.
Push Worker отправляет Web Push.
Redis Pub/Sub помогает доставлять Live Events.
WebSocket Nodes передают события подключённым пользователям.
React отображает историю уведомлений и обновляет интерфейс.
Но для небольшого проекта архитектуру можно упростить.
Например, начать с PostgreSQL, Celery и Email, а WebSocket и Push добавить позже.
70. Как внедрять систему уведомлений поэтапно
Notification System MVP Roadmap:
Этап 1. In-App Notifications
Реализовать:
таблицу уведомлений;
REST API;
список уведомлений;
статус прочтения;
количество непрочитанных.
Этап 2. Email Notifications
Добавить:
Email Templates;
Celery;
Retry;
Delivery Records;
Email Provider.
Этап 3. Real-Time
Добавить:
WebSocket или SSE;
Redis Pub/Sub;
Reconnection;
синхронизацию после отключения.
Этап 4. Web Push
Добавить:
Service Worker;
VAPID;
Push Subscriptions;
Push Worker;
очистку недействительных подписок.
Этап 5. Advanced Features
Добавить:
Preferences;
Quiet Hours;
Digest;
приоритеты;
расширенный мониторинг;
Transactional Outbox.
Такой подход позволяет не усложнять MVP раньше времени.
71. Типичные ошибки при разработке системы уведомлений
Первая ошибка — отправлять Email непосредственно из HTTP Endpoint.
Вторая — использовать WebSocket как единственное хранилище уведомлений.
Третья — не учитывать повторную доставку задач.
Четвёртая — не проверять Tenant Isolation.
Пятая — не хранить Delivery Status.
Шестая — повторять постоянные ошибки бесконечно.
Седьмая — отправлять секретные данные в Push Payload.
Восьмая — не учитывать настройки пользователя.
Девятая — не поддерживать Quiet Hours.
Десятая — отправлять слишком много уведомлений.
Одиннадцатая — не защищать массовые рассылки от злоупотреблений.
Двенадцатая — использовать Redis Pub/Sub как надёжную очередь.
Тринадцатая — не реализовывать Reconnection.
Четырнадцатая — не синхронизировать историю после восстановления соединения.
Пятнадцатая — не обрабатывать Bounce и Complaint.
Шестнадцатая — не настраивать SPF, DKIM и DMARC.
Семнадцатая — хранить VAPID Private Key во Frontend.
Восемнадцатая — не очищать недействительные Push Subscriptions.
Девятнадцатая — не учитывать Race Conditions.
Двадцатая — не мониторить задержку доставки.
Эти ошибки Notification System могут приводить к потере сообщений, дублированию Email, утечкам данных и перегрузке инфраструктуры.
72. Чек-лист Notification System для Production
Notification System Production Checklist:
Уведомления сохраняются в PostgreSQL.
Реализована проверка получателя.
Проверяется Tenant Context.
Есть REST API для истории уведомлений.
Реализован статус прочтения.
Используется Cursor Pagination при необходимости.
Email отправляется асинхронно.
SMTP Credentials хранятся безопасно.
Настроены SPF, DKIM и DMARC.
Обрабатываются Bounce и Complaint.
Есть Retry Policy.
Повторные события не создают неконтролируемые дубликаты.
Есть Delivery Tracking.
Есть обработка окончательных ошибок.
WebSocket требует Authentication.
WebSocket проверяет права пользователя.
После Reconnection выполняется синхронизация.
Redis Pub/Sub не используется как единственное хранилище событий.
Push Subscriptions привязаны к пользователю.
VAPID Private Key хранится на Backend.
Недействительные Push Subscriptions удаляются.
Пользователь может управлять настройками.
Учитываются Quiet Hours.
Критичные уведомления имеют отдельную политику.
Настроен Rate Limiting.
Секреты не попадают в уведомления.
Есть Audit Logs для важных операций.
Мониторятся Delivery Failures.
Мониторится Queue Lag.
Тестируются повторные события.
Тестируется Tenant Isolation.
Есть план восстановления после сбоя Provider.
FAQ
Что такое система уведомлений?
Это подсистема приложения, которая создаёт, хранит и доставляет пользователям сообщения о важных событиях.
Какие виды уведомлений существуют?
Email, In-App, Web Push, SMS и другие каналы.
Что лучше: WebSocket или SSE?
Для двустороннего обмена удобен WebSocket. Для однонаправленного потока событий SSE часто проще.
Можно ли реализовать уведомления только через WebSocket?
Можно показывать Live Events, но для надёжной истории уведомлений лучше использовать постоянное хранилище.
Зачем нужен Redis Pub/Sub?
Для распространения событий между несколькими экземплярами Backend.
Гарантирует ли Redis Pub/Sub доставку?
Нет. Отключённые подписчики могут пропустить сообщения.
Зачем нужен Celery?
Для выполнения отправки уведомлений и других фоновых задач вне HTTP-запросов.
Как избежать дублирования Email?
Использовать Delivery Records, Idempotency Keys и учитывать повторную обработку задач.
Что такое Web Push?
Механизм отправки Push-сообщений через браузерную инфраструктуру.
Нужен ли Service Worker для Web Push?
Да, стандартная браузерная Web Push архитектура использует Service Worker.
Что такое VAPID?
Механизм идентификации сервера приложения при работе с Web Push.
Можно ли отправлять Push без разрешения пользователя?
Обычные браузерные системные уведомления требуют соответствующего разрешения.
Что такое Notification Preferences?
Настройки, определяющие, какие уведомления и по каким каналам хочет получать пользователь.
Что такое Quiet Hours?
Период, когда некритичные уведомления задерживаются или отключаются.
Что такое Notification Digest?
Сводка нескольких событий в одном уведомлении.
Что такое Transactional Outbox?
Паттерн, позволяющий сохранять бизнес-изменение и событие для последующей публикации в одной транзакции.
Что такое At-Least-Once Delivery?
Модель доставки, при которой сообщение может обрабатываться повторно.
Нужно ли создавать отдельный Notification Microservice?
Не обязательно. Для небольшого приложения часто достаточно отдельного Backend-модуля.
Как хранить уведомления в PostgreSQL?
Обычно используют таблицу с получателем, типом, содержимым, временем создания и статусом прочтения.
Как масштабировать уведомления?
Через очереди задач, несколько Workers, распределение Live Events и оптимизацию базы данных.
Как защитить уведомления в SaaS?
Проверять Authentication, Authorization, Tenant Isolation и ограничивать доступ к истории и каналам доставки.
Заключение
Система уведомлений — важная часть современных SaaS-приложений.
Она связывает бизнес-события с действиями пользователей и помогает им вовремя получать нужную информацию.
Но надёжные уведомления требуют большего, чем один WebSocket Endpoint или функция отправки Email.
Необходимо разделять создание уведомления, его хранение и доставку.
PostgreSQL позволяет сохранять историю.
Celery помогает выполнять отправку асинхронно.
Redis Pub/Sub позволяет распространять Live Events между Backend-инстансами.
WebSocket и SSE обеспечивают обновление интерфейса без перезагрузки страницы.
Web Push позволяет уведомлять пользователя вне активной вкладки при поддержке браузера и разрешении пользователя.
Transactional Outbox помогает уменьшать риск потери событий между базой данных и очередью.
Delivery Tracking и Idempotency позволяют контролировать повторные попытки и анализировать ошибки.
Preferences, Quiet Hours и Digest помогают не перегружать пользователей сообщениями.
При этом безопасность остаётся обязательной частью архитектуры: каждое уведомление должно быть связано с правильным получателем, а в Multi-Tenant SaaS — ещё и с правильной организацией.
Главный принцип: сначала надёжно сохраните уведомление, затем доставляйте его по нужным каналам, учитывая ошибки, повторы и пользовательские настройки.
Для небольшого проекта достаточно начать с In-App Notifications и Email через Celery.
По мере роста можно добавлять WebSocket, Redis Pub/Sub, Web Push, Digest и отдельные очереди для разных каналов.
Так система уведомлений будет развиваться вместе с продуктом, оставаясь понятной, безопасной и масштабируемой.