Уявіть: клієнт оформив замовлення на 1 млн грн, але сповіщення не дійшло — менеджер втратив угоду через завислий RabbitMQ. Ми будуємо мультиканальну систему сповіщень, яка витримає пікове навантаження в 10 000 повідомлень на хвилину та забезпечить доставку 99.9% повідомлень. Користувач сам обирає канали: Email, SMS, Push, In-App. Ключові завдання — централізоване сховище налаштувань, черга відправлення, дедуплікація та повтор при збоях. За 5 років ми впровадили 50+ таких систем для великих інтернет-магазинів та SaaS-платформ.
Проблеми, які вирішуємо
Без черги сповіщення губляться при піковому навантаженні: база не справляється з тисячами вставок, зовнішні API повертають 503 помилки. Невалідні push-токени накопичуються — кожне сповіщення на такий токен витрачає бюджет. Користувачі скаржаться на спам, якщо налаштування не дозволяють вимкнути канал. Наша архітектура вирішує все це.
Архітектура черги: як гарантувати доставку?
Система будується навколо черги (Redis або RabbitMQ). Кожне сповіщення — задача в черзі. Якщо зовнішній сервіс тимчасово недоступний, задача повторюється з експоненційною затримкою. Після трьох невдач — в Dead Letter Queue для ручного розбору.
[Event: OrderShipped]
↓
[NotificationService]
├── Перевірити налаштування користувача
├── Email: в чергу → SendGrid/Mailgun
├── SMS: в чергу → SMSC/Twilio
├── Push: в чергу → Firebase FCM
└── In-App: зберегти в БД → WebSocket push
Порівняння каналів: Email, SMS, Push, In-App
| Канал | Час доставки | Надійність | Вимагає дозволу | Найкраще для |
|---|---|---|---|---|
| Хвилини | 95–99% | Ні | Довгі детальні повідомлення | |
| SMS | Секунди | 99% | Так | Критичні оповіщення (2FA) |
| Push | Секунди | 90–95% | Так | Швидкі нагадування |
| In-App | Миттєво | 100% (всередині) | Ні | Сповіщення всередині додатку |
Push-сповіщення доставляються за секунди, тоді як Email може затримуватися на хвилини через черги провайдера. SMS — найшвидший, але дорогий канал.
Реалізація на 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() ?? []);
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();
}
if ($channels['email_enabled'] && isset($payload['email'])) {
SendEmailNotificationJob::dispatch($user, $type, $payload['email'])->onQueue('notifications');
}
if ($channels['sms_enabled'] && $user->phone && isset($payload['sms'])) {
SendSmsNotificationJob::dispatch($user, $payload['sms'])->onQueue('notifications');
}
if ($channels['push_enabled'] && isset($payload['push'])) {
SendPushNotificationJob::dispatch($user, $payload['push'])->onQueue('notifications');
}
}
}
Налаштування провайдерів
Email: шаблони та провайдери
Для Email використовуємо SendGrid (або Mailgun) з готовими шаблонами в Blade. Кожен тип сповіщення — окремий mailable клас. Важливо налаштувати відстеження відкриттів та кліків.
SMS: SMSC.ru та Twilio
Для Росії — SMSC.ru, HTTP-запит з логіном та паролем. Для міжнародних — Twilio. Обидва обгорнуті в чергу з повторами при 500-х помилках.
Push: Firebase FCM та Web Push
Firebase Cloud Messaging відправляє сповіщення на Android, iOS та Web (через Service Worker). Ми видаляємо невалідні токени з бази після відповіді FCM. Для Web Push використовуються VAPID-ключі.
Реалізація клієнтської сторони: Web Push та центр сповіщень
Web Push: Service Worker підписка
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;
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>
);
}
Чому N+1 запит небезпечний в системі сповіщень?
При отриманні списку сповіщень не робіть запит по кожному користувачу окремо — використовуйте eager loading. Ми перевіряємо всі запити через Laravel Debugbar та оптимізуємо індекси. Це скорочує час відповіді API з 2 секунд до 50 мс.
Процес впровадження та терміни
| Етап | Що робимо | Термін |
|---|---|---|
| Аналітика | Описуємо типи сповіщень, обираємо провайдерів, проектуємо схему БД | 1–2 дні |
| Проектування | Налаштовуємо чергу, пишемо сервіс сповіщень, UI для налаштувань | 2–3 дні |
| Реалізація Email та SMS | Інтеграція SendGrid/Mailgun та SMSC/Twilio, шаблони, обробка помилок | +2 дні |
| Push (FCM + Web) | Service Worker, VAPID, обробка невалідних токенів | +2 дні |
| In-App + WebSocket | Збереження в БД, real-time push через Laravel Echo | +2 дні |
| Тестування та деплой | Навантажувальне тестування, моніторинг, документація | +1–2 дні |
| Повна система | Всі канали, UI, черги, повтори | 7–10 днів |
Типові помилки та як їх уникнути
- Неправильний порядок черг: Email та Push на одному воркері — при великому обсязі Email блокує Push. Рішення: виділені черги. - Відсутність дедуплікації: при повторному кліку на «Відправити» користувач отримує дублі. Рішення: перевіряти за хешем події. - Ігнорування rate limit провайдера: FCM лімітує 600 000 запитів/сек, але для SMS ліміт нижчий. Налаштовуємо семафор.Як уникнути дублювання сповіщень?
Використовуємо унікальний ідентифікатор події (UUID). Перед відправленням перевіряємо, чи вже оброблялася така подія. Якщо так — пропускаємо.
Зв'яжіться з нами для оцінки проєкту — отримайте консультацію за 2 дні. Ми гарантуємо SLA 99.9% та надаємо сертифікованих спеціалістів.







