Реалізація системи сповіщень (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 днів