Реализация системы уведомлений (Email, SMS, Push, In-App) для сайта

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация системы уведомлений (Email, SMS, Push, In-App) для сайта
Сложная
~1-2 недели
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Реализация системы уведомлений: Email, SMS, Push, In-App

Система уведомлений информирует пользователей о событиях через несколько каналов. Пользователь сам выбирает, какие уведомления и по каким каналам получать. Ключевые задачи: центральное хранилище настроек, очередь отправки, дедупликация.

Архитектура

[Event: OrderShipped]
         ↓
[NotificationService]
  ├── Проверить настройки пользователя
  ├── Email: в очередь → SendGrid/Mailgun
  ├── SMS: в очередь → SMSC/Twilio
  ├── Push: в очередь → Firebase FCM
  └── In-App: сохранить в БД → WebSocket push

Структура БД

-- Настройки уведомлений пользователя
CREATE TABLE notification_preferences (
    user_id          INTEGER  NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    notification_type VARCHAR(100) NOT NULL,  -- 'order.shipped', 'comment.reply', etc.
    email_enabled    BOOLEAN  NOT NULL DEFAULT true,
    sms_enabled      BOOLEAN  NOT NULL DEFAULT false,
    push_enabled     BOOLEAN  NOT NULL DEFAULT true,
    inapp_enabled    BOOLEAN  NOT NULL DEFAULT true,
    PRIMARY KEY (user_id, notification_type)
);

-- In-App уведомления
CREATE TABLE notifications (
    id         UUID         PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id    INTEGER      NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    type       VARCHAR(100) NOT NULL,
    title      VARCHAR(255),
    body       TEXT,
    data       JSONB        NOT NULL DEFAULT '{}',
    read_at    TIMESTAMPTZ,
    created_at TIMESTAMPTZ  NOT NULL DEFAULT NOW()
);

CREATE INDEX ON notifications(user_id, read_at, created_at DESC);

