Back to Blog

Every web application needs to communicate with its users. An order is shipped. An appointment is approaching. A document needs a signature. A payment is overdue. Someone left a comment. A report is ready.

Without notifications, users have to manually check the application to see if anything happened. With well-designed notifications, the application reaches out to them at the right time, through the right channel, with the right message. We go deeper on all of this in a plain English guide to custom web applications.

We build notification systems for custom web applications, and they’re one of those features that clients initially treat as an afterthought (“oh, and it should send emails”), but quickly realize are central to how their application functions. A great notification system makes your app feel alive and responsive. A bad one fills inboxes with noise, annoys users, and gets ignored.

Here’s how to build notification systems that people actually appreciate receiving.

Multi-Channel: Why One Channel Isn’t Enough

Different messages need different channels. An appointment reminder works well as a text message. A detailed invoice is better as an email. A real-time alert belongs in a push notification.

And different users have different preferences. Some people live in their email inbox. Others never check email but respond to texts within minutes. Some love push notifications. Others have disabled them entirely.

A well-designed notification system supports multiple channels and lets users choose how they want to be reached.

Email

Best for: Detailed content, documents, receipts, reports, anything the user might want to reference later.

Advantages:

  • Universal: everyone has an email address
  • Rich formatting: HTML templates, images, attachments
  • Persistent: sits in the inbox until addressed
  • Searchable: users can find old notifications

Disadvantages:

  • Slow response times: average email open time is hours, not minutes
  • Deliverability challenges: emails can land in spam
  • Inbox fatigue: people are overwhelmed with email

SMS/Text Messages

Best for: Time-sensitive alerts, appointment reminders, verification codes, short status updates.

Advantages:

  • Near-instant delivery and read rates (95%+ open rate)
  • Works on any phone, no app required
  • Feels personal and immediate
  • High action rates for simple requests

Disadvantages:

  • Limited to 160 characters (or segmented into multiple messages)
  • Costs per message (typically $0.01-0.05 per SMS)
  • Regulatory requirements (opt-in, opt-out compliance)
  • Can feel intrusive if overused

Push Notifications (Web and Mobile)

Best for: Real-time alerts, status changes, interactive prompts when the user isn’t actively using the app.

Advantages:

  • Instant delivery
  • Highly visible on the user’s device
  • Can include action buttons
  • No per-message cost after implementation

Disadvantages:

  • Requires user permission (and many people decline)
  • Web push support varies across browsers and platforms
  • Easy to abuse: too many notifications and users revoke permission
  • Limited content: short messages only

In-App Notifications

Best for: Non-urgent updates, activity feeds, system messages visible when the user is actively using the app.

Advantages:

  • No external service dependencies
  • Fully controlled formatting and behavior
  • Can include rich content and interactive elements
  • No deliverability concerns

Disadvantages:

  • Only visible when the user is in the app
  • Not suitable for time-sensitive communications
  • Must be built into the application UI

Choosing the Right Channel for Each Notification

We use a simple framework when designing notification systems:

Notification TypePrimary ChannelSecondary Channel
Appointment reminderSMSEmail
Invoice/receiptEmailIn-app
Real-time status changePushIn-app
Document ready for signatureEmail + SMSPush
Payment overdueEmailSMS
Welcome/onboardingEmailIn-app
System maintenanceEmailIn-app
Security alert (login, password)Email + SMSPush
Daily/weekly digestEmail–
Comment or replyPushEmail

The primary channel is the default. The secondary channel is a fallback or supplement. For critical notifications (security alerts, payment issues), we often send through two channels simultaneously.

Dashboards and portals turn scattered data into one source of truth.
Dashboards and portals turn scattered data into one source of truth.

Building with Email: SendGrid and Beyond

For transactional email (messages triggered by user actions, not marketing blasts), we primarily use SendGrid, though there are several solid options.

Transactional Email Providers

SendGrid (Twilio)
The industry standard. Robust API, excellent deliverability, detailed analytics. Free tier covers 100 emails/day.
Postmark
Focused specifically on transactional email with best-in-class deliverability. Their philosophy is speed and reliability over volume.
Amazon SES
The cheapest option at scale ($0.10 per 1,000 emails). Requires more setup and monitoring but hard to beat on price.
Mailgun
Developer-friendly with good API design and solid deliverability.
Resend
A newer option built on modern principles with a clean developer experience.

Email Architecture Best Practices

