npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

sms-bulk-tz

v1.0.3

Published

A production-ready Node.js client for the TAPSA Bulk SMS API — send single or bulk SMS messages to Tanzanian phone numbers with retry, validation, and structured error handling.

Readme

sms-bulk-tz

A production-ready Node.js client for the TAPSA Bulk SMS API — send single or bulk SMS messages to Tanzanian phone numbers with built-in retry, structured error handling, and full input validation.

npm version license node


Table of Contents


Features

  • ✅ Send SMS to a single recipient or multiple in one API call
  • 📦 Batch processing for very large recipient lists with configurable batch size
  • 🔁 Automatic retry with exponential back-off on transient/server errors
  • ⏱️ Timeout handling — every request respects a configurable deadline
  • 🛡️ Input validation — phone format (255XXXXXXXXX), message length (max 160 chars), and required fields — all checked before hitting the network
  • 🧱 Structured errors — every thrown error carries a machine-readable .code, HTTP .status, and raw .raw response body
  • 🔇 Zero noise in production, opt-in debug logging for development
  • 🪶 Single runtime dependency (axios)

Requirements


Installation

npm install sms-bulk-tz

Quick Start

const SMSBulkTZ = require("sms-bulk-tz");

const sms = new SMSBulkTZ({
  apiKey: "your_api_key_here",
  senderId: "MYAPP",       // optional, defaults to "TAPSA"
});

// Send to a single number
const result = await sms.send({
  to: "255712345678",
  message: "Hello from MYAPP!",
});

console.log(result);
// {
//   success: true,
//   message: "Messages processed",
//   senderId: "MYAPP",
//   recipients: [...],
//   deducted: 1,
//   remainingBalance: 149
// }

Configuration

Pass options to the constructor to customise the client's behaviour.

const sms = new SMSBulkTZ({
  apiKey: "your_api_key_here",   // required
  senderId: "MYAPP",             // default: "TAPSA"
  timeout: 15000,                // ms — default: 10 000
  retryAttempts: 3,              // default: 3  (0 = no retries)
  retryDelay: 500,               // ms initial delay, doubles each attempt — default: 500
  debug: false,                  // default: false
  baseURL: "https://api.smstapsa.site/v1", // default — override only if needed
});

| Option | Type | Default | Description | |---|---|---|---| | apiKey | string | — | Required. Your TAPSA API key | | senderId | string | "TAPSA" | Sender name shown to recipients | | timeout | number | 10000 | Request timeout in milliseconds | | retryAttempts | number | 3 | Max retries on network/server errors (4xx errors are never retried) | | retryDelay | number | 500 | Initial delay in ms between retries; doubles each attempt | | debug | boolean | false | Log verbose debug output to console.debug | | baseURL | string | TAPSA API URL | Override the base API URL |


API Reference

send()

Send a message to one or more recipients in a single API call.

const result = await sms.send({
  to: "255712345678",               // string or string[]
  message: "Your OTP is 482910",
  senderId: "MYAPP",                // optional — overrides instance senderId
});

Parameters

| Parameter | Type | Required | Description | |---|---|---|---| | to | string \| string[] | ✅ | Recipient number(s) in 255XXXXXXXXX format | | message | string | ✅ | Message text — max 160 characters | | senderId | string | ❌ | Overrides the instance-level senderId for this call only |

Returns Promise<object>

{
  "success": true,
  "message": "Messages processed",
  "senderId": "MYAPP",
  "recipients": [...],
  "deducted": 1,
  "remainingBalance": 148
}

sendBulk()

Send a message to a large list of recipients. Internally splits the list into parallel batches. A failure in one batch does not abort the others — results from all batches are always returned.

const result = await sms.sendBulk({
  recipients: [
    "255712345678",
    "255765432100",
    "255789000111",
    // ... hundreds more
  ],
  message: "Our sale ends tonight — shop now!",
  batchSize: 100,   // optional — numbers per API call, default: 100
  senderId: "SHOP", // optional
});

console.log(result.summary);
// { sent: 3, failed: 0 }

Parameters

