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.







