Last Updated :

14th  March 2026

SMS API Code Examples: Tanzania Developer Guide

Learn how to send SMS using an API with real code examples in PHP, Python, Node.js, and cURL. Integrate SMS quickly using the Notify Africa SMS API.

Notify Africa SMS API uses RESTful HTTPS endpoint https://api.notify.africa/api/v1/api/messages/send with Bearer token auth. Send single/bulk SMS in Tanzania via phone_number (255 format), message, sender_id. Code examples available in curl, Python (requests), Node.js (fetch), PHP (curl), Go (net/http). Official SDKs for Python, Node.js, PHP, Go simplify integration. Check status via GET /api/v1/api/messages/status/{messageId}. Full docs: https://docs.notify.africa/. Test with free credits after signup.

Modern applications rely heavily on automated messaging.

From OTP verification and transaction alerts to marketing campaigns and appointment reminders, SMS APIs allow software systems to communicate with users instantly.

Instead of sending SMS manually, developers integrate an SMS API gateway into their applications.

This enables:

  • automated notifications

  • real-time alerts

  • account verification messages

  • system-triggered SMS campaigns

For developers building applications in Tanzania, choosing the right SMS API is critical for reliable delivery, fast integration, and scalable messaging.

In this guide, you will learn:

  • how SMS APIs work

  • real SMS API code examples

  • how to send SMS using different programming languages

  • best practices for reliable SMS integration

All examples are designed around the Notify Africa SMS API, which is built specifically for businesses and developers sending SMS across Tanzanian networks.

You can explore the full developer documentation here:

https://notify.africa/bulk-sms/developers

Before You Write A Single Line of Code

Before integrating the Notify Africa SMS API, you need three things in place.

First, a Notify Africa account. Register at notify.africa. Your account gives you access to the dashboard, your API token, and your Sender ID management.

Second, your API token. Once logged in, navigate to your developer settings to retrieve your API token. This is a Bearer token used in the Authorization header of every API request. Store it as an environment variable — never hardcode it in your source files.

Third, a registered Sender ID. In Tanzania, commercial SMS must be sent under a registered Sender ID — a brand name that appears in place of a phone number on the recipient's handset. Notify Africa handles TCRA-compliant Sender ID registration for all clients. You will need your Sender ID's numeric identifier from your dashboard for API calls. Learn more about getting set up at notify.africa/bulk-sms.

The base URL for all Notify Africa API requests is:

https://api.notify.africa

All requests must use HTTPS. The API returns JSON responses.

AUTHENTICATION: How To Connect To The Notify Africa API

The Notify Africa API uses Bearer token authentication. Every request must include an Authorization header with your API token.

The header format is:

Authorization: Bearer YOUR_API_TOKEN

Replace YOUR_API_TOKEN with the token from your Notify Africa dashboard. This applies to every example in this guide.

A note on security: never commit your API token to version control. Store it in an environment variable (.env file, server environment, or secrets manager) and reference it in your code. This is shown in every example below.

How To Get Your API Key And Sender ID

  1. Sign up free → https://account.notify.africa/auth/register

  2. Verify account and get API key from dashboard

  3. Register sender ID (free, fast TCRA compliance—handled in dashboard)

  4. Test with free credits provided on signup

No credit card needed to start coding.

SENDING A Single SMS: Code Examples In Four Languages

PHP — USING THE OFFICIAL SDK

Install via Composer:

composer require ipfsoftwares/notify-africa-php:dev-main

Send a single SMS:

<?php

use NotifyAfrica\Sms\ClientConfig;
use NotifyAfrica\Sms\NotifyAfricaClient;
use NotifyAfrica\Sms\Request\SingleMessageRequest;

$config = new ClientConfig(
    apiToken: getenv('NOTIFY_AFRICA_API_TOKEN'),
    baseUri: 'https://api.notify.africa',
);

$client = new NotifyAfricaClient($config);

$response = $client->sendSingleMessage(
    new SingleMessageRequest(
        phoneNumber: '255754123456',
        message: 'Habari! Your order #1042 has been confirmed. Thank you for shopping with us. - ShopName',
        senderId: 'YOUR_SENDER_ID_ID',
    )
);

echo 'Message ID: ' . $response->getMessageId();

PHP — RAW cURL (NO SDK)

<?php

$apiToken = getenv('NOTIFY_AFRICA_API_TOKEN');

