MojaWave
Login Get Started →

What businesses are building with SMS

Step-by-step integration recipes for the four most common SMS use cases — with real code you can copy and ship today.

OTP & 2FA

Send one-time passwords for account login, payments, and sensitive actions.

Payment Alerts

Real-time SMS alerts for mobile money receipts, bank deposits, and withdrawals.

Order Tracking

Automated updates when an order is received, processed, out for delivery, or completed.

Marketing Blasts

Run SMS campaigns for seasonal offers, flash sales, and personalized customer promos.

Detailed Integration Recipes

How it works

OTP & Two-Factor Authentication

Send one-time passwords for account login, payments, and sensitive actions. Follow these steps to build a secure, production-ready OTP flow.

User triggers action
Your backend
Generate OTP
MojaWave API
User's phone
Verify & allow
1

Generate a secure OTP server-side

Always generate OTPs on your backend — never on the client. Use a cryptographically random source, store a hashed copy with an expiry timestamp.

2

Send OTP via MojaWave SMS API

Call POST /v1/sms with your TCRA Sender ID and the generated code. Include an expiry hint in the message.

3

Verify the code submitted by the user

Compare hashes, check the expiry, enforce a max-attempts counter. On success, invalidate the OTP so it can't be reused.

4

Handle delivery receipt via webhook

Subscribe to MojaWave delivery webhooks so you know if the OTP SMS was actually delivered. Offer a resend option after 60 seconds if undelivered.

Step 1 — Generate OTP
// Step 1: Generate & store OTP (Node.js)
const crypto = require('crypto');
const bcrypt = require('bcrypt');

async function createOTP(phone) {
  const otp = crypto.randomInt(
    100000, 999999
  ).toString();
  const hash = await bcrypt.hash(otp, 10);
  const expiresAt = Date.now() + 5*60*1000;

  await db.otps.upsert({
    phone, hash, expiresAt, attempts: 0
  });
  return otp; // send via SMS only
}
import secrets, bcrypt
from datetime import datetime, timedelta

def create_otp(phone: str) -> str:
    otp = str(secrets.randbelow(900000)
               + 100000)
    hashed = bcrypt.hashpw(
        otp.encode(), bcrypt.gensalt()
    )
    expires = (datetime.utcnow()
               + timedelta(minutes=5))
    db.otps.upsert({
        "phone": phone,
        "hash": hashed,
        "expires_at": expires,
        "attempts": 0
    })
    return otp
# Step 2: Send OTP via MojaWave
curl -X POST \
  https://api.mojawave.com/v1/sms \
  -H "Authorization: Bearer sk_live_mw_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+255755123456",
    "from": "Duka Masta",
    "message": "Your code: 482139. 5 min."
  }'

// 200 OK
{ "id": "sms_7k2mx9",
  "status": "queued",
  "network": "vodacom",
  "cost_tzs": 20 }

📱 SMS Preview

Duka Masta

Your MojaWave verification code is 482 139. Valid for 5 minutes. Do not share this code.

Just now · Delivered ✓✓

Hash, never plaintext

Store bcrypt/argon2 hashes. If your DB leaks, OTPs are useless to attackers.

5-minute expiry

Longer widens the attack window. Shorter frustrates users. 5 min is the sweet spot.

Rate-limit resends

Allow resend only after 60 s and max 3 per session to prevent SMS flooding.

Log delivery receipts

If the OTP SMS fails to deliver, don't silently fail — show the user an error.

How it works

Payment Alerts

Send instant confirmation SMS immediately after a Mobile Money transaction or card payment is processed.

Payment event
Your webhook
Build message
MojaWave API
Customer's phone
1

Listen to your payment provider webhook

Subscribe to transaction events from M-Pesa, Selcom, or your payment gateway. Validate the signature before processing.

2

Compose a clear, concise alert message

Include: amount, transaction reference, sender name, and new balance. Keep under 160 chars to stay in one SMS segment.

3

Send via MojaWave and log the SMS ID

Fire the SMS call immediately on payment success. Store the returned sms_id alongside the transaction record.

4

Retry on delivery failure

If the delivery webhook shows failed, retry once after 30 s then mark as undeliverable in your records.

Payment Alert — Node.js
// Express webhook handler
app.post('/webhooks/payment', async (req, res) => {
  const { event, transaction } = req.body;
  if (event !== 'payment.success')
    return res.sendStatus(200);

  const { amount, phone, ref, balance }
    = transaction;

  const message =
    `Duka Masta: Umepokea TZS ` +
    `${amount.toLocaleString()} ` +
    `kutoka ${ref}. ` +
    `Salio: TZS ${balance.toLocaleString()}.`;

  const sms = await mojawave.sms.send({
    to: phone, from: 'Duka Masta', message
  });
  await db.transactions.update(ref,
    { smsId: sms.id });
  res.sendStatus(200);
});

📱 SMS Preview

Duka Masta

Duka Masta: Umepokea TZS 150,000 kutoka AMINA J. Salio: TZS 320,000. Ref: TXN-2847.

Just now · Delivered ✓✓

Fire-and-don't-await

Return HTTP 200 to your payment provider immediately. Send SMS async via a queue.

Use Swahili for trust

Swahili messages feel local and trusted. Include your merchant name to reduce fraud confusion.

Always include a ref

A short transaction ref lets customers call support with context — critical for dispute resolution.

Keep under 160 chars

161+ chars splits into 2 segments and doubles your cost. Use a char counter in your template builder.

How it works

Order Status Updates

Keep customers informed at every stage: confirmed, shipped, out for delivery, and delivered.

