Loading...
Loading...
Bi-weekly engineering deep dives on auth, notifications, and developer infrastructure. No spam.
Sign Up for UpdatesPublished by Zyphr
10 min read
Read more
5 min read
Read more
7 min read
Read more
Your users don't live in their inbox. Some check email religiously. Some ignore it entirely but respond to SMS within seconds. Some have push notifications enabled. Some prefer to check an in-app notification center when they're already using your product.
If you want to reliably reach users, you need multiple channels. The problem is that "multiple channels" usually means multiple providers, multiple SDKs, and multiple integration headaches.
Here's a typical multi-channel stack:
| Channel | Provider | SDK | Dashboard |
|---|---|---|---|
| SendGrid | @sendgrid/mail | sendgrid.com | |
| SMS | Twilio | twilio | twilio.com |
| Push | Firebase | firebase-admin | console.firebase.google.com |
| In-App | Custom-built | N/A | Your own admin panel |
Four providers. Four authentication mechanisms. Four error handling patterns. Four sets of delivery logs. And when a user says "I didn't get notified," you're hunting across four dashboards to figure out what happened.
It gets worse when you add features that span channels:
Most teams solve this by building an internal notification service — a queue-backed system that routes messages to the right provider based on channel, preferences, and fallback rules. This works, but now you're maintaining a critical piece of infrastructure that isn't your core product.
Zyphr provides a single API for all four channels. Same SDK, same authentication, same error handling, same delivery tracking.
await zyphr.emails.send({
to: 'user@example.com',
template: 'order-shipped',
variables: { trackingUrl, orderNumber },
});
await zyphr.sms.send({
to: '+15551234567',
template: 'verification-code',
variables: { code: '482910' },
});
await zyphr.push.send({
subscriberId: 'user_abc123',
title: 'Your order shipped!',
body: 'Track your package with the link below.',
data: { orderId: 'ord_789' },
});
await zyphr.inApp.send({
subscriberId: 'user_abc123',
template: 'order-update',
variables: { orderNumber, status: 'shipped' },
});
Same SDK. Same pattern. Same error codes. Same delivery tracking. The only thing that changes is the channel.
Every user in Zyphr has a unified subscriber profile that holds all their contact information and preferences:
await zyphr.subscribers.create({
externalId: 'user_abc123',
email: 'jane@example.com',
phone: '+15551234567',
preferences: {
email: { enabled: true },
sms: { enabled: true, topics: ['security'] },
push: { enabled: true },
inApp: { enabled: true },
},
});
When you send a notification, Zyphr checks the subscriber's preferences and only delivers through the channels they've opted into. No preference-checking logic in your code.
Not every notification is equal. A user might want push notifications for security alerts but email for weekly digests. Zyphr handles this with topics:
// Subscribe a user to a topic with channel preferences
await zyphr.topics.subscribe('security-alerts', {
subscriberId: 'user_abc123',
channels: ['push', 'sms'],
});
// Send to the topic — only reaches users subscribed with matching channels
await zyphr.emails.send({
topic: 'security-alerts',
template: 'suspicious-login',
variables: { ipAddress, location },
});
Every message — regardless of channel — shows up in a single delivery timeline:
| Timestamp | Channel | Status | Details |
|---|---|---|---|
| 14:30:01 | Delivered | Opened at 14:35 | |
| 14:30:02 | Push | Delivered | Tapped at 14:31 |
| 14:30:03 | In-App | Delivered | Read at 14:45 |
| 14:30:04 | SMS | Delivered | — |
One dashboard. One API to query delivery status. One place to debug when something goes wrong.
If your app is email-only and always will be, a single email provider is fine. But the moment you need a second channel — SMS for OTPs, push for real-time alerts, in-app for non-urgent updates — the complexity of managing multiple providers grows fast.
Zyphr lets you start with one channel and add more without changing your infrastructure. Same SDK, same patterns, same dashboard.