$payload = json_encode([
    'phone_number' => '255754123456',
    'message'      => 'Habari! Your order #1042 has been confirmed. Thank you. - ShopName',
    'sender_id'    => 'YOUR_SENDER_ID_ID',
]);

$ch = curl_init('https://api.notify.africa/v1/sms/send');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => $payload,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiToken,
        'Content-Type: application/json',
        'Accept: application/json',
    ],
    CURLOPT_TIMEOUT        => 30,
]);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$data = json_decode($response, true);

if ($httpCode === 200) {
    echo 'Sent. Message ID: ' . $data['message_id'];
} else {
    echo 'Error: ' . $data['message'];
}

PYTHON — USING REQUESTS

import os
import requests

api_token = os.environ.get('NOTIFY_AFRICA_API_TOKEN')

url = 'https://api.notify.africa/v1/sms/send'

headers = {
    'Authorization': f'Bearer {api_token}',
    'Content-Type': 'application/json',
    'Accept': 'application/json',
}

payload = {
    'phone_number': '255754123456',
    'message': 'Habari! Your order #1042 has been confirmed. Thank you. - ShopName',
    'sender_id': 'YOUR_SENDER_ID_ID',
}

try:
    response = requests.post(url, json=payload, headers=headers, timeout=30)
    response.raise_for_status()
    data = response.json()
    print(f"Sent. Message ID: {data['message_id']}")
except requests.exceptions.HTTPError as e:
    print(f"HTTP Error: {e.response.status_code} - {e.response.json()}")
except requests.exceptions.RequestException as e:
    print(f"Request failed: {e}")

JAVASCRIPT / NODE.JS — USING FETCH

