Powering SA prepaid since 2003 / No monthly fees, ever
SIMcloud bulk airtime and data recharge platform

SIMcloud API

How to Send SMS with the SIMcloud API and Node.js

A Node.js SMS integration with secure bearer authentication, message-length validation, persisted IDs and asynchronous status checks.

Author: SIMcloud Published 15 July 2026
Node.js service sending a validated SMS through a secure API gateway

The SIMcloud SMS API can queue a South African SMS from a Node.js server and return an ID that your application uses to check status. This tutorial uses the built-in fetch available in current Node.js releases, so the example does not require an HTTP client package.

Requirements and security

  • A funded SIMcloud account and API token.
  • A server-side Node.js runtime with fetch.
  • The token stored in SIMCLOUD_API_TOKEN.
  • A database record for your local message and returned sms_id.
  • A lawful and authorised reason to message the recipient.

Never send the API token to a browser or mobile app. Your frontend should call your own authenticated backend, and your backend should call SIMcloud.

Create the Node.js request helper

const baseUrl = 'https://simcloud.co.za';

async function simcloudRequest(path, options = {}) {
  const token = process.env.SIMCLOUD_API_TOKEN;
  if (!token) {
    throw new Error('SIMCLOUD_API_TOKEN is not configured');
  }

  const response = await fetch(`${baseUrl}${path}`, {
    ...options,
    headers: {
      Authorization: `Bearer ${token}`,
      Accept: 'application/json',
      ...(options.body ? {'Content-Type': 'application/json'} : {}),
      ...options.headers,
    },
    signal: AbortSignal.timeout(30000),
  });

  const body = await response.json();
  return {status: response.status, body};
}

Keep the HTTP status. A 401 is an operations/configuration fault, while a 400 normally means the request data is invalid.

Validate the recipient and message locally

The SMS API documentation requires the international South African format +27XXXXXXXXX and accepts messages up to 459 characters.

function validateSms(recipient, message) {
  if (!/^\+27\d{9}$/.test(recipient)) {
    throw new Error('Recipient must use +27XXXXXXXXX format');
  }

  if (typeof message !== 'string' || message.length === 0) {
    throw new Error('Message is required');
  }

  if (message.length > 459) {
    throw new Error('Message may not exceed 459 characters');
  }
}

Character length affects the number of SMS credits: up to 160 characters is one part, 161–306 is two and 307–459 is three. Validate the actual string that will be sent, including personalisation.

Send the SMS

async function sendSms(recipient, message) {
  validateSms(recipient, message);

  const result = await simcloudRequest('/api/sms.php', {
    method: 'POST',
    body: JSON.stringify({recipient, message}),
  });

  if (result.status === 401) {
    throw new Error('SIMcloud API authentication failed');
  }

  if (result.status !== 200) {
    throw new Error(result.body.message || 'SIMcloud SMS request failed');
  }

  return {
    smsId: Number(result.body.sms_id),
    smsCount: Number(result.body.sms_count),
    queuedAt: result.body.timestamp,
  };
}

Persist smsId against your local message before scheduling a status check. Do not keep the ID only in process memory.

Check the message status

async function getSmsStatus(smsId) {
  const result = await simcloudRequest(
    `/api/sms.php?sms_id=${encodeURIComponent(smsId)}`
  );

  if (result.status === 404) {
    return {status: 'not_found'};
  }

  if (result.status !== 200) {
    throw new Error(result.body.message || 'SMS status request failed');
  }

  return result.body;
}

The documentation notes that status checks within 15 seconds of sending return queued. Use a background job rather than holding the original HTTP request open.

StatusHow to treat it
queued, pending, stagedNon-final. Schedule another bounded status check.
sentSubmitted onward but not necessarily confirmed on the handset.
deliveredDelivery to the recipient handset was confirmed.
failedFinal failure requiring application-specific follow-up.
unknownRetain the record and investigate rather than assuming delivery.

Expose a safe application route

Your route should authenticate the user, authorise the messaging purpose, validate the input, create a local message record and then call SIMcloud. Do not expose a generic unauthenticated “send SMS” endpoint.

  1. Authenticate the application user or trusted system.
  2. Authorise the recipient and message purpose.
  3. Create a local pending message with a unique reference.
  4. Send through SIMcloud.
  5. Persist the returned sms_id and SMS part count.
  6. Queue a status job for later.
  7. Update the local record as statuses change.

Do not log bearer tokens or full sensitive message content. Log the local record ID, SIMcloud SMS ID, endpoint, HTTP status and a safe failure reason.

The API makes delivery possible; it does not create permission to contact someone. Separate operational communication from direct marketing and apply your current consent, objection, opt-out and data-protection process. Review South African Information Regulator guidance and obtain appropriate advice for your use case.

Frequently asked questions

Which number format should Node.js send?

Use the documented +27XXXXXXXXX South African format.

How long may the SMS be?

Up to 459 characters, billed as one, two or three SMS parts according to length.

Should I poll immediately?

No. Persist the SMS ID and use a background status check. Checks within 15 seconds return queued.

Does sent mean delivered?

No. Delivered is the status that confirms receipt on the handset.

Can I put the token in frontend JavaScript?

No. Keep it on your server and call SIMcloud from the backend.

Put this guide into practice

Connect operational SMS to your Node.js backend

Queue messages server-side, persist the SIMcloud SMS ID and monitor delivery without exposing the API token.