/SMS API

SMS API

Send SMS messages to phone numbers in Ghana through the SMSGist API or any of the official SDKs.


Send a Regular Message

The most common use case — send a text message to one or more recipients.

Using the SDKs

Go

client, err := zNotification.New("smsgist")
if err != nil {
    log.Fatal(err)
}

resp, err := client.Send("sms").
    To("0245972246", "0201234567").
    From("MyBrand").
    Message("Your order #1234 has been confirmed.").
    Exec()

if err != nil {
    log.Fatal(err)
}

fmt.Println(resp.MessageID)        // "9b1deb4d-3b7d-..."
fmt.Println(resp.Status)           // "queued"
fmt.Println(resp.RecipientsCount)  // 2
fmt.Println(resp.Segments)         // 1
fmt.Println(resp.TotalCost)        // 0.06

PHP

use SMSGist\Client;

$client = Client::create();

$response = $client->send('sms')
    ->to('0245972246', '0201234567')
    ->from('MyBrand')
    ->message('Your order #1234 has been confirmed.')
    ->exec();

echo $response->messageId;        // "9b1deb4d-3b7d-..."
echo $response->status;           // "queued"
echo $response->recipientsCount;  // 2
echo $response->segments;         // 1
echo $response->totalCost;        // 0.06

Node.js

import { SMSGist } from '@smsgist/node';

const client = new SMSGist();

// Fluent builder
const response = await client.send('sms')
    .to('0245972246', '0201234567')
    .from('MyBrand')
    .message('Your order #1234 has been confirmed.')
    .exec();

console.log(response.messageId);        // "9b1deb4d-3b7d-..."
console.log(response.status);           // "queued"
console.log(response.recipientsCount);  // 2

// Or use the object-based API
const response2 = await client.sms.send({
    recipients: ['0245972246', '0201234567'],
    message: 'Your order #1234 has been confirmed.',
    senderId: 'MyBrand',
});

REST API

POST /api/sms/send

{
  "recipients": ["0245972246", "0201234567"],
  "message": "Your order #1234 has been confirmed.",
  "route": "regular"
}

Response — HTTP 201

{
  "status": "success",
  "message": "SMS queued for sending successfully",
  "data": {
    "message_id": "9b1deb4d-3b7d-4b2a-9d1e-4c7f3a2b9d1e",
    "status": "queued",
    "recipients_count": 2,
    "segments": 1,
    "total_cost": 0.06,
    "cost_per_unit": 0.03,
    "recipients": [
      { "recipient_id": "a1b2c3d4-...", "recipient": "+233245972246" },
      { "recipient_id": "b2c3d4e5-...", "recipient": "+233201234567" }
    ]
  }
}

Send an OTP Message

OTP messages use priority routing for faster delivery — ideal for verification codes, 2FA, and time-sensitive one-time passwords.

The only difference from a regular message is the routing. Use MessageOtp() instead of Message() in the SDKs, or set "route": "otp" in the REST API.

Using the SDKs

Go

resp, err := client.Send("sms").
    To("0245972246").
    From("MyApp").
    MessageOtp("Your verification code is 482916. It expires in 5 minutes.").
    Exec()

PHP

$response = $client->send('sms')
    ->to('0245972246')
    ->from('MyApp')
    ->messageOtp('Your verification code is 482916. It expires in 5 minutes.')
    ->exec();

Node.js

// Fluent builder
const response = await client.send('sms')
    .to('0245972246')
    .from('MyApp')
    .messageOtp('Your verification code is 482916. It expires in 5 minutes.')
    .exec();

// Or use the dedicated OTP method
const response2 = await client.sms.sendOtp({
    recipients: ['0245972246'],
    message: 'Your verification code is 482916. It expires in 5 minutes.',
    senderId: 'MyApp',
});

REST API

POST /api/sms/send

{
  "recipients": ["0245972246"],
  "message": "Your verification code is 482916. It expires in 5 minutes.",
  "route": "otp"
}

Sender ID

The sender ID is the name that appears as the "from" on the recipient's phone. It can be up to 11 alphanumeric characters (e.g. MyBrand, ACME Corp).

Use From() in the SDKs or configure a default sender ID on your credential in the dashboard. Sender IDs must be approved before use — you can request one from the Sender IDs page in the dashboard.

Examples

// Go — set sender ID per message
resp, err := client.Send("sms").
    To("0245972246").
    From("MyBrand").
    Message("Hello from MyBrand").
    Exec()
// PHP — set sender ID per message
$response = $client->send('sms')
    ->to('0245972246')
    ->from('MyBrand')
    ->message('Hello from MyBrand')
    ->exec();
// Node.js — set sender ID per message
const response = await client.send('sms')
    .to('0245972246')
    .from('MyBrand')
    .message('Hello from MyBrand')
    .exec();