| Parameter | Type | Required | Description | |---|---|---|---| | recipients | string[] | ✅ | Array of phone numbers (255XXXXXXXXX format) | | message | string | ✅ | Message text — max 160 characters | | batchSize | number | ❌ | Recipients per API call — default 100 | | senderId | string | ❌ | Overrides the instance-level senderId for this call only |

Returns Promise<object>

{
  "totalRecipients": 3,
  "successful": [ /* one entry per successful batch */ ],
  "failed": [
    {
      "batch": ["255799000000"],
      "error": "Insufficient balance",
      "code": "INSUFFICIENT_BALANCE"
    }
  ],
  "summary": {
    "sent": 2,
    "failed": 1
  }
}

getBalance()

Retrieve the current account balance.

const balance = await sms.getBalance();

console.log(balance);
// {
//   success: true,
//   balance: 148,
//   currency: "TZS",
//   smsRate: 30
// }

Error Handling

All methods throw an SMSError on failure. Import it for instanceof checks.

const SMSBulkTZ = require("sms-bulk-tz");
const { SMSError } = require("sms-bulk-tz");

try {
  await sms.send({ to: "255712345678", message: "Hello!" });
} catch (err) {
  if (err instanceof SMSError) {
    console.error(err.message);  // human-readable description
    console.error(err.code);     // machine-readable code (see table below)
    console.error(err.status);   // HTTP status code, or null for network errors
    console.error(err.raw);      // raw API response body, or null
  }
}

Branching on error codes:

} catch (err) {
  if (!(err instanceof SMSError)) throw err; // re-throw unexpected errors

  switch (err.code) {
    case "INSUFFICIENT_BALANCE":
      await notifyAdminToTopUp();
      break;
    case "UNAUTHORIZED":
      console.error("Check your API key");
      break;
    case "INVALID_PHONE_NUMBER":
      console.error("Bad input:", err.message);
      break;
    case "REQUEST_TIMEOUT":
      console.error("Request timed out — retry later");
      break;
    default:
      throw err;
  }
}

Error Codes

| Code | Source | Description | |---|---|---| | MISSING_API_KEY | Client | No API key provided to the constructor | | INVALID_RECIPIENTS | Client | to / recipients is missing or empty | | INVALID_PHONE_NUMBER | Client | One or more numbers fail 255XXXXXXXXX validation | | INVALID_MESSAGE | Client | Message is missing or empty | | MESSAGE_TOO_LONG | Client | Message exceeds 160 characters | | REQUEST_TIMEOUT | Network | Request exceeded the configured timeout | | NETWORK_ERROR | Network | No response received (DNS failure, connection refused, etc.) | | BAD_REQUEST | API (400) | Invalid parameters sent to the API | | UNAUTHORIZED | API (401) | API key is invalid or missing | | INSUFFICIENT_BALANCE | API (402) | Account does not have enough credit | | SERVER_ERROR | API (500) | Unexpected error on the TAPSA server | | API_ERROR | API (other) | Any other non-2xx API response |


Debug Mode

Enable debug: true to log all requests, responses, retry attempts, and batch results to console.debug. Useful during development — disable in production.

const SMSBulkTZ = require("sms-bulk-tz");

const sms = new SMSBulkTZ({
  apiKey: "your_api_key_here",
  debug: true,
});

Example output:

[sms-bulk-tz] Attempt 1…
[sms-bulk-tz] Sending SMS { phoneNumbers: ['255712345678'], message: '***', senderId: 'TAPSA' }
[sms-bulk-tz] Send response { success: true, deducted: 1, remainingBalance: 147, ... }

Messages are redacted in debug output — the actual message text is never logged.


Phone Number Format

All phone numbers must be in international format without a +:

255 7XX XXX XXX
└─┘ └───────────┘
 TZ   9 digits

| ✅ Valid | ❌ Invalid | |---|---| | 255712345678 | 0712345678 (missing country code) | | 255765432100 | +255712345678 (leading + not allowed) | | 255789000111 | 712345678 (too short) |


License

MIT


Built for the TAPSA Bulk SMS API. Not officially affiliated with Lazack Organisation.