Every business runs on conversations. A receipt after a purchase. A code to log in. A reminder before an appointment. A call when something urgent cannot wait. The channel changes, but the goal never does. Reach the customer, and be understood.
For a long time, Zend did that over SMS and WhatsApp. Over the past few months we have gone a lot further. Zend can now send email from your own domain, place voice calls, and tie every channel together under one API and one bill. Here is what we built, and more importantly, why.
Email that actually comes from you
Email is the channel of record. It is where people expect anything official to live: receipts, invoices, password resets, account updates, order confirmations. It is the message customers save, search for, and come back to weeks later. If SMS is a tap on the shoulder, email is the paper trail.
So why build our own email? Because the alternatives let our customers down. You could bolt on a separate email provider, which means another vendor, another integration, another invoice, and another dashboard to watch. Or you could send from a generic shared address, the kind that drops into spam and makes people wonder whether the message is even real.
Neither is good enough for a business that wants to be trusted. So we built email into Zend properly. You send from your own domain, authenticated and signed, so your mail arrives looking like it came from you, because it did. We carry the deliverability work in the background: authentication, reputation monitoring, and automatic handling of addresses that bounce or complain, so a single bad address never drags down the rest of your mail. And all of it lives in the same place as your SMS and WhatsApp, on the same balance, through the same API.
The promise is easy to say and genuinely hard to get right. Your email reaches the inbox, and it looks like you.
And sending it is a single call:
import { Zend } from '@usezend/node';
const zend = new Zend(process.env.ZEND_API_KEY);
await zend.emails.send({
from: 'receipts@yourbrand.com',
to: 'customer@example.com',
subject: 'Your receipt',
html: '<p>Thanks for your order. Your receipt is below.</p>',
});
Send the document, not a link
Then we made email do more. A lot of business mail is really about a file. The invoice. The receipt. The ticket. The statement. Until now, sending one meant hosting it somewhere and pasting a link, which is an extra step for you and a worse experience for the customer, who has to click through, maybe sign in, and hope the link still works.
Now you just attach the file. The invoice arrives as an invoice. It sounds obvious, and that is exactly the point. Less friction for your customer, fewer support tickets for you, and a more professional impression every single time you hit send. Whatever you need to send along, a PDF, an image, a spreadsheet, it travels with the message.
The content is a Buffer or a base64 string, and you can send up to ten files, seven megabytes in total:
import { readFileSync } from 'node:fs';
await zend.emails.send({
from: 'billing@yourbrand.com',
to: 'customer@example.com',
subject: 'Invoice #1024',
html: '<p>Thanks for your order. Your invoice is attached.</p>',
attachments: [
{
filename: 'invoice-1024.pdf',
content: readFileSync('./invoice-1024.pdf'),
contentType: 'application/pdf',
},
],
});
Know what happens after you hit send
Sending an email and hoping is not a strategy. Did they open it? Did they click? Which subject line worked, which campaign fell flat, who is engaged and who has quietly gone cold? Without answers, every send is a guess.
So we added open and click tracking. Now you can see who opened your mail and who followed your links, which turns email from a shot in the dark into something you can measure and steadily improve. Send a campaign, watch the open rate, follow up with the people who did not open, and put more into what clearly works.
We were deliberate about one thing. Tracking is off by default, and you switch it on per domain, only where it makes sense. Marketing and onboarding mail, yes. Password resets and one-time codes, no. Your customers' privacy is not something to enable by accident, so we made it a conscious choice rather than the default.
The first time someone opens a message, Zend notifies your app so it can react right away, for example marking a lead as engaged or scheduling a follow-up:
{
"event": "message.read",
"channel": "email",
"message_id": "6a60c00a4ede20ab509eac0c",
"to": "customer@example.com",
"read_at": "2026-07-22T13:05:36.133Z"
}
Voice, because not every message is meant to be read
Some messages need to be heard, not read. That is especially true here in Ghana, where a great many people are reached far better by a phone call than by a screen. Not everyone carries a smartphone. Not everyone has data switched on. Not everyone reads comfortably in the language an app defaults to. But almost everyone answers a ringing phone.
That is why we built voice. With Zend you can place a call that reads your message aloud in a natural voice, or that plays a recording you provide. It is made for the moments that matter most: a verification code for someone on a basic handset, an urgent alert that should not sit unread in an inbox, an appointment reminder, a delivery on its way. Codes are read out digit by digit so they are easy to catch, and if a call goes unanswered, it can fall back to an SMS on its own, so a missed call is never a missed message.
A call is one request, with an automatic SMS fallback for anyone who does not pick up:
await zend.voice.send({
recipients: ['+233201234567'],
text: 'Hello from Zend. Your verification code is 4 8 2 1.',
voice: 'female',
retry: true,
fallback: { sms: true },
});
Voice closes the last gap in reach. Between SMS, WhatsApp, email, and now voice, there is no customer you cannot get a message to, in the form most likely to land.
One platform, a lot less to manage
Each of these channels could be its own vendor, its own bill, and its own thing to learn. That is the tax most businesses quietly pay to reach their customers, and it adds up in money, in time, and in complexity.
Zend takes that tax away. Email, SMS, WhatsApp, and voice all run on one platform, one balance, and one API. For your developers, that means a single, well-typed client they integrate once and reuse everywhere, so they spend their time building your product instead of stitching together other people's APIs. The same client reaches every channel, in the same shape:
import { Zend } from '@usezend/node';
const zend = new Zend(process.env.ZEND_API_KEY);
// Email
await zend.emails.send({
from: 'receipts@yourbrand.com',
to: 'customer@example.com',
subject: 'Your receipt',
html: '<p>Thanks for your order.</p>',
});
// SMS or WhatsApp
await zend.messages.send({
to: '+233201234567',
body: 'Thanks for your order.',
preferredChannels: ['sms'],
});
// Voice
await zend.voice.send({
recipients: ['+233201234567'],
text: 'Thanks for your order.',
});
One method per channel, all the same shape. One client to learn, and every channel is covered.
The Node SDK, built to get out of your way
Reaching your customers should be your engineers' easiest problem, not a week of reading API docs. So the official client, @usezend/node, is designed to disappear into your code.
It never throws for API or network errors. Every call returns a plain result you check, so one failed send cannot crash a request handler you forgot to wrap:
const { data, error } = await zend.emails.send({
from: 'you@yourdomain.com',
to: 'customer@example.com',
subject: 'Welcome aboard',
html: '<p>Glad to have you.</p>',
});
if (error) {
console.error('Send failed:', error.message);
} else {
console.log('Sent', data.id);
}
It is fully typed, so your editor autocompletes every field and catches mistakes before you run anything. It has no runtime dependencies, so it adds nothing to your bundle. And it is a single install, npm i @usezend/node, for SMS, WhatsApp, email, and voice together. Less code standing between an idea and a message going out.
See it working, and trust that it is up
When more of your business runs through one platform, you need to trust that platform and see what it is doing. So alongside the channels, we built the visibility to match. You get real-time delivery numbers across every channel, so you can watch mail land and calls connect as they happen. You can register a webhook once and have Zend tell your systems the moment anything changes, whether a message was delivered, a call was answered, or an email was opened, so your app reacts in real time instead of polling and waiting. And a public status page means you never have to guess whether the problem is on your side or ours.
Register an endpoint once, and from then on every status change arrives at your server, signed and retried, in one consistent shape:
app.post('/webhooks/zend', (req, res) => {
const { event, message_id, to } = req.body;
if (event === 'message.delivered') markDelivered(message_id);
if (event === 'message.read') markEngaged(to); // an email was opened
res.sendStatus(200);
});
Where this is going
One idea runs through all of it. A business should be able to reach any customer, on any channel, from one place, without paying a complexity tax to do it. Email gave you the channel of record. Attachments let it carry real documents. Tracking made it measurable. Voice made sure no one is out of reach. The SDK put every channel behind one typed client. And the platform underneath makes the whole thing something you can lean on as you grow.
We are only getting started. Add a domain and send your first email, or place a test call, and watch it land.