const sendSMS = async () => {
  const apiToken = process.env.NOTIFY_AFRICA_API_TOKEN;

  const payload = {
    phone_number: '255754123456',
    message: 'Habari! Your order #1042 has been confirmed. Thank you. - ShopName',
    sender_id: 'YOUR_SENDER_ID_ID',
  };

  try {
    const response = await fetch('https://api.notify.africa/v1/sms/send', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${apiToken}`,
        'Content-Type': 'application/json',
        'Accept': 'application/json',
      },
      body: JSON.stringify(payload),
    });

    if (!response.ok) {
      const error = await response.json();
      throw new Error(`API error ${response.status}: ${error.message}`);
    }

    const data = await response.json();
    console.log('Sent. Message ID:', data.message_id);
  } catch (err) {
    console.error('SMS send failed:', err.message);
  }
};

sendSMS();

cURL — COMMAND LINE

curl -X POST https://api.notify.africa/v1/sms/send \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "phone_number": "255754123456",
    "message": "Habari! Your order #1042 has been confirmed. Thank you. - ShopName",
    "sender_id": "YOUR_SENDER_ID_ID"
  }'

SENDING Bulk SMS: Code Examples For Campaigns

PHP — USING THE OFFICIAL SDK

<?php

use NotifyAfrica\Sms\ClientConfig;
use NotifyAfrica\Sms\NotifyAfricaClient;
use NotifyAfrica\Sms\Request\BatchMessageRequest;

$config = new ClientConfig(
    apiToken: getenv('NOTIFY_AFRICA_API_TOKEN'),
    baseUri: 'https://api.notify.africa',
);

$client = new NotifyAfricaClient($config);

$response = $client->sendBatchMessages(
    new BatchMessageRequest(
        phoneNumbers: [
            '255754123456',
            '255784987654',
            '255653112233',
            '255620445566',
        ],
        message: 'Promotion ya wiki hii: Punguzo la 20% kwa bidhaa zote. Karibu leo. - ShopName',
        senderId: 'YOUR_SENDER_ID_ID',
    )
);

echo 'Batch ID: ' . $response->getBatchId();

PYTHON — BULK SEND

import os
import requests

api_token = os.environ.get('NOTIFY_AFRICA_API_TOKEN')

url = 'https://api.notify.africa/v1/sms/batch'

headers = {
    'Authorization': f'Bearer {api_token}',
    'Content-Type': 'application/json',
    'Accept': 'application/json',
}

payload = {
    'phone_numbers': [
        '255754123456',
        '255784987654',
        '255653112233',
        '255620445566',
    ],
    'message': 'Promotion ya wiki hii: Punguzo la 20% kwa bidhaa zote. Karibu leo. - ShopName',
    'sender_id': 'YOUR_SENDER_ID_ID',
}

try:
    response = requests.post(url, json=payload, headers=headers, timeout=60)
    response.raise_for_status()
    data = response.json()
    print(f"Batch sent. Batch ID: {data['batch_id']}")
except requests.exceptions.HTTPError as e:
    print(f"Error: {e.response.json()}")

JAVASCRIPT — BULK SEND WITH CONTACT LIST FROM ARRAY

const sendBulkSMS = async (phoneNumbers, message) => {
  const apiToken = process.env.NOTIFY_AFRICA_API_TOKEN;

  const payload = {
    phone_numbers: phoneNumbers,
    message: message,
    sender_id: 'YOUR_SENDER_ID_ID',
  };

  try {
    const response = await fetch('https://api.notify.africa/v1/sms/batch', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${apiToken}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(payload),
    });

    const data = await response.json();
    if (!response.ok) throw new Error(data.message);

    console.log('Bulk send successful. Batch ID:', data.batch_id);
    return data.batch_id;
  } catch (err) {
    console.error('Bulk send failed:', err.message);
  }
};

const contacts = ['255754123456', '255784987654', '255653112233'];
const message = 'Promotion ya wiki hii: Punguzo la 20%. Karibu leo. - ShopName';
sendBulkSMS(contacts, message);

LOADING CONTACTS FROM A CSV FILE (PYTHON)

import os
import csv
import requests

api_token = os.environ.get('NOTIFY_AFRICA_API_TOKEN')

def normalise_tz_number(number):
    """Normalise a Tanzanian phone number to 255XXXXXXXXX format."""
    number = number.strip().replace(' ', '').replace('-', '').replace('+', '')
    if number.startswith('0'):
        number = '255' + number[1:]
    return number

phone_numbers = []

with open('contacts.csv', newline='') as csvfile:
    reader = csv.DictReader(csvfile)
    for row in reader:
        normalised = normalise_tz_number(row['phone'])
        if len(normalised) == 12:  # 255 + 9 digits
            phone_numbers.append(normalised)

print(f"Loaded {len(phone_numbers)} valid numbers")

payload = {
    'phone_numbers': phone_numbers,
    'message': 'Promotion ya wiki hii: Punguzo la 20%. Karibu leo. - ShopName',
    'sender_id': 'YOUR_SENDER_ID_ID',
}

response = requests.post(
    'https://api.notify.africa/v1/sms/batch',
    json=payload,
    headers={
        'Authorization': f'Bearer {api_token}',
        'Content-Type': 'application/json',
    },
    timeout=60
)

print(response.json())

Start reaching your customers today

Send SMS, email and WhatsApp campaigns in minutes.

Start free trial

OTP AND TRANSACTIONAL SMS Examples

PHP — OTP SEND WITH CODE GENERATION

<?php

use NotifyAfrica\Sms\ClientConfig;
use NotifyAfrica\Sms\NotifyAfricaClient;
use NotifyAfrica\Sms\Request\SingleMessageRequest;

function generateOtp(int $digits = 6): string {
    return str_pad(
        (string) random_int(0, (10 ** $digits) - 1),
        $digits,
        '0',
        STR_PAD_LEFT
    );
}

function sendOtp(string $phoneNumber, string $otp): bool {
    $config = new ClientConfig(
        apiToken: getenv('NOTIFY_AFRICA_API_TOKEN'),
        baseUri: 'https://api.notify.africa',
    );

    $client = new NotifyAfricaClient($config);

    $message = "Your verification code is {$otp}. "
             . "It expires in 5 minutes. "
             . "Do not share this code. - AppName";

    try {
        $client->sendSingleMessage(
            new SingleMessageRequest(
                phoneNumber: $phoneNumber,
                message: $message,
                senderId: 'YOUR_SENDER_ID_ID',
            )
        );
        return true;
    } catch (\Exception $e) {
        error_log('OTP send failed: ' . $e->getMessage());
        return false;
    }
}

// Usage
$otp  = generateOtp(6);
$sent = sendOtp('255754123456', $otp);
// Store $otp against the user session/phone number with an expiry timestamp

PYTHON — OTP WITH EXPIRY STORED IN REDIS

import os
import random
import redis
import requests

api_token    = os.environ.get('NOTIFY_AFRICA_API_TOKEN')
redis_client = redis.Redis(host='localhost', port=6379, decode_responses=True)

def generate_otp(digits=6):
    return str(random.randint(10 ** (digits - 1), 10 ** digits - 1))

def send_otp(phone_number, otp):
    message = (
        f"Your code is {otp}. "
        f"Valid for 5 minutes. "
        f"Do not share. - AppName"
    )

    response = requests.post(
        'https://api.notify.africa/v1/sms/send',
        json={
            'phone_number': phone_number,
            'message':      message,
            'sender_id':    'YOUR_SENDER_ID_ID',
        },
        headers={
            'Authorization': f'Bearer {api_token}',
            'Content-Type':  'application/json',
        },
        timeout=15
    )

    if response.status_code == 200:
        # Store OTP in Redis with 5-minute expiry
        redis_client.setex(f'otp:{phone_number}', 300, otp)
        return True
    return False

def verify_otp(phone_number, submitted_otp):
    stored_otp = redis_client.get(f'otp:{phone_number}')
    if stored_otp and stored_otp == submitted_otp:
        redis_client.delete(f'otp:{phone_number}')  # Invalidate after use
        return True
    return False

PERSONALISED SMS Examples: Dynamic Fields From A Database

PYTHON — PERSONALISED PAYMENT REMINDERS FROM A DATABASE

import os
import requests
import psycopg2

api_token = os.environ.get('NOTIFY_AFRICA_API_TOKEN')

def send_payment_reminders():
    conn   = psycopg2.connect(os.environ.get('DATABASE_URL'))
    cursor = conn.cursor()

    cursor.execute("""
        SELECT first_name, phone_number, amount_due, due_date
        FROM   loan_accounts
        WHERE  due_date = CURRENT_DATE + INTERVAL '1 day'
        AND    status   = 'active'
    """)

    customers = cursor.fetchall()
    results   = {'sent': 0, 'failed': 0}

    for first_name, phone_number, amount_due, due_date in customers:
        message = (
            f"Habari {first_name}, mkopo wako wa TZS {amount_due:,.0f} "
            f"unastahili kulipwa {due_date.strftime('%d %B %Y')}. "
            f"Lipa kupitia M-Pesa au benki. "
            f"Maswali? Piga: 0754000000. - YourSACCO"
        )

        response = requests.post(
            'https://api.notify.africa/v1/sms/send',
            json={
                'phone_number': phone_number,
                'message':      message,
                'sender_id':    'YOUR_SENDER_ID_ID',
            },
            headers={
                'Authorization': f'Bearer {api_token}',
                'Content-Type':  'application/json',
            },
            timeout=30
        )

        if response.status_code == 200:
            results['sent'] += 1
        else:
            results['failed'] += 1
            print(f"Failed for {phone_number}: {response.json()}")

    conn.close()
    print(f"Done. Sent: {results['sent']}, Failed: {results['failed']}")

send_payment_reminders()

SCHEDULED SMS Examples

JAVASCRIPT — SCHEDULED SEND WITH ISO 8601 TIMESTAMP

const scheduleSmsCampaign = async (phoneNumbers, message, scheduledAt) => {
  // scheduledAt must be ISO 8601 with timezone offset
  // e.g. '2025-04-15T09:00:00+03:00' for 9am Tanzania time (EAT = UTC+3)

  const apiToken = process.env.NOTIFY_AFRICA_API_TOKEN;

  const payload = {
    phone_numbers: phoneNumbers,
    message:       message,
    sender_id:     'YOUR_SENDER_ID_ID',
    scheduled_at:  scheduledAt,
  };

  try {
    const response = await fetch('https://api.notify.africa/v1/sms/batch', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${apiToken}`,
        'Content-Type':  'application/json',
      },
      body: JSON.stringify(payload),
    });

    const data = await response.json();
    if (!response.ok) throw new Error(data.message);

    console.log('Campaign scheduled. Batch ID:', data.batch_id);
    return data.batch_id;
  } catch (err) {
    console.error('Schedule failed:', err.message);
  }
};