-- Push-токены
CREATE TABLE push_tokens (
    id         SERIAL PRIMARY KEY,
    user_id    INTEGER     NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    token      TEXT        NOT NULL UNIQUE,
    platform   VARCHAR(20) NOT NULL,  -- 'web', 'ios', 'android'
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

Laravel: базовая реализация

class NotificationService
{
    public function notify(User $user, string $type, array $payload): void
    {
        $prefs = NotificationPreference::where('user_id', $user->id)
            ->where('notification_type', $type)
            ->first();

        // Настройки по умолчанию
        $defaults = [
            'email_enabled' => true,
            'sms_enabled'   => false,
            'push_enabled'  => true,
            'inapp_enabled' => true,
        ];

        $channels = array_merge($defaults, $prefs?->toArray() ?? []);

        // In-App: синхронно (сохранить + WebSocket)
        if ($channels['inapp_enabled']) {
            $notification = Notification::create([
                'user_id' => $user->id,
                'type'    => $type,
                'title'   => $payload['title'] ?? null,
                'body'    => $payload['body'] ?? null,
                'data'    => $payload['data'] ?? [],
            ]);

            broadcast(new NewNotificationEvent($user, $notification))->toOthers();
        }

        // Email: в очередь
        if ($channels['email_enabled'] && isset($payload['email'])) {
            SendEmailNotificationJob::dispatch($user, $type, $payload['email'])->onQueue('notifications');
        }

        // SMS: в очередь
        if ($channels['sms_enabled'] && $user->phone && isset($payload['sms'])) {
            SendSmsNotificationJob::dispatch($user, $payload['sms'])->onQueue('notifications');
        }

        // Push: в очередь
        if ($channels['push_enabled'] && isset($payload['push'])) {
            SendPushNotificationJob::dispatch($user, $payload['push'])->onQueue('notifications');
        }
    }
}

// Пример использования
app(NotificationService::class)->notify($user, 'order.shipped', [
    'title' => 'Ваш заказ отправлен',
    'body'  => "Заказ #{$order->number} передан в службу доставки",
    'data'  => ['order_id' => $order->id, 'url' => route('orders.show', $order)],
    'email' => ['order' => $order],   // данные для email шаблона
    'sms'   => "Заказ #{$order->number} отправлен. Трек: {$order->tracking_number}",
    'push'  => ['title' => 'Заказ отправлен', 'body' => "Заказ #{$order->number}"],
]);

Email: SendGrid

class SendEmailNotificationJob implements ShouldQueue
{
    public int $tries = 3;
    public int $backoff = 60;

    public function __construct(
        private User $user,
        private string $type,
        private array $emailData,
    ) {}

    public function handle(): void
    {
        $mailable = $this->resolveMailable($this->type, $this->emailData);
        Mail::to($this->user->email)->send($mailable);
    }

    private function resolveMailable(string $type, array $data): Mailable
    {
        return match ($type) {
            'order.shipped'   => new OrderShippedMail($data['order']),
            'comment.reply'   => new CommentReplyMail($data['comment']),
            'password.reset'  => new PasswordResetMail($data['token']),
            default           => new GenericNotificationMail($type, $data),
        };
    }
}

SMS: SMSC.ru (Россия)

class SendSmsNotificationJob implements ShouldQueue
{
    public function __construct(private User $user, private string $text) {}

    public function handle(): void
    {
        Http::get('https://smsc.ru/sys/send.php', [
            'login'   => config('services.smsc.login'),
            'psw'     => config('services.smsc.password'),
            'phones'  => $this->user->phone,
            'mes'     => $this->text,
            'charset' => 'utf-8',
            'fmt'     => 3,  // JSON
        ])->throw();
    }
}

Push: Firebase Cloud Messaging

class SendPushNotificationJob implements ShouldQueue
{
    public function __construct(private User $user, private array $pushData) {}

    public function handle(): void
    {
        $tokens = PushToken::where('user_id', $this->user->id)->pluck('token')->toArray();

        if (empty($tokens)) return;

        $response = Http::withToken(config('services.firebase.server_key'))
            ->post('https://fcm.googleapis.com/fcm/send', [
                'registration_ids' => $tokens,
                'notification' => [
                    'title' => $this->pushData['title'],
                    'body'  => $this->pushData['body'],
                    'icon'  => '/icon-192.png',
                    'click_action' => $this->pushData['url'] ?? '/',
                ],
                'data' => $this->pushData['data'] ?? [],
            ]);

        // Удалить невалидные токены
        $results = $response->json('results', []);
        foreach ($results as $index => $result) {
            if (isset($result['error']) && in_array($result['error'], ['InvalidRegistration', 'NotRegistered'])) {
                PushToken::where('token', $tokens[$index])->delete();
            }
        }
    }
}

Web Push: Service Worker подписка

// Подписка на Web Push
async function subscribeToPush(): Promise<void> {
  const registration = await navigator.serviceWorker.ready;

  const subscription = await registration.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: urlBase64ToUint8Array(import.meta.env.VITE_VAPID_PUBLIC_KEY),
  });

  await api.post('/api/push-tokens', {
    token: JSON.stringify(subscription),
    platform: 'web',
  });
}

React: центр уведомлений

function NotificationCenter() {
  const { data, refetch } = useQuery({ queryKey: ['notifications'], queryFn: fetchNotifications });
  const unread = data?.filter(n => !n.read_at).length ?? 0;

  // Обновление в реальном времени через WebSocket
  useEffect(() => {
    const echo = window.Echo.private(`notifications.${currentUser.id}`)
      .listen('NewNotificationEvent', () => refetch());
    return () => echo.stopListening('NewNotificationEvent');
  }, []);

  return (
    <div className="notification-center">
      <button className="bell" aria-label={`Уведомления: ${unread} непрочитанных`}>
        <BellIcon />
        {unread > 0 && <span className="badge">{unread > 99 ? '99+' : unread}</span>}
      </button>

      <ul className="notification-list">
        {data?.map(notification => (
          <li key={notification.id} className={notification.read_at ? '' : 'unread'}>
            <span>{notification.title}</span>
            <time>{timeAgo(notification.created_at)}</time>
          </li>
        ))}
      </ul>
    </div>
  );
}

Срок реализации

Задача Срок
In-App уведомления + WebSocket 2–3 дня
Email канал с шаблонами +1–2 дня
SMS через SMSC/Twilio +1 день
Firebase Push уведомления +1–2 дня
Настройки уведомлений пользователем +1–2 дня
Полная система все каналы + UI 7–10 дней