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.
The multi-vendor nightmare
Here's a typical multi-channel stack:
| Channel | Provider | SDK | Dashboard |
|---|
| Email | 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:
User preferences: "Send me push notifications for orders but email for marketing." Where does this logic live?
Channel fallback: If push delivery fails, fall back to SMS. Who orchestrates that?
Unified subscriber profiles: A user's email, phone, device tokens, and notification preferences — stored where?
Delivery tracking: Did the user get the message on any channel? Check all four dashboards.
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.
One API, four channels
Zyphr provides a single API for all four channels. Same SDK, same authentication, same error handling, same delivery tracking.
Email
await zyphr.emails.send({
to: 'user@example.com',
template: 'order-shipped',
variables: { trackingUrl, orderNumber },
});
SMS
await zyphr.sms.send({
to: '+15551234567',
template: 'verification-code',
variables: { code: '482910' },
});
Push notification
await zyphr.push.send({
subscriberId: 'user_abc123',
title: 'Your order shipped!',
body: 'Track your package with the link below.',
data: { orderId: 'ord_789' },
});
In-app notification
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.
Subscriber profiles
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.
Topic-based subscriptions
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 },
});
Unified delivery tracking
Every message — regardless of channel — shows up in a single delivery timeline:
| Timestamp | Channel | Status | Details |
|---|
| 14:30:01 | Email | 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.
When you need this
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.
Start sending on every channel →