// Schedule a Wednesday 9am campaign (Tanzania EAT = UTC+3)
const contacts = ['255754123456', '255784987654'];
const message  = 'Ofa ya wiki: Punguzo la 15% kwa wateja wetu wote. Karibu leo. - ShopName';

scheduleSmsCampaign(contacts, message, '2025-04-16T09:00:00+03:00');

Always include the +03:00 offset in your timestamp. Tanzania is East Africa Time (EAT), UTC+3. Omitting the offset will schedule delivery three hours early.

CHECKING Delivery Status: Code Examples

PYTHON — CHECK SINGLE MESSAGE STATUS

import os
import requests

api_token = os.environ.get('NOTIFY_AFRICA_API_TOKEN')

def check_message_status(message_id):
    url = f'https://api.notify.africa/v1/sms/status/{message_id}'

    response = requests.get(
        url,
        headers={
            'Authorization': f'Bearer {api_token}',
            'Accept':        'application/json',
        },
        timeout=15
    )

    if response.status_code == 200:
        data = response.json()
        print(f"Status:       {data['status']}")
        print(f"Delivered at: {data.get('delivered_at', 'Not yet')}")
        print(f"Network:      {data.get('network', 'Unknown')}")
        return data
    else:
        print(f"Error: {response.json()}")
        return None

