Zindua
Start Free

One API.
Email + WhatsApp.

A single route: POST /v1/send. Set channel to email or whatsapp. Same templates, same API key, same logs.

5 min
Connect channels
Email service + WhatsApp QR
2 min
Create templates
One slug, two channels
10s
Send from code
channel: email | whatsapp
Single route

POST /v1/send

All messages use this endpoint. Change channel per request without changing URL or API key.

POSThttps://api.zindua.dev/v1/v1/send

Authorization: Bearer znd_live_xxx

FieldRequiredDescription
toYesRecipient. Email address if channel is email (default). E.164 phone if channel is whatsapp (e.g. 243812345678).
templateYesTemplate slug from your dashboard (same slug for email and WhatsApp).
channelNo"email" (default) or "whatsapp". One route, switch channel per request.
langNoISO 639-1 code (fr, en, sw…). Falls back to project default if missing.
variablesNoKey/value map for {{placeholders}} in the template.
cc, bcc, replyTo, attachmentsNoEmail only. Ignored when channel is whatsapp.
Email body
body-email.json
{
  "to": "user@example.com",
  "channel": "email",
  "template": "welcome",
  "variables": { "name": "Alex" }
}
WhatsApp body
body-whatsapp.json
{
  "to": "243812345678",
  "channel": "whatsapp",
  "template": "otp-verification",
  "variables": { "code": "4592" }
}
Response (202)
response.json
{
  "success": true,
  "status": "queued",
  "logId": "uuid",
  "channel": "whatsapp",
  "langUsed": "fr",
  "langFallback": false,
  "testMode": false
}
curl-send.sh
curl -X POST https://api.zindua.dev/v1/v1/send \
  -H "Authorization: Bearer $ZINDUA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "243812345678",
    "channel": "whatsapp",
    "template": "otp-verification",
    "variables": { "code": "4592" }
  }'

How Zindua Works

Four steps from dashboard to delivery on email or WhatsApp.

01

Connect channels

Email: Dashboard → Services (Gmail, Outlook, SMTP). WhatsApp: scan QR under Dashboard → WhatsApp. Messages go out from your accounts.

02

Create templates

One slug per template. Email body is HTML; WhatsApp body is short text. Use {{variables}} on both channels.

03

Send from code

POST /v1/send with channel email or whatsapp. Same API key, same logs. Free plan starts with WhatsApp OTP only.

04

Track delivery

View logs in the dashboard. Webhooks for email events. WhatsApp delivery status appears in logs.

< 5 min

Quickstart

Scroll through each framework or click one in the top bar to jump directly.

Next.js

Full integration guide

1Install
terminal
npm install @zindua/sdk
2Set API Key
.env
# .env.local
ZINDUA_API_KEY=znd_live_xxxxxxxxxxxxxxxxxxxx
3Initialize
route.ts
// lib/zindua.ts
import { Zindua } from '@zindua/sdk';

export const zindua = new Zindua({
  apiKey: process.env.ZINDUA_API_KEY!,
});
4Send email
send-email-route.ts
// Email (default channel)
await zindua.send({
  to: 'user@example.com',
  template: 'welcome',
  variables: { name: 'Alex' },
});

// Same route, explicit channel:
await zindua.send({
  to: 'user@example.com',
  channel: 'email',
  template: 'welcome',
  variables: { name: 'Alex' },
});
5Send WhatsApp
send-whatsapp-route.ts
// WhatsApp OTP (E.164 phone, no + prefix)
await zindua.send({
  to: '243812345678',
  channel: 'whatsapp',
  template: 'otp-verification',
  variables: { code: '4592', app: 'MonApp' },
});
6Language (optional)
i18n-route.ts
await zindua.send({
  to: '243812345678',
  channel: 'whatsapp',
  template: 'otp-verification',
  lang: 'fr',
  variables: { code: '4592' },
});

React

Full integration guide

1Install
terminal
npm install @zindua/sdk
2Set API Key
.env
ZINDUA_API_KEY=znd_live_xxxxxxxxxxxxxxxxxxxx
3Initialize
server.js
// Backend only. Never call Zindua from the browser.
const { Zindua } = require('@zindua/sdk');
const zindua = new Zindua({ apiKey: process.env.ZINDUA_API_KEY });
4Send email
send-email-server.js
app.post('/api/notify', async (req, res) => {
  const { email, channel = 'email', phone, template, variables } = req.body;

  const to = channel === 'whatsapp' ? phone : email;
  const result = await zindua.send({ to, channel, template, variables });
  res.json(result);
});
5Send WhatsApp
send-whatsapp-server.js
// Client calls YOUR backend, not Zindua:
await fetch('/api/notify', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    channel: 'whatsapp',
    phone: '243812345678',
    template: 'otp-verification',
    variables: { code: '482910' },
  }),
});
6Language (optional)
i18n-server.js
// Pass lang from your app locale
body: JSON.stringify({
  channel: 'whatsapp',
  phone: '243812345678',
  template: 'otp-verification',
  lang: 'fr',
  variables: { code: '482910' },
})

