WhollySoftware
Back to blogBackend

Building a Notification Service: Email, SMS, and Push in One Pipeline

Wholly Software TeamDecember 15, 20257 min read
Building a Notification Service: Email, SMS, and Push in One Pipeline

A client's application had grown three separate, ad hoc notification implementations — email sent directly from controllers, SMS bolted on later through a different service class, push notifications added by a different engineer with yet another pattern. None of them retried failures consistently, and there was no single place to see whether a given user had actually been notified about a given event.

We consolidated all three behind a single notification service with a common interface: callers submit an event with a template name, recipient, and payload, and the service resolves which channels apply based on the user's preferences and the notification type's configured defaults. The channel-specific logic — talking to SendGrid for email, Twilio for SMS, Firebase Cloud Messaging for push — lives entirely behind that interface, so a caller creating an 'order shipped' notification doesn't need to know or care which providers are involved.

Each channel got its own retry and backoff policy because their failure modes are genuinely different. SMS failures from Twilio are often permanent (bad number, carrier filtering) and rarely worth retrying more than once. Push notification failures are frequently transient (expired device token, temporary FCM issue) and worth a few retries. Email sits in between. Treating all three with the same generic retry policy, which is what the original ad hoc implementations effectively did, wasted retries on SMS and gave up too early on push.

We added a notification log table that records every attempt, its channel, its outcome, and links back to the triggering event, which turned 'did this customer get notified about their refund' from a support engineer grepping logs into a single query. That table also let us build a simple internal dashboard showing delivery rates per channel, which surfaced a real problem: a specific email domain was silently rejecting our messages due to a DMARC misconfiguration on our sending domain that had gone unnoticed for weeks.

User notification preferences turned out to need more nuance than a simple on/off toggle per channel. We ended up modeling preferences per notification category (marketing, transactional, security) crossed with channel, since users reasonably wanted SMS for security alerts but not for marketing, while being fine with email for both. That two-dimensional preference model added complexity to the schema but matched what users actually expected when they went looking for notification settings.

NotificationsBackendSystem DesignReliability
Building a Notification Service: Email, SMS, and Push in One Pipeline — Wholly Software