check_message_status('YOUR_MESSAGE_ID_HERE')

PHP — CHECK BATCH DELIVERY SUMMARY

<?php

function checkBatchStatus(string $batchId): array {
    $apiToken = getenv('NOTIFY_AFRICA_API_TOKEN');

    $ch = curl_init("https://api.notify.africa/v1/sms/batch/{$batchId}/status");
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => [
            'Authorization: Bearer ' . $apiToken,
            'Accept: application/json',
        ],
        CURLOPT_TIMEOUT => 15,
    ]);

    $response = json_decode(curl_exec($ch), true);
    curl_close($ch);

    $deliveryRate = round(($response['delivered'] / $response['total']) * 100, 1);

    echo "Total:         {$response['total']}\n";
    echo "Delivered:     {$response['delivered']}\n";
    echo "Failed:        {$response['failed']}\n";
    echo "Pending:       {$response['pending']}\n";
    echo "Delivery rate: {$deliveryRate}%\n";

    return $response;
}

checkBatchStatus('YOUR_BATCH_ID_HERE');

WEBHOOK HANDLER Examples: Receiving Delivery Reports

PHP — WEBHOOK ENDPOINT

<?php

// webhook.php — register this URL in your Notify Africa dashboard

header('Content-Type: application/json');

$payload = file_get_contents('php://input');
$data    = json_decode($payload, true);

if (!$data) {
    http_response_code(400);
    exit;
}

// Return 200 IMMEDIATELY before any heavy processing
http_response_code(200);
echo json_encode(['received' => true]);

// Flush response to client before continuing
if (function_exists('fastcgi_finish_request')) {
    fastcgi_finish_request();
}

// Safe to take time here — client has already received 200
$messageId = $data['message_id']   ?? null;
$status    = $data['status']       ?? null;
$phone     = $data['phone_number'] ?? null;
$network   = $data['network']      ?? null;

if ($messageId && $status) {
    $pdo  = new PDO(getenv('DATABASE_URL'));
    $stmt = $pdo->prepare(
        'UPDATE sms_logs
         SET    status     = ?,
                network    = ?,
                updated_at = NOW()
         WHERE  message_id = ?'
    );
    $stmt->execute([$status, $network, $messageId]);
}

NODE.JS — EXPRESS WEBHOOK HANDLER WITH IDEMPOTENCY CHECK

const express = require('express');
const app     = express();

app.use(express.json());

app.post('/webhooks/sms-delivery', async (req, res) => {

  // Respond immediately — never make the provider wait
  res.status(200).json({ received: true });

  const { message_id, status, phone_number, network, delivered_at } = req.body;

  if (!message_id || !status) return;

  try {
    // Idempotency check — prevent duplicate processing on retried webhooks
    const existing = await db.query(
      'SELECT id FROM sms_logs WHERE message_id = $1 AND status = $2',
      [message_id, status]
    );

    if (existing.rows.length > 0) {
      console.log(`Duplicate webhook for ${message_id}, skipping`);
      return;
    }

    await db.query(
      `UPDATE sms_logs
       SET    status       = $1,
              network      = $2,
              delivered_at = $3,
              updated_at   = NOW()
       WHERE  message_id   = $4`,
      [status, network, delivered_at, message_id]
    );

    console.log(`Delivery update: ${message_id} → ${status} on ${network}`);
  } catch (err) {
    console.error('Webhook processing error:', err.message);
  }
});