Template system. Don’t construct emails in code. Use a template system where:

  • A base layout defines the header, footer, and overall structure
  • Individual notification templates extend the base with specific content
  • Dynamic variables populate personalized data
  • Templates can be updated without code changes

Responsive design. Email HTML is notoriously difficult to style. We use a tested base template (built on frameworks like MJML or Maizzle) that renders consistently across Gmail, Outlook, Apple Mail, and mobile clients.

Plain text fallback. Every HTML email should include a plain text version. Some email clients prefer it, and it helps with spam filtering.

Unsubscribe handling. Every email should include an unsubscribe mechanism. For transactional emails, this might be a preference center rather than a full unsubscribe: “stop sending me invoice reminders” rather than “stop all email.”

Delivery tracking. Track:

  • Delivery rate: did the email reach the server?
  • Bounce rate: hard bounces (invalid address) vs. soft bounces (full inbox)
  • Open rate: was the email opened? (Note: Apple Mail Privacy Protection affects this metric)
  • Click rate: did the user click any links?
  • Spam complaint rate: are users marking your emails as spam? This should be near zero for transactional email.

Building with SMS: Twilio and Compliance

SMS notifications require more care than email, both technically and legally.

SMS Providers

Twilio
The dominant platform. Comprehensive API, global coverage, excellent documentation. Pricing around $0.0079 per outbound SMS in the US.
Vonage (Nexmo)
Strong alternative with competitive pricing and good international support.
Amazon SNS
Simple and cheap for basic SMS needs, though less feature-rich than dedicated providers.
MessageBird
Popular in Europe with strong international SMS routing.

SMS Compliance Requirements

SMS is regulated, and non-compliance can result in fines and having your number blocked.

Opt-in is mandatory. You must have explicit consent before sending SMS messages. This means:

  • A clear opt-in mechanism (checkbox, text-to-subscribe, form field)
  • Documentation of when and how consent was given
  • Disclosure of what types of messages will be sent
  • Disclosure of message frequency

Opt-out must be immediate. Users must be able to text STOP at any time, and your system must immediately cease sending messages to that number.

10DLC registration. In the US, businesses sending application-to-person (A2P) SMS must register their brand and campaigns with The Campaign Registry. Unregistered traffic faces higher filtering rates and potential blocking.

Message content requirements:

  • Identify your business in every message
  • Include opt-out instructions (e.g., “Reply STOP to unsubscribe”)
  • Don’t send messages outside reasonable hours (typically 8am-9pm in the recipient’s time zone)

Implementation Patterns

A typical SMS notification implementation:

javascript
// Send an appointment reminder
async function sendAppointmentReminder(appointment) {
  const message = await twilioClient.messages.create({
    body: `Reminder: Your appointment with ${appointment.providerName} is tomorrow at ${appointment.time}. Reply C to confirm or R to reschedule.`,
    from: process.env.TWILIO_PHONE_NUMBER,
    to: appointment.customerPhone,
    statusCallback: `${process.env.APP_URL}/webhooks/sms-status`
  });

  // Log the notification
  await NotificationLog.create({
    type: 'sms',
    recipient: appointment.customerId,
    messageId: message.sid,
    status: 'sent',
    content: message.body
  });
}

The statusCallback URL receives delivery status updates from Twilio, letting you track whether each message was delivered, failed, or triggered a reply.

Building with Push Notifications

Web push notifications let your application reach users even when they’re not on your site.

How Web Push Works

  1. User grants permission. The browser shows a permission prompt. If the user allows it, you receive a subscription object.
  2. Store the subscription. Save the subscription details (endpoint URL and encryption keys) on your server.
  3. Send notifications. When an event occurs, your server sends a push message through the browser’s push service.
  4. Service worker handles receipt. A service worker running in the background receives the push message and displays a notification to the user.

Push API Implementation

javascript
// Client: Request permission and subscribe
async function subscribeToPush() {
  const permission = await Notification.requestPermission();
  if (permission !== 'granted') return;

  const registration = await navigator.serviceWorker.ready;
  const subscription = await registration.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: VAPID_PUBLIC_KEY
  });

  // Send subscription to your server
  await fetch('/api/push-subscriptions', {
    method: 'POST',
    body: JSON.stringify(subscription),
    headers: { 'Content-Type': 'application/json' }
  });
}
javascript
// Service Worker: Handle incoming push
self.addEventListener('push', (event) => {
  const data = event.data.json();
  event.waitUntil(
    self.registration.showNotification(data.title, {
      body: data.body,
      icon: '/icon-192.png',
      badge: '/badge-72.png',
      data: { url: data.url }
    })
  );
});