Phone Number Formats

The API accepts Ghana phone numbers in any of these formats. All formats are normalized internally before sending.

FormatExampleDescription
Local024597224610 digits starting with 0
International23324597224612 digits starting with 233
E.164+233245972246With + prefix

You can mix formats in the same request:

resp, err := client.Send("sms").
    To("0245972246", "233201234567", "+233551234567").
    From("MyApp").
    Message("Hello").
    Exec()

The SDKs pass phone numbers directly to the API without validation — the API validates and normalizes them. If a number is invalid, it will appear in the response with an error.


Prevent Duplicate Sends

Use an idempotency key to prevent the same message from being sent twice. This is useful when retrying requests after network errors — if a request with the same key was already processed within 24 hours, the original response is returned without re-sending.

Pass any stable, unique string (e.g. an order ID, transaction reference, or UUID).

Using the SDKs

// Go
resp, err := client.Send("sms").
    To("0245972246").
    From("MyApp").
    Message("Order #5678 has shipped").
    IdempotencyKey("order-shipped-5678").
    Exec()
// PHP
$response = $client->send('sms')
    ->to('0245972246')
    ->from('MyApp')
    ->message('Order #5678 has shipped')
    ->idempotencyKey('order-shipped-5678')
    ->exec();
// Node.js
const response = await client.send('sms')
    .to('0245972246')
    .from('MyApp')
    .message('Order #5678 has shipped')
    .idempotencyKey('order-shipped-5678')
    .exec();

REST API

Add the X-Idempotency-Key header:

X-Idempotency-Key: order-shipped-5678

Message Segments and Encoding

SMS messages are split into segments based on the character encoding used. Each segment is billed separately at GHS 0.03.

Character Limits

EncodingSingle SMSPer segment (multi-part)Max segments
GSM-7 (standard)160 characters153 characters10
Unicode (UCS-2)70 characters67 characters10

The maximum message length is 1,530 characters (10 segments of 153 chars in GSM-7).

What Triggers Unicode?

If your message contains any character outside the GSM-7 set, the entire message switches to Unicode encoding. Common triggers:

  • Emojis (e.g. thumbs up, smileys)
  • Accented characters not in GSM-7 (e.g. certain diacritics)
  • Non-Latin scripts (Chinese, Arabic, etc.)

Extended GSM-7 Characters

These characters are part of GSM-7 but count as 2 characters each:

^ { } [ ] ~ | € \

Cost Examples

MessageEncodingCharactersSegmentsCost
"Hello, your order is ready."GSM-7291GHS 0.03
"Your code is 482916"GSM-7201GHS 0.03
A 200-character notificationGSM-72002GHS 0.06
"Welcome! 🎉" (has emoji)Unicode101GHS 0.03

The response always includes segments and total_cost so you know exactly what was charged.


Check Delivery Status

After sending, use the message_id from the send response to check delivery status. This returns the overall status and per-recipient details.

Using the SDKs

Go

status, err := client.GetDeliveryStatus("9b1deb4d-3b7d-4b2a-9d1e-4c7f3a2b9d1e")
if err != nil {
    log.Fatal(err)
}

fmt.Println(status.Delivered)  // number of delivered recipients
fmt.Println(status.Pending)    // number still pending
fmt.Println(status.Failed)     // number that failed

PHP

$status = $client->getDeliveryStatus('9b1deb4d-3b7d-4b2a-9d1e-4c7f3a2b9d1e');

echo $status->delivered;        // number of delivered recipients
echo $status->pending;          // number still pending
echo $status->failed;           // number that failed
echo $status->totalRecipients;  // total recipients

Node.js

const status = await client.messages.getStatus('9b1deb4d-3b7d-4b2a-9d1e-4c7f3a2b9d1e');

console.log(status.delivered);        // number of delivered recipients
console.log(status.pending);          // number still pending
console.log(status.failed);           // number that failed
console.log(status.totalRecipients);  // total recipients

REST API

GET /api/message/{id}/status

{
  "status": "success",
  "message": "Action performed successfully",
  "data": {
    "message_id": "9b1deb4d-3b7d-4b2a-9d1e-4c7f3a2b9d1e",
    "service": "sms",
    "status": "sent",
    "total_recipients": 2,
    "delivered": 1,
    "pending": 0,
    "failed": 1,
    "sent_at": "2026-03-21T10:28:00Z",
    "recipients": [
      {
        "recipient_id": "a1b2c3d4-...",
        "recipient": "+233245972246",
        "send_status": "sent",
        "delivery_status": "DELIVERED",
        "delivered_at": "2026-03-21T10:30:15Z",
        "error_message": null
      },
      {
        "recipient_id": "b2c3d4e5-...",
        "recipient": "+233201234567",
        "send_status": "failed",
        "delivery_status": "FAILED",
        "delivered_at": null,
        "error_message": "Number not reachable"
      }
    ]
  }
}

