Introduction
Imagine a customer placing an order on your site, paying by card, but not receiving a status update. They start worrying, call support — your business loses money and reputation. How do you build a fault-tolerant notification system that delivers messages via email, SMS, or Telegram regardless of channel failures? We break down the architecture, code, and infrastructure tested on over 50 projects in e-commerce, fintech, and SaaS. We guarantee that no event is lost — our system uses a queue with exponential backoff and monitoring.
In over 5 years, we have never encountered a lost notification due to an architectural mistake. All issues were resolved during the design phase.
Problems We Solve
- Message loss — a channel goes down or the API returns an error (e.g., Twilio returns 500). We use a queue (Bull Queue) with retry and logging. On failure, the message is retried after 2, 4, 8 seconds (exponential backoff). After 3 failures, it goes into a Dead Letter Queue for manual analysis.
- Channel confusion — a user wants Telegram notifications but gets SMS. We implement a personal cabinet with preference settings: channel selection for each event type, quiet hours, and time zone.
- Spam traps — transactional and marketing notifications are separated. Marketing uses separate channels (email) with an unsubscribe option. Quiet hours (22:00–08:00) block sending during off-hours.
Expand sample code for Notification Service
// notification.service.ts interface NotificationRequest { userId: string; type: NotificationType; data: Record<string, unknown>; channels?: Channel[]; priority?: 'high' | 'normal' | 'low'; } type NotificationType = | 'order.placed' | 'order.shipped' | 'payment.failed' | 'password.reset' | 'promo.discount'; class NotificationService { async send(request: NotificationRequest): Promise<void> { const prefs = await this.userPrefsRepo.findByUserId(request.userId); const channels = request.channels ?? this.resolveChannels(request.type, prefs); await Promise.allSettled( channels.map(channel => this.sendViaChannel(channel, request, prefs)) ); } private resolveChannels(type: NotificationType, prefs: UserPrefs): Channel[] { const channelMap: Record<NotificationType, Channel[]> = { 'order.placed': ['email', 'telegram'], 'order.shipped': ['email', 'sms', 'telegram'], 'payment.failed': ['email', 'sms'], 'password.reset': ['email'], 'promo.discount': prefs.marketingChannels }; return channelMap[type] ?? ['email']; } } Email via Resend
For email notifications, we use Resend. Below is a code example:
import { Resend } from 'resend'; const resend = new Resend(process.env.RESEND_API_KEY); async function sendEmailNotification( user: User, type: NotificationType, data: Record<string, unknown> ) { const template = emailTemplates[type]; await resend.emails.send({ from: '[email protected]', to: user.email, subject: template.subject(data), react: template.component({ user, ...data }) }); } // Template for order.shipped const orderShippedTemplate = { subject: (data) => `Your order #${data.orderId} has shipped`, component: ({ user, orderId, trackingNumber, estimatedDelivery }) => ( <OrderShippedEmail name={user.firstName} orderId={orderId} trackingNumber={trackingNumber} trackingUrl={`https://example.com/track/${trackingNumber}`} estimatedDelivery={estimatedDelivery} /> ) }; SMS via Twilio
For SMS notifications, we use Twilio. Example:
import twilio from 'twilio'; const client = twilio(process.env.TWILIO_SID, process.env.TWILIO_TOKEN); async function sendSmsNotification( user: User, type: NotificationType, data: Record<string, unknown> ) { if (!user.phone || !user.phoneVerified) return; const templates: Record<NotificationType, (data: Record<string, unknown>) => string> = { 'order.shipped': (d) => `Order #${d.orderId} shipped. Track: ${d.trackingNumber}. Expected by ${d.date}`, 'payment.failed': (d) => `Payment for order #${d.orderId} failed. Update your card: ${d.retryUrl}` }; const text = templates[type]?.(data); if (!text) return; await client.messages.create({ to: user.phone, from: process.env.TWILIO_PHONE, body: text }); } Telegram via Bot API
For Telegram notifications, we use a Telegram bot. Example:
import TelegramBot from 'node-telegram-bot-api'; const bot = new TelegramBot(process.env.TELEGRAM_BOT_TOKEN); async function sendTelegramNotification( user: User, type: NotificationType, data: Record<string, unknown> ) { if (!user.telegramChatId) return; const messages: Record<string, (d: Record<string, unknown>) => string> = { 'order.shipped': (d) => `📦 *Order #${d.orderId} shipped*\n\nTracking: \`${d.trackingNumber}\`\nExpected delivery: ${d.date}`, 'payment.failed': (d) => `⚠️ *Payment failed*\n\nOrder #${d.orderId} not paid. [Retry payment](${d.retryUrl})` }; const text = messages[type]?.(data); if (!text) return; await bot.sendMessage(user.telegramChatId, text, { parse_mode: 'Markdown', disable_web_page_preview: true }); } // User linking Telegram account bot.onText(/\/start (.+)/, async (msg, match) => { const linkToken = match[1]; const userId = await verifyLinkToken(linkToken); if (userId) { await userRepo.updateTelegramChatId(userId, msg.chat.id.toString()); bot.sendMessage(msg.chat.id, '✅ Telegram connected successfully! You will receive notifications.'); } }); Preference Management
// Table user_notification_prefs interface UserNotificationPrefs { userId: string; emailEnabled: boolean; smsEnabled: boolean; telegramEnabled: boolean; marketingEmailEnabled: boolean; marketingSmsEnabled: boolean; quietHoursStart: string; quietHoursEnd: string; timezone: string; } How to Prevent Notification Loss?
A notification queue with retry is the key element. On send failure (e.g., Twilio returns 500), the message retries after 2, 4, 8 seconds. After 3 failures, it goes into the DLQ (dead letter queue) for manual analysis. Delivery reliability without a queue is about 95%; with a queue, it's 99.9%. That's 50 times fewer lost messages. For example, with 10,000 notifications per day, without a queue you lose 500; with a queue, only 10.
Why Do You Need a Queue with Retry?
Services are unstable: Resend may delay emails, Telegram has rate limits. A queue with backoff guarantees the message will be delivered without a crunch. We use Bull Queue with Redis. Additionally, we set up monitoring: if the error count exceeds a threshold, we send an alert to the team in Telegram.
Channel Comparison
| Channel | Delivery Speed | Reliability | Ideal For | Cost per Unit |
|---|---|---|---|---|
| minutes | high | marketing, transactional | $0.001 per email (Resend) | |
| SMS | seconds | high | urgent events (payment) | $0.05 per message (Twilio) |
| Telegram | instant | medium | push notifications | free |
SMS is delivered 60 times faster than email, which is critical for payment notifications. Although the per-unit cost of SMS is higher, it's justified for urgent events.
How to Set Up Notifications: Step-by-Step Guide for Notification Setup
- Define triggers. Which events in your app require notifications? Typical set: order placed, payment failed, password reset.
- Choose channels. For each trigger, determine the preferred channel. Duplicate critical events to two channels (email + SMS).
- Set up the notification queue. Integrate Bull Queue with Redis to process messages with retry and logging.
- Create templates. Develop HTML templates for email, text templates for SMS and Telegram with personalization.
- Test scenarios. Test each channel failure, rate limit exceeding, and correct error handling.
Process
| Stage | Duration | Result |
|---|---|---|
| Analysis | 1–2 days | Flow diagram and triggers |
| Design | 2–3 days | Architecture and channel selection |
| Implementation | 5–7 days | Integration and code |
| Testing | 2 days | Test report |
| Deployment | 1 day | Working system |
What's Included
- Notification architecture design
- Integration for email (Resend/SendGrid), SMS (Twilio), and Telegram (Bot API)
- Queue and retry configuration
- Personal cabinet for preference management
- Scenario testing (load, channel failures)
- Documentation and team training
Timeline
Basic configuration (3 channels, 5 triggers, notification queue) — from 1 to 2 weeks. The timeline depends on business logic complexity. Contact us — we'll assess your project in 1 day. Get a consultation — we'll select the optimal channels for your business and traffic.
Notification Costs
We use providers with low rates: Resend from $0.001 per email, Twilio from $0.05 per SMS. Telegram is free. You only pay for actual usage, with no hidden fees. Optimizing channel selection can save up to 40% of your notification budget. For example, sending 10,000 transactional emails costs $10, while 10,000 SMS costs $500; switching to Telegram for non-urgent notifications can save thousands annually.