app.listen(3000);

ERROR HANDLING Patterns: Production-Grade Code

PHP — COMPREHENSIVE ERROR HANDLING WITH THE OFFICIAL SDK

<?php

use NotifyAfrica\Sms\ClientConfig;
use NotifyAfrica\Sms\NotifyAfricaClient;
use NotifyAfrica\Sms\Request\SingleMessageRequest;
use NotifyAfrica\Sms\Exception\AuthenticationException;
use NotifyAfrica\Sms\Exception\ValidationException;
use NotifyAfrica\Sms\Exception\HttpRequestException;

function sendSmsWithFullErrorHandling(
    string $phoneNumber,
    string $message,
    string $senderId
): array {

    $config = new ClientConfig(
        apiToken:       getenv('NOTIFY_AFRICA_API_TOKEN'),
        baseUri:        'https://api.notify.africa',
        timeout:        30.0,
        connectTimeout: 10.0,
    );

    $client = new NotifyAfricaClient($config);

    try {

        $response = $client->sendSingleMessage(
            new SingleMessageRequest(
                phoneNumber: $phoneNumber,
                message:     $message,
                senderId:    $senderId,
            )
        );

        return [
            'success'    => true,
            'message_id' => $response->getMessageId(),
        ];

    } catch (AuthenticationException $e) {
        // Token invalid or account suspended — do NOT retry, alert DevOps
        error_log('[CRITICAL] Notify Africa auth failed: ' . $e->getMessage());
        return ['success' => false, 'error' => 'authentication_failed'];

    } catch (ValidationException $e) {
        // Bad phone number, missing field — do NOT retry without fixing the data
        error_log('[WARNING] Validation error for ' . $phoneNumber . ': ' . $e->getMessage());
        return ['success' => false, 'error' => 'validation_error', 'detail' => $e->getMessage()];

    } catch (HttpRequestException $e) {
        // Network/timeout issue — safe to retry with backoff
        error_log('[ERROR] HTTP error: ' . $e->getMessage());
        return ['success' => false, 'error' => 'network_error'];

    } catch (\Exception $e) {
        error_log('[ERROR] Unexpected error: ' . $e->getMessage());
        return ['success' => false, 'error' => 'unexpected_error'];
    }
}

Start reaching your customers today

Send SMS, email and WhatsApp campaigns in minutes.

Start free trial

PYTHON — RETRY WITH EXPONENTIAL BACKOFF

import os
import time
import requests
from requests.exceptions import RequestException

api_token = os.environ.get('NOTIFY_AFRICA_API_TOKEN')

def send_sms_with_retry(phone_number, message, sender_id, max_retries=3):
    url     = 'https://api.notify.africa/v1/sms/send'
    headers = {
        'Authorization': f'Bearer {api_token}',
        'Content-Type':  'application/json',
    }
    payload = {
        'phone_number': phone_number,
        'message':      message,
        'sender_id':    sender_id,
    }

    for attempt in range(1, max_retries + 1):
        try:
            response = requests.post(url, json=payload, headers=headers, timeout=30)

            if response.status_code == 200:
                return response.json()

            elif response.status_code == 401:
                # Authentication error — do not retry
                raise ValueError('Authentication failed. Check your API token.')

            elif response.status_code == 422:
                # Validation error — do not retry without fixing the data
                raise ValueError(f"Validation error: {response.json()}")

            elif response.status_code == 429:
                # Rate limited — wait longer before retrying
                wait_time = 2 ** attempt * 2
                print(f"Rate limited. Waiting {wait_time}s (attempt {attempt}/{max_retries})")
                time.sleep(wait_time)

            else:
                # Server error — retry with exponential backoff
                wait_time = 2 ** attempt
                print(f"Server error {response.status_code}. Retry {attempt}/{max_retries} in {wait_time}s")
                time.sleep(wait_time)

        except RequestException as e:
            wait_time = 2 ** attempt
            print(f"Network error on attempt {attempt}: {e}. Retrying in {wait_time}s")
            time.sleep(wait_time)

    raise Exception(f"Failed to send SMS after {max_retries} attempts")

ENVIRONMENT VARIABLE Setup: Security Best Practices

CREATING A .ENV FILE