Delivery Status Values

StatusMeaning
DELIVEREDCarrier confirmed the message was delivered to the handset
PENDINGMessage sent to carrier, waiting for delivery confirmation
FAILEDDelivery failed (see error_message for details)

For real-time delivery updates instead of polling, use Webhooks.


Retry Failed Messages

If some recipients failed, you can retry delivery to just the failed ones. This creates a new message with a new ID and only charges for the retried recipients.

Using the SDKs

Go

resp, err := client.Retry("9b1deb4d-3b7d-4b2a-9d1e-4c7f3a2b9d1e")
if err != nil {
    log.Fatal(err)
}

fmt.Println(resp.MessageID)        // new message ID for the retry
fmt.Println(resp.RecipientsCount)  // number of failed recipients being retried
fmt.Println(resp.TotalCost)        // cost for the retry only

PHP

$response = $client->retry('9b1deb4d-3b7d-4b2a-9d1e-4c7f3a2b9d1e');

echo $response->messageId;        // new message ID for the retry
echo $response->recipientsCount;  // number of failed recipients being retried
echo $response->totalCost;        // cost for the retry only

Node.js

const response = await client.messages.retry('9b1deb4d-3b7d-4b2a-9d1e-4c7f3a2b9d1e');

console.log(response.messageId);        // new message ID for the retry
console.log(response.recipientsCount);  // number of failed recipients being retried
console.log(response.totalCost);        // cost for the retry only

REST API

POST /api/message/{id}/retry

{
  "status": "success",
  "message": "Message re-queued successfully",
  "data": {
    "message_id": "new-uuid-for-retry",
    "original_message_id": "9b1deb4d-3b7d-4b2a-9d1e-4c7f3a2b9d1e",
    "status": "queued",
    "recipients_count": 1,
    "total_cost": 0.03
  }
}

Request Fields Reference

POST /api/sms/send

FieldTypeRequiredDescription
recipientsstringYesPhone numbers in any accepted format (see above)
messagestringYesMessage body (max 1,530 characters)
routestringNo"regular" (default) or "otp" for priority delivery

Response Fields

FieldTypeDescription
message_idstringUnique ID — use this to check status or retry
statusstring"queued" — the message is being processed
recipients_countintNumber of recipients
segmentsintNumber of SMS segments per recipient
total_costfloatTotal cost in GHS for all recipients and segments
cost_per_unitfloatCost per segment (GHS 0.03)
recipientsarrayPer-recipient tracking IDs

Error Handling

Using the SDKs

Go

The Go SDK returns typed errors you can inspect:

import (
    "errors"
    "fmt"

    zNotification "devops.zedeks.com/go-packages/zNotification"
    "devops.zedeks.com/go-packages/zNotification/gateway"
)

resp, err := client.Send("sms").
    To("0245972246").
    From("MyApp").
    Message("Hello").
    Exec()

if err != nil {
    var apiErr *gateway.APIError
    if errors.As(err, &apiErr) {
        // HTTP error from the API (e.g. 402 insufficient credits)
        fmt.Println(apiErr.StatusCode)
        fmt.Println(apiErr.Body)
    } else if errors.Is(err, zNotification.ErrMissingRecipients) {
        fmt.Println("No recipients provided")
    } else {
        fmt.Println("Unexpected error:", err)
    }
}

PHP

use SMSGist\Exceptions\ApiException;
use SMSGist\Exceptions\ValidationException;

try {
    $response = $client->send('sms')
        ->to('0245972246')
        ->message('Hello')
        ->exec();
} catch (ValidationException $e) {
    // Missing required fields (caught before API call)
    echo $e->getMessage();
} catch (ApiException $e) {
    // HTTP error from the API
    echo $e->getCode();     // HTTP status code
    echo $e->getMessage();  // Error message
}

Node.js

try {
    const response = await client.send('sms')
        .to('0245972246')
        .message('Hello')
        .exec();
} catch (error) {
    if (error.statusCode) {
        // HTTP error from the API
        console.log(error.statusCode);  // e.g. 402
        console.log(error.message);     // e.g. "Insufficient credits"
    } else {
        // Client-side error (e.g. missing recipients)
        console.log(error.message);
    }
}

Common Errors

HTTP StatusMeaningWhat to do
400Bad request — invalid parametersCheck your request body
401Authentication failedCheck X-Client-Id and your credentials
402Insufficient creditsTop up your credit balance
422Validation errorCheck the message field for details
429Rate limit exceededWait and retry
500Server errorRetry the request

Pricing

CountryRate per segment
GhanaGHS 0.03

Each segment of a multi-part message is billed separately. The total_cost in the response reflects the total charge across all recipients and segments.