// Handle notification click
self.addEventListener('notificationclick', (event) => {
  event.notification.close();
  event.waitUntil(clients.openWindow(event.notification.data.url));
});

Push Notification Best Practices

  • Ask for permission at the right time. Don’t show the permission prompt immediately on page load. Wait until the user has engaged with your app and there’s a clear reason to subscribe. “Would you like to be notified when your order ships?” is better than a generic prompt on the homepage.
  • Be selective about what triggers a push. Reserve push for genuinely important, time-sensitive notifications. If you push too often, users will revoke permission.
  • Include an action URL. When the user taps the notification, take them directly to the relevant content, not the homepage.
  • Handle expired subscriptions. Push subscriptions can expire. Handle 410 (Gone) responses from the push service by removing the subscription from your database.

User Notification Preferences

A well-designed notification system puts users in control. Here’s what a notification preference center should include:

Channel Preferences

Let users choose their preferred channel for each notification category:

  • Appointment reminders: Email / SMS / Push / All / None
  • Billing notifications: Email / SMS / Push / All / None
  • Status updates: Email / SMS / Push / All / None
  • Marketing: Email / None

Frequency Controls

For non-urgent notifications, let users choose frequency:

  • Real-time: get every notification as it happens
  • Daily digest: a summary once per day
  • Weekly digest: a summary once per week

Quiet Hours

Let users set times when they don’t want to receive notifications (except for critical alerts like security events).

Global Controls

An easy “pause all notifications” toggle and a “notification level” setting (all, important only, critical only) give users a quick way to manage volume.

Delivery Tracking and Reliability

For business-critical notifications, you need to know whether they were delivered.

Notification Logging

Every notification should be logged with:

  • Timestamp: when it was sent
  • Recipient: who it was sent to
  • Channel: email, SMS, push, in-app
  • Content: what was sent (or a reference to the template and data)
  • Status: queued, sent, delivered, failed, bounced, opened, clicked
  • Error details: if it failed, why

Retry Logic

For failed notifications:

  • Transient failures (network timeout, service unavailable): retry with exponential backoff (1 min, 5 min, 30 min)
  • Permanent failures (invalid email, disconnected phone): don’t retry. Flag the contact record for review.
  • Rate limit failures: queue and retry after the rate limit window expires

Fallback Channels

For critical notifications, implement automatic fallback:

  1. Attempt primary channel (e.g., SMS)
  2. If delivery fails or is unconfirmed after a threshold (e.g., 30 minutes), send through secondary channel (e.g., email)
  3. Log both attempts

Monitoring and Alerting

Set up monitoring for:

  • Delivery success rate dropping below a threshold
  • Bounce rates increasing (might indicate a sender reputation issue)
  • Queue depth growing (might indicate a processing bottleneck)
  • Provider outages (switch to backup provider if available)

Architecture Considerations

Queue-Based Processing

Don’t send notifications synchronously in your application code. If sending an email takes two seconds and your endpoint needs to send five notifications, that’s ten seconds of blocked processing.

Instead, use a message queue:

  1. Application publishes a notification event to the queue
  2. A worker process picks up the event and handles delivery
  3. The application continues processing without waiting

Popular queuing options: Redis + Bull, RabbitMQ, Amazon SQS, or even a database-backed queue for simpler applications.

Template Management

Keep notification templates separate from application code:

  • Store templates in a database or file system
  • Support variables for personalization
  • Allow non-developers to edit template content
  • Version templates so changes can be rolled back

Rate Limiting

Protect your users and your sender reputation:

  • Maximum notifications per user per hour/day
  • Maximum total sends per minute (respect provider rate limits)
  • Duplicate suppression (don’t send the same notification twice)
  • Throttling during high-volume events

What It Costs

For a typical small business web application sending a few thousand notifications per month:

Email
Free to $20/month (SendGrid free tier covers 100/day, paid plans from $20/month)
SMS
$50-200/month depending on volume (at $0.0079/message, 5,000 messages = ~$40)
Push
Free (you host the service worker and use the Web Push API)
Development
The notification system itself is a meaningful development investment, but it’s a one-time build that scales with your application

The infrastructure costs are modest. The development cost is the main investment, and it pays off every day the system is running.

Keep Reading