Python

Full integration guide

1Install
terminal
pip install zindua
2Set API Key
.env
ZINDUA_API_KEY=znd_live_xxxxxxxxxxxxxxxxxxxx
3Initialize
app.py
import os
from zindua import Zindua

zindua = Zindua(api_key=os.environ["ZINDUA_API_KEY"])
4Send email
send-email-app.py
# Email
zindua.send(
    to="user@example.com",
    template="reset-password",
    variables={"name": "Sarah", "link": "https://..."},
)
5Send WhatsApp
send-whatsapp-app.py
# WhatsApp
zindua.send(
    to="243812345678",
    channel="whatsapp",
    template="otp-verification",
    variables={"code": "4592"},
)
6Language (optional)
i18n-app.py
zindua.send(
    to="243812345678",
    channel="whatsapp",
    template="otp-verification",
    lang="fr",
    variables={"code": "4592"},
)

Flutter

Full integration guide

1Install
pubspec.yaml
# pubspec.yaml
dependencies:
  zindua: ^1.0.0
2Set API Key
.env
// flutter run --dart-define=ZINDUA_KEY=znd_live_...
3Initialize
main.dart
import 'package:zindua/zindua.dart';

final zindua = Zindua(apiKey: const String.fromEnvironment('ZINDUA_KEY'));
4Send email
send-email-main.dart
await zindua.send(
  to: 'customer@example.com',
  template: 'order-shipped',
  variables: {'trackingUrl': 'https://...'},
);
5Send WhatsApp
send-whatsapp-main.dart
await zindua.send(
  to: '243812345678',
  channel: 'whatsapp',
  template: 'otp-verification',
  variables: {'code': '4592'},
);
6Language (optional)
i18n-main.dart
await zindua.send(
  to: '243812345678',
  channel: 'whatsapp',
  template: 'otp-verification',
  lang: 'ar',
  variables: {'code': '4592'},
);

React Native

Full integration guide

1Install
terminal
npm install @zindua/react-native
2Set API Key
.env
ZINDUA_API_KEY=znd_live_xxxxxxxxxxxxxxxxxxxx
3Initialize
VerifyScreen.tsx
// Call your backend; do not embed Zindua keys in the app binary.
4Send email
send-email-VerifyScreen.tsx
// Your Node backend:
await zindua.send({
  to: email,
  channel: 'email',
  template: 'verify-email',
  variables: { link },
});
5Send WhatsApp
send-whatsapp-VerifyScreen.tsx
// Your Node backend:
await zindua.send({
  to: phone,
  channel: 'whatsapp',
  template: 'otp-verification',
  variables: { code },
});
6Language (optional)
i18n-VerifyScreen.tsx
await fetch('https://api.yourapp.com/send-otp', {
  method: 'POST',
  body: JSON.stringify({ phone, code, lang: 'fr' }),
});
{}

REST / cURL

Full integration guide

1Install
terminal
# Any HTTP client
2Set API Key
.env
export ZINDUA_KEY="znd_live_xxxxxxxxxxxxxxxxxxxx"
3Initialize
terminal
# https://api.zindua.dev/v1/v1/send
# Authorization: Bearer znd_live_xxx
4Send email
send-email-terminal
curl -X POST https://api.zindua.dev/v1/v1/send \
  -H "Authorization: Bearer $ZINDUA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "user@example.com",
    "channel": "email",
    "template": "welcome",
    "variables": { "name": "Alex" }
  }'
5Send WhatsApp
send-whatsapp-terminal
curl -X POST https://api.zindua.dev/v1/v1/send \
  -H "Authorization: Bearer $ZINDUA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "243812345678",
    "channel": "whatsapp",
    "template": "otp-verification",
    "variables": { "code": "4592" }
  }'
6Language (optional)
i18n-terminal
curl -X POST https://api.zindua.dev/v1/v1/send \
  -H "Authorization: Bearer $ZINDUA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "243812345678",
    "channel": "whatsapp",
    "template": "otp-verification",
    "lang": "fr",
    "variables": { "code": "4592" }
  }'

Templates

One slug for email and WhatsApp. Update copy in the dashboard without redeploying your app.

How templates work

1

Create a template in Dashboard → Templates and set a slug (e.g. otp-verification).

2

Email: paste HTML with {{variables}}. WhatsApp: short plain text for OTP and alerts.

3

Add language versions if needed (fr, en, sw…).

4

Call send() with template slug + channel. Zindua renders variables per channel.

Email (HTML)
welcome-template.html
<!DOCTYPE html>
<html>
<body style="font-family: sans-serif; padding: 20px;">
  <h1>Welcome, {{name}}!</h1>
  <p>Thanks for joining {{appName}}.</p>
  <a href="{{verifyUrl}}" 
     style="background: #f97316; color: white; 
            padding: 12px 24px; border-radius: 8px;
            text-decoration: none; display: inline-block;">
    Verify Your Email
  </a>
