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.
| Format | Example | Description |
|---|---|---|
| Local | 0245972246 | 10 digits starting with 0 |
| International | 233245972246 | 12 digits starting with 233 |
| E.164 | +233245972246 | With + 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
| Encoding | Single SMS | Per segment (multi-part) | Max segments |
|---|---|---|---|
| GSM-7 (standard) | 160 characters | 153 characters | 10 |
| Unicode (UCS-2) | 70 characters | 67 characters | 10 |
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
| Message | Encoding | Characters | Segments | Cost |
|---|---|---|---|---|
| "Hello, your order is ready." | GSM-7 | 29 | 1 | GHS 0.03 |
| "Your code is 482916" | GSM-7 | 20 | 1 | GHS 0.03 |
| A 200-character notification | GSM-7 | 200 | 2 | GHS 0.06 |
| "Welcome! 🎉" (has emoji) | Unicode | 10 | 1 | GHS 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
| Status | Meaning |
|---|---|
DELIVERED | Carrier confirmed the message was delivered to the handset |
PENDING | Message sent to carrier, waiting for delivery confirmation |
FAILED | Delivery 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
| Field | Type | Required | Description |
|---|---|---|---|
recipients | string | Yes | Phone numbers in any accepted format (see above) |
message | string | Yes | Message body (max 1,530 characters) |
route | string | No | "regular" (default) or "otp" for priority delivery |
Response Fields
| Field | Type | Description |
|---|---|---|
message_id | string | Unique ID — use this to check status or retry |
status | string | "queued" — the message is being processed |
recipients_count | int | Number of recipients |
segments | int | Number of SMS segments per recipient |
total_cost | float | Total cost in GHS for all recipients and segments |
cost_per_unit | float | Cost per segment (GHS 0.03) |
recipients | array | Per-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 Status | Meaning | What to do |
|---|---|---|
400 | Bad request — invalid parameters | Check your request body |
401 | Authentication failed | Check X-Client-Id and your credentials |
402 | Insufficient credits | Top up your credit balance |
422 | Validation error | Check the message field for details |
429 | Rate limit exceeded | Wait and retry |
500 | Server error | Retry the request |
Pricing
| Country | Rate per segment |
|---|---|
| Ghana | GHS 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.