# .env — never commit this file to version control
NOTIFY_AFRICA_API_TOKEN=your_api_token_here
DATABASE_URL=your_database_connection_string_here

Add to .gitignore immediately:

echo ".env" >> .gitignore

PYTHON — LOADING .ENV WITH PYTHON-DOTENV

pip install python-dotenv
from dotenv import load_dotenv
import os

load_dotenv()

api_token = os.environ.get('NOTIFY_AFRICA_API_TOKEN')

PHP — LOADING .ENV WITH VLUCAS/PHPDOTENV

composer require vlucas/phpdotenv
<?php

require_once __DIR__ . '/vendor/autoload.php';

$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();

$apiToken = $_ENV['NOTIFY_AFRICA_API_TOKEN'];

NODE.JS — LOADING .ENV WITH DOTENV

npm install dotenv
require('dotenv').config();

const apiToken = process.env.NOTIFY_AFRICA_API_TOKEN;

For production servers, set environment variables directly on the server — through your hosting panel, Docker environment, or a secrets manager — rather than deploying a .env file.

FREQUENTLY ASKED QUESTIONS

What is the base URL for the Notify Africa SMS API?

The base URL is https://api.notify.africa. All endpoints use HTTPS. Developer documentation, including full endpoint paths, request formats, and response structures, is at notify.africa/bulk-sms/developers.

Is there an official SDK for the Notify Africa API?

Yes. An official PHP 8.1+ SDK is available via Composer:

composer require ipfsoftwares/notify-africa-php:dev-main

It provides typed request and response objects, domain-specific exceptions for authentication, validation, and HTTP errors, and configurable timeouts. It is maintained by iPF Softwares, the ISO 27001:2022 certified company behind Notify Africa.

What phone number format does the Notify Africa API expect?

All Tanzanian numbers must be formatted as 255XXXXXXXXX — country code 255 followed by nine digits, no leading zero, no plus sign, no spaces. The local format 0754123456 becomes 255754123456. Here is the normalisation function used in the examples above:

def normalise_tz_number(number):
    number = number.strip().replace(' ', '').replace('-', '').replace('+', '')
    if number.startswith('0'):
        number = '255' + number[1:]
    return number
function normaliseTzNumber(string $number): string {
    $number = preg_replace('/[\s\-\+]/', '', $number);
    if (str_starts_with($number, '0')) {
        $number = '255' . substr($number, 1);
    }
    return $number;
}

How do I get a Sender ID for my API integration?

Register at notify.africa. During onboarding, Notify Africa handles TCRA-compliant Sender ID registration across all four Tanzanian networks. Once registered, your Sender ID is assigned a numeric identifier which you pass as the sender_id parameter in API calls.

Can I send personalised messages via the API?

Yes. The API accepts a plain text message body, so you build each personalised string in your application before the API call. See the personalised payment reminder example earlier in this guide for a complete database-driven implementation.

What is the difference between single send and batch send?

The single send endpoint accepts one phone_number. The batch endpoint accepts an array of phone_numbers and processes them efficiently as a group, returning a single batch_id. For any campaign with more than a handful of recipients, always use the batch endpoint. Looping the single endpoint is inefficient and hits rate limits quickly.

How do I handle errors correctly in production?

Match your response strategy to the error type. A 401 means stop and fix your credentials — do not retry. A 422 means your data is wrong — do not retry without cleaning it. A 429 means you are rate-limited — retry with exponential backoff. A 5xx means a server issue — retry with backoff. The PHP SDK exceptions map directly to these categories. See the error handling section above for complete examples.

Conclusion

Integrating SMS into your Tanzanian app or system doesn’t have to be complicated. With Notify Africa’s clear endpoints, ready code examples in curl/Python/Node.js/PHP/Go, and official SDKs, you can send reliable messages—OTPs, alerts, reminders—in no time.

Enjoy 99.9% delivery, TCRA compliance, and hands-on local support that understands Tanzanian networks and needs.

Ready to code?
→ Get your API key + free credits: https://account.notify.africa/auth/register
→ Dive into full docs: https://docs.notify.africa/
→ Need help with your integration? Book a free session: https://notify.africa/contact

Let’s get your SMS flowing reliably today. Your next feature is just a few lines of code away!

Related Articles

Discover More on This Topic

Notify Africa Deco

© 2026 Notify Africa


A solution by iPF Softwares, an ISO 27001:2022 certified software development company.