</body>
</html>
WhatsApp (plain text)
otp-verification-whatsapp.txt
{{app}} — your verification code: {{code}}

This code expires in 10 minutes. Do not share it.
WhatsApp channel

Send OTP & codes on WhatsApp

Use the same API and templates as email. Set channel: "whatsapp" and a phone number in E.164 format. Your message is delivered from the number you connect in the dashboard.

Dashboard setup (before your first send)

1

Create a project and copy your API key (znd_live_… or znd_test_…).

2

Open Dashboard → your project → WhatsApp → Connect.

3

Scan the QR code with the phone you use for OTP (dedicated business line recommended).

4

Wait until status shows Connected. You can Pause sending or Unlink the number anytime.

5

Call POST /v1/send from your backend only. Never expose the API key in mobile or web clients.

Free plan: WhatsApp OTP only (200 messages/month). Pro and Team add the email API plus higher or unlimited WhatsApp OTP quotas.

Pause

Temporarily stop outbound WhatsApp from your number. API returns a clear error until you resume.

Unlink

Disconnect the session completely. Scan again to reconnect. Unlink does not delete your templates or logs.

Full examples: and Quickstart step 5 per framework.

send-whatsapp-otp.sh
curl -X POST https://api.zindua.dev/v1/v1/send \
  -H "Authorization: Bearer $ZINDUA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "243812345678",
    "channel": "whatsapp",
    "template": "otp-verification",
    "variables": { "code": "4592" }
  }'

Plans & channels

Free starts with WhatsApp OTP. Upgrade for email API and higher WhatsApp quotas. Same POST /v1/send on every plan.

PlanEmail APIWhatsAppWhatsApp quota
Free Yes200 / month
Pro Yes Yes20,000 / month
Team Yes YesUnlimited

Free: WhatsApp OTP only. Connect your number in the dashboard.

Pro: Email API + WhatsApp. Connect Gmail/SMTP and your WhatsApp line.

Team: Full channels for production scale.

Compare plans

Multilingual messages

Each template can have multiple language versions for email and WhatsApp. Zindua picks the right one from your send() call.

Default Language

Every project has a default language (Dashboard → Project Settings). When you call send() with optional lang, Zindua uses that template version for email or WhatsApp. If the version is missing, it falls back to the project default.

lang: "sw"
Swahili version found
lang: "de"
Not found → Falls back to default (fr)
No lang param
Uses project default (fr)
Send with language (Next.js)
i18n-nextjs.ts
await zindua.send({
  to: '243812345678',
  channel: 'whatsapp',
  template: 'otp-verification',
  lang: 'fr',
  variables: { code: '4592' },
});

Webhooks

Get real-time notifications when emails are delivered, bounced, opened, or clicked. Set up in Dashboard → Settings → Webhooks.

Webhook Payload
webhook-payload.json
{
  "type": "email.delivered",
  "timestamp": "2026-04-12T12:00:00Z",
  "data": {
    "messageId": "msg_482910",
    "to": "user@example.com",
    "template": "welcome",
    "lang": "fr",
    "deliveredAt": "2026-04-12T12:00:01Z"
  }
}

Official SDKs

We build and maintain SDKs for major platforms. All SDKs wrap the same REST API with native ergonomics.

Official
Node.js / TypeScript

Full TypeScript types. Works with Next.js, Express, Remix, Hono, and edge runtimes.

npm install @zindua/sdk
Official
Python

Sync and async support. Compatible with Django, FastAPI, Flask, and serverless.

pip install zindua
Official
Flutter / Dart

Native Dart SDK. Works on iOS, Android, Web, and Desktop Flutter apps.

pub add zindua
Official
React Native

Optimized for mobile. Supports Expo and bare React Native workflows.

npm install @zindua/react-native
{}v1.0
Any Language

Language-agnostic HTTP API. Use from Go, Ruby, PHP, Java, or anything with HTTP.

https://api.zindua.dev/v1
Official
React (via Node)

Use the Node SDK in your backend. Never expose API keys in client-side React.

npm install @zindua/sdk

Security

Keep your integration safe. Here's what matters.

Never expose your API key

Use znd_live_ keys only on the server (env vars, secrets manager). Never in mobile apps, browsers, or public repos.

Backend-only send()

Your app calls your API route; your API route calls Zindua. The end user never sees Zindua credentials.

One key per project

Keys are scoped to a single project. Rotate instantly from Dashboard if leaked.

WhatsApp session stays on Zindua

After QR scan, the linked session is stored encrypted on our side. You only use your API key; you never receive session tokens.

CORS allowlist (browser)

If you call from a browser, add allowed origins under Settings → Integrations. Server-to-server calls without Origin are unaffected.

Verify webhook signatures

Validate X-Zindua-Signature with your webhook secret before trusting delivery events.