Все статьи

Как спроектировать систему уведомлений: Email, Web Push и Real-Time Notifications

Полное руководство для Full Stack разработчика: React, FastAPI, WebSocket, Redis Pub/Sub, Celery, SMTP, Web Push API и архитектура уведомлений в Production

26 минут чтения

Представим, что вы разрабатываете 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

|

+--> Email

|

+--> Web Push

Одна бизнес-операция может запускать несколько каналов доставки.

При этом не каждое событие должно отправляться во все каналы.

Например, комментарий к задаче можно показать внутри приложения, а критическое предупреждение о безопасности — дополнительно отправить по Email.

2. Какие типы уведомлений существуют

Типы уведомлений в SaaS:

Тип

Пример

Основная задача

Transactional

Подтверждение Email

Подтверждение операции

Security

Новый вход в аккаунт

Безопасность

Collaboration

Новый комментарий

Совместная работа

Billing

Ошибка оплаты

Управление подпиской

System

Завершение отчёта

Информирование

Marketing

Новая функция

Вовлечение пользователей

Разные категории требуют разных правил доставки.

Например, пользователь может отключить маркетинговые письма, но важные сообщения о безопасности обычно обрабатываются отдельно.

3. Чем отличаются Email, In-App и Push

Email vs Push vs In-App Notifications:

Канал

Преимущества

Ограничения

Email

Работает вне приложения, удобен для подробных сообщений

Задержки, Spam Filters

In-App

История уведомлений, удобная навигация

Требует открытия приложения

Web Push

Может уведомлять вне открытой вкладки

Разрешение браузера, поддержка платформы

WebSocket

Быстрая доставка в активное приложение

Требует активного соединения

SSE

Простой поток событий от сервера к браузеру

Односторонняя передача

На практике часто используют несколько каналов одновременно.


4. Как выглядит архитектура уведомлений

Notification Service Architecture:

Business Event

|

v

Notification Service

|

v

PostgreSQL

|

v

Task Queue

|

+----------+----------+

| | |

v v v

Email Web Push Real-Time

Worker Worker Gateway

| | |

v v v

SMTP/API Push Service WebSocket

| | |

+----------+----------+

|

v

User

Здесь важно разделить три процесса:

  1. Возникновение бизнес-события.

  2. Создание уведомления.

  3. Доставка уведомления по каналам.

Это позволяет повторять доставку, не повторяя исходную бизнес-операцию.

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

Email

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 и отдельные очереди для разных каналов.

Так система уведомлений будет развиваться вместе с продуктом, оставаясь понятной, безопасной и масштабируемой.


Наверх