Order placed
Confirmed SMS
Shipped
Shipped SMS
Out for delivery
OFD SMS
Delivered SMS
1

Define order status event triggers

Map your order states to SMS triggers: CONFIRMED, SHIPPED, OUT_FOR_DELIVERY, DELIVERED.

2

Build personalised message templates

Include the customer's first name, order reference, and relevant ETA. Short, clear messages outperform verbose ones.

3

Send SMS at each status transition

Hook into your OMS state-change events and fire a corresponding SMS. Use idempotency keys to prevent duplicate sends.

4

Track delivery and log per order

Store each sms_id against the order so support staff can check delivery status if a customer claims they never got an update.

Order Notifications — Node.js
const TEMPLATES = {
  CONFIRMED: (o) =>
    `Habari ${o.name}! Oda #${o.ref} ` +
    `imekubaliwa.`,

  SHIPPED: (o) =>
    `Oda #${o.ref} imetumwa! ` +
    `ETA: ${o.eta}. Track: duka.co/t/${o.ref}`,

  OUT_FOR_DELIVERY: (o) =>
    `${o.name}, oda #${o.ref} ipo njiani! ` +
    `Itafika leo. Kuwa karibu.`,

  DELIVERED: (o) =>
    `✓ Oda #${o.ref} imefikia. Asante! ` +
    `Tathmini: duka.co/review/${o.ref}`
};

async function notifyStatus(order, status) {
  return mojawave.sms.send({
    to:      order.phone,
    from:    'Duka Masta',
    message: TEMPLATES[status](order),
    metadata: { orderId: order.ref, status }
  });
}

📱 SMS Thread Preview

Duka Masta

Habari Amina! Oda #ORD-9912 imekubaliwa.

12:05 ✓✓

Oda #ORD-9912 imetumwa! ETA: Leo 5pm.

14:22 ✓✓

Amina, oda #ORD-9912 ipo njiani! Itafika leo. Kuwa karibu.

16:40 ✓✓

Use customer's first name

Personalisation dramatically improves perceived quality. "Habari Amina!" vs "Dear Customer".

Idempotency prevents duplicates

Pass orderId + status as idempotency key so a retry bug doesn't double-send.

Include a tracking link

Even a simple order lookup page reduces WISMO support calls by up to 40%.

Respect quiet hours

Don't send between 10pm–7am unless same-day delivery. Use the send_at parameter.

How it works

Marketing Blasts

Send promotions, reminders, and re-engagement campaigns to opted-in customers — with TCRA-compliant opt-out handling built in.

Build segment
Filter opted-in only
Compose message
Schedule send
MojaWave bulk API
Track + unsubscribes
1

Build your opted-in recipient list

Only send to customers who explicitly opted in. Filter out any phone numbers that have replied STOP. This is both a legal requirement and the ethical standard.

2

Compose your campaign message

Lead with your brand name, include a clear offer or CTA, and always end with opt-out instructions: "Jibu STOP kusimamisha."

3

Send bulk via a single API call

Pass an array of up to 10,000 numbers in to. MojaWave handles batching, routing, and rate limits automatically.

4

Process inbound STOP replies

MojaWave forwards inbound SMS to your webhook. Detect "STOP" and immediately remove from your list — required within 24 hours by TCRA regulations.

Bulk Marketing — Node.js
// 1. Opted-in list only
const phones = (await db.customers.findMany({
  where: { smsOptIn: true, smsOptOut: false },
  select: { phone: true }
})).map(r => r.phone);

// 2. Send bulk campaign
const campaign = await mojawave.sms.send({
  to:      phones,     // up to 10,000
  from:    'Duka Masta',
  message: 'Duka Masta: 20% off leo! ' +
           'duka.co/promo · Jibu STOP.',
  send_at: '2025-01-15T09:00:00+03:00'
});

// 3. Handle inbound STOP replies
app.post('/webhooks/inbound', async (req, res) => {
  const { from, message } = req.body;
  if (message.trim().toUpperCase() === 'STOP')
    await db.customers.update(
      { phone: from }, { smsOptOut: true }
    );
  res.sendStatus(200);
});

📱 SMS Preview

Duka Masta

Duka Masta: Punguzo KUBWA! Pata 20% off manunuzi yote leo tu. Nenda: duka.co/promo. Jibu STOP kusimamisha.

09:00 · Delivered ✓✓

POST /webhooks/inbound-sms
{ "event":   "inbound.sms",
  "from":    "+255755123456",
  "to":      "Duka Masta",
  "message": "STOP",
  "network": "vodacom",
  "ts":      "2025-01-15T09:03:21Z" }

Opt-out is not optional

TCRA regulations require every marketing SMS to include a clear opt-out instruction. Failure risks your Sender ID being blacklisted.

Schedule for peak hours

9–11am and 4–6pm generate the highest response rates in Tanzania. Use send_at to schedule in advance.

Segment, don't blast all

Send promos only to customers who've purchased in the last 90 days. Smaller, relevant audiences convert far better.

Track campaign metrics

Use unique UTM links per campaign. Combine delivery receipts with your analytics to measure true ROI per send.

And so much more...

Flexible APIs for every customer touchpoint.

Invoices & Receipts

Send payment confirmations and invoices with attached PDFs automatically after each transaction.

Welcome Emails

Onboard new users with a branded welcome series triggered by account creation events.

Abandoned Carts

Recover lost revenue by automatically messaging users who left items in their digital basket.

Appointment Reminders

Reduce no-shows with automated reminders sent 24 hours before a scheduled meeting or booking.

Ready to ship?

Send your first SMS today

Register and get 3 free SMS credits — no credit card required.

Chat with us