Complete guide to integrating SMESS WhatsApp API into your application
New to this? You don't have to write any code — the API Tester below does it by clicking a button, and this page is written so your own AI assistant can read it and build the integration for you. Prefer to code it yourself? The Quick Start walks through every step.
SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Create and reveal keys from your client portal.
curl command you can paste straight into a terminal (on a Mac, open the Terminal app; on Windows, open Command Prompt or PowerShell) — no extra software needed.Messages are sent from your own WhatsApp number. Link it once by scanning a QR code on the Connections page (WhatsApp → Linked Devices). Until a number is connected, sends can't deliver.
Register or log in, then create a key at API Keys.
SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Send messages via POST to /api/send using apikey, recipient and text — or try it without writing code in the API Tester, the fastest way to a first successful send.
The API returns HTTP 202 with a queue_id; errors carry an error_code. Watch delivery in Messages / Queue, or get push notifications via Delivery Webhooks.
| API | A way for one computer program to ask another to do something — here, to send a WhatsApp message. You (or your AI, or a script) send a request; SMESS sends the message. |
| API key | A private password-like code that proves a request is really from your account. Yours is on the API Keys page — never share it publicly. |
| Endpoint | A specific address the API listens on, e.g. /api/send. Different endpoints do different things (send one message, send many, check status). |
| Request | One call to an endpoint — e.g. one instruction to send one message. |
| JSON | A simple, structured way to write data as text, e.g. {"recipient":"+233...","text":"Hi"}. One of two ways to format a request on this page; a plain form is the other. |
| HTTP status code | A 3-digit number every response carries, e.g. 202 (accepted) or 401 (bad key). See Error Handling for the full list. |
| Queue | Messages are not sent the instant you ask — they are placed in line and delivered moments later, paced to keep your number safe. queue_id is the ticket number for one message in that line. |
| Webhook | The reverse of an API call: instead of you checking on a message, SMESS notifies your own server the moment it is delivered or fails. Optional — see Delivery Webhooks. |
| Rate limit | A ceiling on how many requests or messages you can send in a given time, there to protect your WhatsApp number from being flagged as spam. See Rate Limits. |
All API requests require your API key. You can authenticate in three ways (in priority order):
| Method | How | Notes |
|---|---|---|
| POST field | apikey=SM-... |
Recommended for server-to-server integrations. |
| Header | X-API-Key: SM-... |
Preferred for cleaner logs. |
| Bearer | Authorization: Bearer SM-... |
Standard OAuth-style header. |
curl -X POST https://smess.io/api/send \
-H "X-API-Key: SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
-d "recipient=+233000000000" \
-d "text=Hello World!"import requests
response = requests.post(
"https://smess.io/api/send",
headers={"X-API-Key": "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"},
data={
"recipient": "+233000000000",
"text": "Hello World!",
},
)
print(response.json())// Node.js 18+ — fetch is built in. Run this server-side only;
// never ship your API key to a browser.
const response = await fetch("https://smess.io/api/send", {
method: "POST",
headers: {
"X-API-Key": "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
recipient: "+233000000000",
text: "Hello World!",
}),
});
console.log(await response.json());<?php
$ch = curl_init('https://smess.io/api/send');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['X-API-Key: SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'],
CURLOPT_POSTFIELDS => http_build_query([
'recipient' => '+233000000000',
'text' => 'Hello World!',
]),
]);
$response = curl_exec($ch);
curl_close($ch);
print_r(json_decode($response, true));// Java 17+ (java.net.http, text blocks)
HttpClient client = HttpClient.newHttpClient();
String body = "recipient=%2B233000000000&text=Hello+World%21";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://smess.io/api/send"))
.header("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());package main
import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
)
func main() {
form := url.Values{}
form.Set("recipient", "+233000000000")
form.Set("text", "Hello World!")
req, _ := http.NewRequest("POST", "https://smess.io/api/send", strings.NewReader(form.Encode()))
req.Header.Set("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
}using var client = new HttpClient();
var content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["recipient"] = "+233000000000",
["text"] = "Hello World!",
});
var request = new HttpRequestMessage(HttpMethod.Post, "https://smess.io/api/send")
{
Content = content,
};
request.Headers.Add("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
var response = await client.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());require 'net/http'
require 'json'
require 'uri'
uri = URI('https://smess.io/api/send')
request = Net::HTTP::Post.new(uri)
request['X-API-Key'] = 'SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
request.set_form_data(
'recipient' => '+233000000000',
'text' => 'Hello World!',
)
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(request)
end
puts JSON.parse(response.body)JSON request bodies are supported. Send Content-Type: application/json with the same parameter names — form-encoded and JSON behave identically:
curl -X POST https://smess.io/api/send \
-H "Content-Type: application/json" \
-H "X-API-Key: SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
-d '{
"recipient": "+233000000000",
"text": "Hello World!"
}'import requests
response = requests.post(
"https://smess.io/api/send",
headers={"X-API-Key": "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"},
json={
"recipient": "+233000000000",
"text": "Hello World!",
},
)
print(response.json())// Node.js 18+ — fetch is built in. Run this server-side only;
// never ship your API key to a browser.
const response = await fetch("https://smess.io/api/send", {
method: "POST",
headers: {
"X-API-Key": "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"Content-Type": "application/json",
},
body: JSON.stringify({
"recipient": "+233000000000",
"text": "Hello World!"
}),
});
console.log(await response.json());<?php
$ch = curl_init('https://smess.io/api/send');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['X-API-Key: SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'recipient' => '+233000000000',
'text' => 'Hello World!',
]),
]);
$response = curl_exec($ch);
curl_close($ch);
print_r(json_decode($response, true));// Java 17+ (java.net.http, text blocks)
HttpClient client = HttpClient.newHttpClient();
String body = """
{
"recipient": "+233000000000",
"text": "Hello World!"
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://smess.io/api/send"))
.header("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := `{
"recipient": "+233000000000",
"text": "Hello World!"
}`
req, _ := http.NewRequest("POST", "https://smess.io/api/send", strings.NewReader(body))
req.Header.Set("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
}using var client = new HttpClient();
var json = @"{
""recipient"": ""+233000000000"",
""text"": ""Hello World!""
}";
var content = new StringContent(json, Encoding.UTF8, "application/json");
var request = new HttpRequestMessage(HttpMethod.Post, "https://smess.io/api/send")
{
Content = content,
};
request.Headers.Add("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
var response = await client.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());require 'net/http'
require 'json'
require 'uri'
uri = URI('https://smess.io/api/send')
request = Net::HTTP::Post.new(uri)
request['X-API-Key'] = 'SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
request['Content-Type'] = 'application/json'
request.body = {
'recipient' => '+233000000000',
'text' => 'Hello World!',
}.to_json
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(request)
end
puts JSON.parse(response.body)Messages are delivered from your own WhatsApp number — recipients see your number, your business name, and your profile photo. You link the number once by scanning a QR code on the Connections page; it stays linked as a companion device.
| Tier | Channel | Sender the recipient sees |
|---|---|---|
| Primary | Your linked WhatsApp session | Your number, name & photo |
| Automatic Fallback | Cloud Backup | Still your number, name & photo. If your primary session isn't connected, SMESS automatically routes through Cloud Backup instead — for any client who has it configured, on every send, no action needed from you. This isn't limited to a particular plan. If Cloud Backup isn't configured or is also unavailable, nothing is sent and you're notified by email so you can reconnect. |
If your own number isn't connected yet, a partner business on SMESS can sponsor your delivery. You request the link from your Connections page and they approve it with caps and an end date; either side can end it instantly. Your API keys, quota, reports and webhooks stay entirely your own — only the transport is shared.
Because WhatsApp ties sender identity to the number, recipients see the sponsor's number and business name. SMESS therefore labels every linked message automatically — your brand on line one, optional event details on line two:
curl -X POST https://smess.io/api/send \
-H "X-API-Key: SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
-d "recipient=+233000000000" \
-d "text=Welcome! Your table is Table 4." \
-d "event_name=Ama & Kofi Wedding" \
-d "event_date=01 Aug 2026"
# Delivered to the guest as:
# *Your Brand*
# Ama & Kofi Wedding · 01 Aug 2026
# Welcome! Your table is Table 4.import requests
response = requests.post(
"https://smess.io/api/send",
headers={"X-API-Key": "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"},
data={
"recipient": "+233000000000",
"text": "Welcome! Your table is Table 4.",
"event_name": "Ama & Kofi Wedding",
"event_date": "01 Aug 2026",
},
)
print(response.json())
# Delivered to the guest as:
# *Your Brand*
# Ama & Kofi Wedding · 01 Aug 2026
# Welcome! Your table is Table 4.// Node.js 18+ — fetch is built in. Run this server-side only;
// never ship your API key to a browser.
const response = await fetch("https://smess.io/api/send", {
method: "POST",
headers: {
"X-API-Key": "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
recipient: "+233000000000",
text: "Welcome! Your table is Table 4.",
event_name: "Ama & Kofi Wedding",
event_date: "01 Aug 2026",
}),
});
console.log(await response.json());
// Delivered to the guest as:
// *Your Brand*
// Ama & Kofi Wedding · 01 Aug 2026
// Welcome! Your table is Table 4.<?php
$ch = curl_init('https://smess.io/api/send');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['X-API-Key: SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'],
CURLOPT_POSTFIELDS => http_build_query([
'recipient' => '+233000000000',
'text' => 'Welcome! Your table is Table 4.',
'event_name' => 'Ama & Kofi Wedding',
'event_date' => '01 Aug 2026',
]),
]);
$response = curl_exec($ch);
curl_close($ch);
print_r(json_decode($response, true));
// Delivered to the guest as:
// *Your Brand*
// Ama & Kofi Wedding · 01 Aug 2026
// Welcome! Your table is Table 4.// Java 17+ (java.net.http, text blocks)
HttpClient client = HttpClient.newHttpClient();
String body = "recipient=%2B233000000000&text=Welcome%21+Your+table+is+Table+4.&event_name=Ama+%26+Kofi+Wedding&event_date=01+Aug+2026";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://smess.io/api/send"))
.header("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
// Delivered to the guest as:
// *Your Brand*
// Ama & Kofi Wedding · 01 Aug 2026
// Welcome! Your table is Table 4.package main
import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
)
func main() {
form := url.Values{}
form.Set("recipient", "+233000000000")
form.Set("text", "Welcome! Your table is Table 4.")
form.Set("event_name", "Ama & Kofi Wedding")
form.Set("event_date", "01 Aug 2026")
req, _ := http.NewRequest("POST", "https://smess.io/api/send", strings.NewReader(form.Encode()))
req.Header.Set("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
}
// Delivered to the guest as:
// *Your Brand*
// Ama & Kofi Wedding · 01 Aug 2026
// Welcome! Your table is Table 4.using var client = new HttpClient();
var content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["recipient"] = "+233000000000",
["text"] = "Welcome! Your table is Table 4.",
["event_name"] = "Ama & Kofi Wedding",
["event_date"] = "01 Aug 2026",
});
var request = new HttpRequestMessage(HttpMethod.Post, "https://smess.io/api/send")
{
Content = content,
};
request.Headers.Add("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
var response = await client.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());
// Delivered to the guest as:
// *Your Brand*
// Ama & Kofi Wedding · 01 Aug 2026
// Welcome! Your table is Table 4.require 'net/http'
require 'json'
require 'uri'
uri = URI('https://smess.io/api/send')
request = Net::HTTP::Post.new(uri)
request['X-API-Key'] = 'SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
request.set_form_data(
'recipient' => '+233000000000',
'text' => 'Welcome! Your table is Table 4.',
'event_name' => 'Ama & Kofi Wedding',
'event_date' => '01 Aug 2026',
)
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(request)
end
puts JSON.parse(response.body)
# Delivered to the guest as:
# *Your Brand*
# Ama & Kofi Wedding · 01 Aug 2026
# Welcome! Your table is Table 4.| Parameter | Type | Description |
|---|---|---|
| event_name | optional | Event, campaign or project name shown under your brand (max 80 chars). Ignored when you send on your own channel. |
| event_date | optional | Human-readable date shown beside the event name, e.g. 01 Aug 2026 (max 40 chars). |
text — do not add the brand or event line yourself, or it will appear twice. If no parameters are supplied, the default label agreed with your sponsor is used.
WhatsApp aggressively flags newly registered numbers that immediately start sending automated traffic — that can get a number restricted or banned regardless of which API or tool you use. Before going live:
| 1–2 weeks of normal use | Use the number in the WhatsApp app like a human first: real two-way conversations, a group or two, complete business profile (name, photo, category, description). |
| Ramp volumes gradually | Start low, prioritise recipients who have saved your number or messaged you first, and grow over weeks — never launch a fresh number straight into a campaign. |
| Automatic protection | SMESS paces every newly connected number with a warm-up ramp during its first week (shorter sending intervals unlock as the number matures). Slower early delivery is deliberate protection, not a malfunction. |
| Meta Verified | Recommended: subscribe to Meta Verified in the WhatsApp Business app (Settings → Meta Verified, ∼US$11/month). You get the verified badge — stronger recipient trust — plus access to Meta support channels if your number is ever flagged. |
POST https://smess.io/api/send
Messages are submitted to a queue and delivered by the background worker. The API returns HTTP 202 Accepted on success, not 200.
| Name | Type | Required | Description |
|---|---|---|---|
| apikey | string | Yes | Your API authentication key. Also accepted as X-API-Key or Authorization: Bearer header. Not api_key. |
| recipient | string | Yes | Phone number with country code (e.g., +233000000000). Any country is supported — see below. Not phone. |
| text | string | Yes* | Message text or media caption. Not message. *At least one content field is required. |
| priority | string/int | No | otp / urgent / high / normal (default) / bulk / low |
| idempotency_key | string | No | Unique key per send attempt. Replaying the same key within 5 minutes returns the original response without sending a duplicate. Recommended for OTPs. |
Every country is supported. A recipient in full international form (leading +) is validated against the E.164 standard and accepted worldwide — there is no country allowlist to request or maintain. A number in local form is resolved using your account's country setting.
| Accepted | Why it works |
|---|---|
| +233000000000 | Ghana — full international form |
| +96100000000 | Lebanon — the country does not need to be “enabled” anywhere |
| +44000000000 | United Kingdom |
| +10000000000 | United States / Canada |
| +97100000000 +4900000000000 +33000000000 | UAE, Germany, France — and every other country code |
| +8600000000000 +5500000000000 | China, Brazil — no configuration needed |
| 0240000000 | Local form → becomes +233000000000 via your account country |
| 233000000000 | Country code without + → normalised to +233000000000 |
| +233 00 000 0000 +233-000-000000 | Spaces, dashes and brackets are stripped automatically |
These are rejected with HTTP 400 and error_code: INVALID_RECIPIENT:
| Rejected | Why it fails |
|---|---|
| +233 | Country code only — no subscriber number |
| +0123456789 | E.164 numbers cannot start with 0 after the + |
| +12345 | Too short — E.164 requires 7–15 digits in total |
| +1234567890123456 | Too long — exceeds the 15-digit E.164 maximum |
| not-a-number +44 (0) ABC | Contains letters |
| (empty) | No recipient supplied |
The endpoint detects the message type from the parameters you provide. Include only the parameters for the type you want to send:
| Type | Required parameter(s) |
|---|---|
| Text | text |
| Image | file (image URL) |
| Document | document (file URL) + optional filename |
| Video | video |
| Audio | audio |
| Location | latitude + longitude + optional label |
| Contact card | contact_name + contact_phone |
| Buttons | button1 + button1id (up to 3) |
| Copy code (OTP) | copycode + optional copytext |
| List menu | list_title + list_button + list_sections (JSON) |
curl -X POST https://smess.io/api/send \
-H "X-API-Key: SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
-d "recipient=+233000000000" \
-d "text=Hello from SMESS!"import requests
response = requests.post(
"https://smess.io/api/send",
headers={"X-API-Key": "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"},
data={
"recipient": "+233000000000",
"text": "Hello from SMESS!",
},
)
print(response.json())// Node.js 18+ — fetch is built in. Run this server-side only;
// never ship your API key to a browser.
const response = await fetch("https://smess.io/api/send", {
method: "POST",
headers: {
"X-API-Key": "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
recipient: "+233000000000",
text: "Hello from SMESS!",
}),
});
console.log(await response.json());<?php
$ch = curl_init('https://smess.io/api/send');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['X-API-Key: SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'],
CURLOPT_POSTFIELDS => http_build_query([
'recipient' => '+233000000000',
'text' => 'Hello from SMESS!',
]),
]);
$response = curl_exec($ch);
curl_close($ch);
print_r(json_decode($response, true));// Java 17+ (java.net.http, text blocks)
HttpClient client = HttpClient.newHttpClient();
String body = "recipient=%2B233000000000&text=Hello+from+SMESS%21";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://smess.io/api/send"))
.header("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());package main
import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
)
func main() {
form := url.Values{}
form.Set("recipient", "+233000000000")
form.Set("text", "Hello from SMESS!")
req, _ := http.NewRequest("POST", "https://smess.io/api/send", strings.NewReader(form.Encode()))
req.Header.Set("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
}using var client = new HttpClient();
var content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["recipient"] = "+233000000000",
["text"] = "Hello from SMESS!",
});
var request = new HttpRequestMessage(HttpMethod.Post, "https://smess.io/api/send")
{
Content = content,
};
request.Headers.Add("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
var response = await client.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());require 'net/http'
require 'json'
require 'uri'
uri = URI('https://smess.io/api/send')
request = Net::HTTP::Post.new(uri)
request['X-API-Key'] = 'SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
request.set_form_data(
'recipient' => '+233000000000',
'text' => 'Hello from SMESS!',
)
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(request)
end
puts JSON.parse(response.body){
"success": true,
"message": "Message queued successfully. It will be sent by the queue worker.",
"data": {
"queue_id": 42,
"recipient": "+233000000000",
"message_type": "text",
"priority": 5,
"priority_label": "normal",
"status": "queued",
"timestamp": "2026-06-16 10:30:00",
"remaining_quota": 999,
"note": "Message will be sent by queue worker with rate limiting and spam prevention"
}
}
All message types use the same POST /api/send endpoint. The type is auto-detected from the parameters you send.
curl -X POST https://smess.io/api/send \
-H "X-API-Key: SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
-d "recipient=+233000000000" \
-d "document=https://example.com/invoice.pdf" \
-d "filename=Invoice-2026.pdf" \
-d "text=Here is your invoice."import requests
response = requests.post(
"https://smess.io/api/send",
headers={"X-API-Key": "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"},
data={
"recipient": "+233000000000",
"document": "https://example.com/invoice.pdf",
"filename": "Invoice-2026.pdf",
"text": "Here is your invoice.",
},
)
print(response.json())// Node.js 18+ — fetch is built in. Run this server-side only;
// never ship your API key to a browser.
const response = await fetch("https://smess.io/api/send", {
method: "POST",
headers: {
"X-API-Key": "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
recipient: "+233000000000",
document: "https://example.com/invoice.pdf",
filename: "Invoice-2026.pdf",
text: "Here is your invoice.",
}),
});
console.log(await response.json());<?php
$ch = curl_init('https://smess.io/api/send');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['X-API-Key: SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'],
CURLOPT_POSTFIELDS => http_build_query([
'recipient' => '+233000000000',
'document' => 'https://example.com/invoice.pdf',
'filename' => 'Invoice-2026.pdf',
'text' => 'Here is your invoice.',
]),
]);
$response = curl_exec($ch);
curl_close($ch);
print_r(json_decode($response, true));// Java 17+ (java.net.http, text blocks)
HttpClient client = HttpClient.newHttpClient();
String body = "recipient=%2B233000000000&document=https%3A%2F%2Fexample.com%2Finvoice.pdf&filename=Invoice-2026.pdf&text=Here+is+your+invoice.";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://smess.io/api/send"))
.header("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());package main
import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
)
func main() {
form := url.Values{}
form.Set("recipient", "+233000000000")
form.Set("document", "https://example.com/invoice.pdf")
form.Set("filename", "Invoice-2026.pdf")
form.Set("text", "Here is your invoice.")
req, _ := http.NewRequest("POST", "https://smess.io/api/send", strings.NewReader(form.Encode()))
req.Header.Set("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
}using var client = new HttpClient();
var content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["recipient"] = "+233000000000",
["document"] = "https://example.com/invoice.pdf",
["filename"] = "Invoice-2026.pdf",
["text"] = "Here is your invoice.",
});
var request = new HttpRequestMessage(HttpMethod.Post, "https://smess.io/api/send")
{
Content = content,
};
request.Headers.Add("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
var response = await client.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());require 'net/http'
require 'json'
require 'uri'
uri = URI('https://smess.io/api/send')
request = Net::HTTP::Post.new(uri)
request['X-API-Key'] = 'SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
request.set_form_data(
'recipient' => '+233000000000',
'document' => 'https://example.com/invoice.pdf',
'filename' => 'Invoice-2026.pdf',
'text' => 'Here is your invoice.',
)
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(request)
end
puts JSON.parse(response.body)No public URL for the file? Upload it to SMESS first with POST /api/media (multipart/form-data, field file; the key goes in the apikey field or the X-API-Key header, exactly as for /api/send), then send using the media_id you get back. SMESS delivers the file as a document attachment (the recipient taps to open it, whatever the type) and destroys it shortly after delivery — only its name, size and checksum stay in your message history. One upload can be sent to many recipients (bulk included); it is removed once every message that uses it has gone out.
Precedence: when a request carries a media_id (or an uploaded file) together with document, file or a template's own file link, the upload wins and the others are ignored.
# 1. Upload (multipart) → media_id
curl -X POST https://smess.io/api/media \
-F "apikey=SM-your-api-key" \
-F "file=@/path/to/invoice.pdf"
# {"success":true,"media_id":"med_3f9a1c27b4d0e8f1","filename":"invoice.pdf","size_bytes":184320,
# "mime":"application/pdf","kind":"ephemeral","expires_at":"2026-09-12 10:00:00","url":"https://smess.io/m/..."}
# 2. Send it (text becomes the caption; filename is optional)
curl -X POST https://smess.io/api/send \
-d "apikey=SM-your-api-key" \
-d "recipient=+233000000000" \
-d "media_id=med_3f9a1c27b4d0e8f1" \
-d "text=Here is your invoice."
# Or do both in one call: post the file straight to /api/send
curl -X POST https://smess.io/api/send \
-F "apikey=SM-your-api-key" \
-F "recipient=+233000000000" \
-F "file=@/path/to/invoice.pdf" \
-F "text=Here is your invoice."
| Parameter | Type | Description |
|---|---|---|
| file | multipart file | Accepted: pdf, doc, docx, xls, xlsx, ppt, pptx, txt, csv, rtf, odt, ods, odp, zip, rar, 7z, gz, tar, jpg, jpeg, png, gif, webp, svg, mp4, mov, avi, mkv, mp3, ogg, wav, m4a. Size limit is your plan's attachment limit (see Rate Limits), never more than 50 MB per request. Executables and web scripts are refused whatever they are named. |
| media_id | string | Returned by /api/media; pass it to /api/send or /api/bulk instead of document. Belongs to your account only. |
| persistent | optional | 1 keeps the file until you delete it or no template links to it any more (for a template you reuse). Default 0: destroyed about 15 minutes after the last message using it went out, 24 hours after upload if never used, and in any case 72 hours after upload unless a scheduled message still needs it. |
| action | optional | status with a media_id returns whether the upload is still active or purged (and why). delete destroys it now. Default upload. |
media_id after its file was destroyed returns 410 with MEDIA_PURGED and a message saying why — upload the file again. Each account may hold up to 500 MB or 200 not-yet-destroyed uploads at a time (STORAGE_QUOTA_EXCEEDED), and /api/media shares your plan's per-minute request limit (RATE_LIMITED). Upload errors: NO_FILE, EMPTY_FILE, UNSUPPORTED_TYPE, FILE_TOO_LARGE (413), UPLOAD_INCOMPLETE, MEDIA_NOT_FOUND (404 on send), MEDIA_UPLOAD_DISABLED (503).
curl -X POST https://smess.io/api/send \
-H "X-API-Key: SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
-d "recipient=+233000000000" \
-d "file=https://example.com/photo.jpg" \
-d "text=Check this out!"import requests
response = requests.post(
"https://smess.io/api/send",
headers={"X-API-Key": "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"},
data={
"recipient": "+233000000000",
"file": "https://example.com/photo.jpg",
"text": "Check this out!",
},
)
print(response.json())// Node.js 18+ — fetch is built in. Run this server-side only;
// never ship your API key to a browser.
const response = await fetch("https://smess.io/api/send", {
method: "POST",
headers: {
"X-API-Key": "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
recipient: "+233000000000",
file: "https://example.com/photo.jpg",
text: "Check this out!",
}),
});
console.log(await response.json());<?php
$ch = curl_init('https://smess.io/api/send');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['X-API-Key: SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'],
CURLOPT_POSTFIELDS => http_build_query([
'recipient' => '+233000000000',
'file' => 'https://example.com/photo.jpg',
'text' => 'Check this out!',
]),
]);
$response = curl_exec($ch);
curl_close($ch);
print_r(json_decode($response, true));// Java 17+ (java.net.http, text blocks)
HttpClient client = HttpClient.newHttpClient();
String body = "recipient=%2B233000000000&file=https%3A%2F%2Fexample.com%2Fphoto.jpg&text=Check+this+out%21";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://smess.io/api/send"))
.header("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());package main
import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
)
func main() {
form := url.Values{}
form.Set("recipient", "+233000000000")
form.Set("file", "https://example.com/photo.jpg")
form.Set("text", "Check this out!")
req, _ := http.NewRequest("POST", "https://smess.io/api/send", strings.NewReader(form.Encode()))
req.Header.Set("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
}using var client = new HttpClient();
var content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["recipient"] = "+233000000000",
["file"] = "https://example.com/photo.jpg",
["text"] = "Check this out!",
});
var request = new HttpRequestMessage(HttpMethod.Post, "https://smess.io/api/send")
{
Content = content,
};
request.Headers.Add("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
var response = await client.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());require 'net/http'
require 'json'
require 'uri'
uri = URI('https://smess.io/api/send')
request = Net::HTTP::Post.new(uri)
request['X-API-Key'] = 'SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
request.set_form_data(
'recipient' => '+233000000000',
'file' => 'https://example.com/photo.jpg',
'text' => 'Check this out!',
)
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(request)
end
puts JSON.parse(response.body)curl -X POST https://smess.io/api/send \
-H "X-API-Key: SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
-d "recipient=+233000000000" \
-d "text=Confirm your delivery?" \
-d "button1=Yes" \
-d "button1id=confirm" \
-d "button2=No" \
-d "button2id=cancel"import requests
response = requests.post(
"https://smess.io/api/send",
headers={"X-API-Key": "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"},
data={
"recipient": "+233000000000",
"text": "Confirm your delivery?",
"button1": "Yes",
"button1id": "confirm",
"button2": "No",
"button2id": "cancel",
},
)
print(response.json())// Node.js 18+ — fetch is built in. Run this server-side only;
// never ship your API key to a browser.
const response = await fetch("https://smess.io/api/send", {
method: "POST",
headers: {
"X-API-Key": "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
recipient: "+233000000000",
text: "Confirm your delivery?",
button1: "Yes",
button1id: "confirm",
button2: "No",
button2id: "cancel",
}),
});
console.log(await response.json());<?php
$ch = curl_init('https://smess.io/api/send');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['X-API-Key: SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'],
CURLOPT_POSTFIELDS => http_build_query([
'recipient' => '+233000000000',
'text' => 'Confirm your delivery?',
'button1' => 'Yes',
'button1id' => 'confirm',
'button2' => 'No',
'button2id' => 'cancel',
]),
]);
$response = curl_exec($ch);
curl_close($ch);
print_r(json_decode($response, true));// Java 17+ (java.net.http, text blocks)
HttpClient client = HttpClient.newHttpClient();
String body = "recipient=%2B233000000000&text=Confirm+your+delivery%3F&button1=Yes&button1id=confirm&button2=No&button2id=cancel";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://smess.io/api/send"))
.header("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());package main
import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
)
func main() {
form := url.Values{}
form.Set("recipient", "+233000000000")
form.Set("text", "Confirm your delivery?")
form.Set("button1", "Yes")
form.Set("button1id", "confirm")
form.Set("button2", "No")
form.Set("button2id", "cancel")
req, _ := http.NewRequest("POST", "https://smess.io/api/send", strings.NewReader(form.Encode()))
req.Header.Set("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
}using var client = new HttpClient();
var content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["recipient"] = "+233000000000",
["text"] = "Confirm your delivery?",
["button1"] = "Yes",
["button1id"] = "confirm",
["button2"] = "No",
["button2id"] = "cancel",
});
var request = new HttpRequestMessage(HttpMethod.Post, "https://smess.io/api/send")
{
Content = content,
};
request.Headers.Add("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
var response = await client.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());require 'net/http'
require 'json'
require 'uri'
uri = URI('https://smess.io/api/send')
request = Net::HTTP::Post.new(uri)
request['X-API-Key'] = 'SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
request.set_form_data(
'recipient' => '+233000000000',
'text' => 'Confirm your delivery?',
'button1' => 'Yes',
'button1id' => 'confirm',
'button2' => 'No',
'button2id' => 'cancel',
)
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(request)
end
puts JSON.parse(response.body)curl -X POST https://smess.io/api/send \
-H "X-API-Key: SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
-d "recipient=+233000000000" \
-d "text=Your verification code is:" \
-d "copycode=482916" \
-d "copytext=Tap to Copy" \
-d "priority=otp" \
-d "idempotency_key=otp-user-123-1789191987"import requests
response = requests.post(
"https://smess.io/api/send",
headers={"X-API-Key": "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"},
data={
"recipient": "+233000000000",
"text": "Your verification code is:",
"copycode": "482916",
"copytext": "Tap to Copy",
"priority": "otp",
"idempotency_key": "otp-user-123-1789191987",
},
)
print(response.json())// Node.js 18+ — fetch is built in. Run this server-side only;
// never ship your API key to a browser.
const response = await fetch("https://smess.io/api/send", {
method: "POST",
headers: {
"X-API-Key": "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
recipient: "+233000000000",
text: "Your verification code is:",
copycode: "482916",
copytext: "Tap to Copy",
priority: "otp",
idempotency_key: "otp-user-123-1789191987",
}),
});
console.log(await response.json());<?php
$ch = curl_init('https://smess.io/api/send');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['X-API-Key: SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'],
CURLOPT_POSTFIELDS => http_build_query([
'recipient' => '+233000000000',
'text' => 'Your verification code is:',
'copycode' => '482916',
'copytext' => 'Tap to Copy',
'priority' => 'otp',
'idempotency_key' => 'otp-user-123-1789191987',
]),
]);
$response = curl_exec($ch);
curl_close($ch);
print_r(json_decode($response, true));// Java 17+ (java.net.http, text blocks)
HttpClient client = HttpClient.newHttpClient();
String body = "recipient=%2B233000000000&text=Your+verification+code+is%3A©code=482916©text=Tap+to+Copy&priority=otp&idempotency_key=otp-user-123-1789191987";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://smess.io/api/send"))
.header("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());package main
import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
)
func main() {
form := url.Values{}
form.Set("recipient", "+233000000000")
form.Set("text", "Your verification code is:")
form.Set("copycode", "482916")
form.Set("copytext", "Tap to Copy")
form.Set("priority", "otp")
form.Set("idempotency_key", "otp-user-123-1789191987")
req, _ := http.NewRequest("POST", "https://smess.io/api/send", strings.NewReader(form.Encode()))
req.Header.Set("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
}using var client = new HttpClient();
var content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["recipient"] = "+233000000000",
["text"] = "Your verification code is:",
["copycode"] = "482916",
["copytext"] = "Tap to Copy",
["priority"] = "otp",
["idempotency_key"] = "otp-user-123-1789191987",
});
var request = new HttpRequestMessage(HttpMethod.Post, "https://smess.io/api/send")
{
Content = content,
};
request.Headers.Add("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
var response = await client.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());require 'net/http'
require 'json'
require 'uri'
uri = URI('https://smess.io/api/send')
request = Net::HTTP::Post.new(uri)
request['X-API-Key'] = 'SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
request.set_form_data(
'recipient' => '+233000000000',
'text' => 'Your verification code is:',
'copycode' => '482916',
'copytext' => 'Tap to Copy',
'priority' => 'otp',
'idempotency_key' => 'otp-user-123-1789191987',
)
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(request)
end
puts JSON.parse(response.body)curl -X POST https://smess.io/api/send \
-H "X-API-Key: SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
-d "recipient=+233000000000" \
-d "latitude=5.6037" \
-d "longitude=-0.1870" \
-d "label=Accra Mall"import requests
response = requests.post(
"https://smess.io/api/send",
headers={"X-API-Key": "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"},
data={
"recipient": "+233000000000",
"latitude": "5.6037",
"longitude": "-0.1870",
"label": "Accra Mall",
},
)
print(response.json())// Node.js 18+ — fetch is built in. Run this server-side only;
// never ship your API key to a browser.
const response = await fetch("https://smess.io/api/send", {
method: "POST",
headers: {
"X-API-Key": "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
recipient: "+233000000000",
latitude: "5.6037",
longitude: "-0.1870",
label: "Accra Mall",
}),
});
console.log(await response.json());<?php
$ch = curl_init('https://smess.io/api/send');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['X-API-Key: SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'],
CURLOPT_POSTFIELDS => http_build_query([
'recipient' => '+233000000000',
'latitude' => '5.6037',
'longitude' => '-0.1870',
'label' => 'Accra Mall',
]),
]);
$response = curl_exec($ch);
curl_close($ch);
print_r(json_decode($response, true));// Java 17+ (java.net.http, text blocks)
HttpClient client = HttpClient.newHttpClient();
String body = "recipient=%2B233000000000&latitude=5.6037&longitude=-0.1870&label=Accra+Mall";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://smess.io/api/send"))
.header("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());package main
import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
)
func main() {
form := url.Values{}
form.Set("recipient", "+233000000000")
form.Set("latitude", "5.6037")
form.Set("longitude", "-0.1870")
form.Set("label", "Accra Mall")
req, _ := http.NewRequest("POST", "https://smess.io/api/send", strings.NewReader(form.Encode()))
req.Header.Set("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
}using var client = new HttpClient();
var content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["recipient"] = "+233000000000",
["latitude"] = "5.6037",
["longitude"] = "-0.1870",
["label"] = "Accra Mall",
});
var request = new HttpRequestMessage(HttpMethod.Post, "https://smess.io/api/send")
{
Content = content,
};
request.Headers.Add("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
var response = await client.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());require 'net/http'
require 'json'
require 'uri'
uri = URI('https://smess.io/api/send')
request = Net::HTTP::Post.new(uri)
request['X-API-Key'] = 'SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
request.set_form_data(
'recipient' => '+233000000000',
'latitude' => '5.6037',
'longitude' => '-0.1870',
'label' => 'Accra Mall',
)
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(request)
end
puts JSON.parse(response.body)curl -X POST https://smess.io/api/send \
-H "X-API-Key: SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
-d "recipient=+233000000000" \
-d "text=Please select a department:" \
-d "list_title=Contact Us" \
-d "list_button=View Departments" \
-d 'list_sections=[{"title":"Sales","rows":[{"title":"New Orders","description":"Place a new order","rowId":"sales_new"}]}]'import requests
import json
response = requests.post(
"https://smess.io/api/send",
headers={"X-API-Key": "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"},
data={
"recipient": "+233000000000",
"text": "Please select a department:",
"list_title": "Contact Us",
"list_button": "View Departments",
"list_sections": json.dumps([
{
"title": "Sales",
"rows": [
{
"title": "New Orders",
"description": "Place a new order",
"rowId": "sales_new"
}
]
}
]),
},
)
print(response.json())// Node.js 18+ — fetch is built in. Run this server-side only;
// never ship your API key to a browser.
const response = await fetch("https://smess.io/api/send", {
method: "POST",
headers: {
"X-API-Key": "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
recipient: "+233000000000",
text: "Please select a department:",
list_title: "Contact Us",
list_button: "View Departments",
list_sections: JSON.stringify([
{
"title": "Sales",
"rows": [
{
"title": "New Orders",
"description": "Place a new order",
"rowId": "sales_new"
}
]
}
]),
}),
});
console.log(await response.json());<?php
$ch = curl_init('https://smess.io/api/send');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['X-API-Key: SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'],
CURLOPT_POSTFIELDS => http_build_query([
'recipient' => '+233000000000',
'text' => 'Please select a department:',
'list_title' => 'Contact Us',
'list_button' => 'View Departments',
'list_sections' => json_encode([
[
'title' => 'Sales',
'rows' => [
[
'title' => 'New Orders',
'description' => 'Place a new order',
'rowId' => 'sales_new'
]
]
]
]),
]),
]);
$response = curl_exec($ch);
curl_close($ch);
print_r(json_decode($response, true));// Java 17+ (java.net.http, text blocks)
HttpClient client = HttpClient.newHttpClient();
String body = "recipient=%2B233000000000&text=Please+select+a+department%3A&list_title=Contact+Us&list_button=View+Departments&list_sections=%5B%7B%22title%22%3A%22Sales%22%2C%22rows%22%3A%5B%7B%22title%22%3A%22New+Orders%22%2C%22description%22%3A%22Place+a+new+order%22%2C%22rowId%22%3A%22sales_new%22%7D%5D%7D%5D";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://smess.io/api/send"))
.header("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());package main
import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
)
func main() {
form := url.Values{}
form.Set("recipient", "+233000000000")
form.Set("text", "Please select a department:")
form.Set("list_title", "Contact Us")
form.Set("list_button", "View Departments")
form.Set("list_sections", `[{"title":"Sales","rows":[{"title":"New Orders","description":"Place a new order","rowId":"sales_new"}]}]`)
req, _ := http.NewRequest("POST", "https://smess.io/api/send", strings.NewReader(form.Encode()))
req.Header.Set("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
}using var client = new HttpClient();
var content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["recipient"] = "+233000000000",
["text"] = "Please select a department:",
["list_title"] = "Contact Us",
["list_button"] = "View Departments",
["list_sections"] = @"[{""title"":""Sales"",""rows"":[{""title"":""New Orders"",""description"":""Place a new order"",""rowId"":""sales_new""}]}]",
});
var request = new HttpRequestMessage(HttpMethod.Post, "https://smess.io/api/send")
{
Content = content,
};
request.Headers.Add("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
var response = await client.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());require 'net/http'
require 'json'
require 'uri'
uri = URI('https://smess.io/api/send')
request = Net::HTTP::Post.new(uri)
request['X-API-Key'] = 'SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
request.set_form_data(
'recipient' => '+233000000000',
'text' => 'Please select a department:',
'list_title' => 'Contact Us',
'list_button' => 'View Departments',
'list_sections' => [
{
'title' => 'Sales',
'rows' => [
{
'title' => 'New Orders',
'description' => 'Place a new order',
'rowId' => 'sales_new'
}
]
}
].to_json,
)
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(request)
end
puts JSON.parse(response.body)Templates are ready-made messages — text, media, buttons, list menus or copy codes — that you send by ID instead of rebuilding every time. Everything you can send, with its ID, placeholders and usage count, is listed on the Templates page.
Your account sees two shelves on that page, and the API treats them identically — both are sent with template_id, and nothing else about the call changes.
| Shelf | Who can send it | Who can change it |
|---|---|---|
| SMESS Library | Every SMESS account, yours included — nothing to set up, the IDs are ready to use today. | SMESS. A library template is read-only for clients; use Save a copy on the Templates page to get an editable version of your own. |
| My templates | Only your account. Another client can never see or send one of yours. | You — create, edit, pause and delete them yourself on the Templates page. |
template_id and its own usage count, and editing it never touches the library original.
Sending one is an ordinary POST /api/send call with one extra parameter, template_id — the same call whether the ID belongs to the SMESS Library or to you. The template supplies the content (and therefore the message type); you supply the recipient and any placeholder values.
Templates apply to /api/send. The bulk endpoint /api/bulk does not accept template_id — it is ignored there. The portal's Bulk Sender does support templates: it submits each row individually to /api/send.
The template_id values in the examples below are illustrations only. Every ID is real for somebody, so copy the ones listed beside your templates on the Templates page rather than the numbers printed here.
| Name | Type | Required | Description |
|---|---|---|---|
| template_id | int | No | ID of any template your account may send — one of your own or one from the SMESS Library, as shown on the Templates page. Omit it for a normal ad-hoc send. |
| variables | JSON object | No | Placeholder values, e.g. {"customer_name":"Ama","order_id":"ORD-1"}. In a JSON request body it may be a real object instead of a string. |
| var_<name> | string | No | A single placeholder value, e.g. var_customer_name=Ama. Merged over variables, so var_* wins when both supply the same name. |
All three are accepted the same three ways as every other parameter: POST form field, query string, or JSON body.
Template content is written with {name} placeholders — Hi {customer_name}, your order {order_id} is ready. — and every occurrence is replaced with the value you supply. Substitution happens everywhere text appears in a template: the message body, video captions, button labels, and list titles and rows.
{order_id} stays {order_id}. SMESS never blanks it out, so a half-filled message is visible rather than silently wrong. Each template — library or your own — lists the names it expects on the Templates page.
The template is a starting point, not a straitjacket. Anything you send explicitly wins over the template's own value, so one template can serve many messages:
| What you send | What is delivered |
|---|---|
| text=… | Your text is used — the template's own text is discarded. |
| (no text) | The template's text is used, with its placeholders filled in. |
| text= (empty) | Empty values never override — the template's text is used. |
The same rule applies to every parameter, not just text: recipient, media URL, button labels, copy code and so on. Everything else behaves exactly as on a normal send — the merged values go through the same message-type auto-detection and the same validation. A buttons template therefore arrives as a buttons message without you sending button1, an image template carries its own file URL, and so on.
curl -X POST https://smess.io/api/send \
-H "X-API-Key: SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
-d "recipient=+233000000000" \
-d "template_id=12" \
-d 'variables={"customer_name":"Ama","order_id":"ORD-1"}'import requests
import json
response = requests.post(
"https://smess.io/api/send",
headers={"X-API-Key": "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"},
data={
"recipient": "+233000000000",
"template_id": "12",
"variables": json.dumps({
"customer_name": "Ama",
"order_id": "ORD-1"
}),
},
)
print(response.json())// Node.js 18+ — fetch is built in. Run this server-side only;
// never ship your API key to a browser.
const response = await fetch("https://smess.io/api/send", {
method: "POST",
headers: {
"X-API-Key": "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
recipient: "+233000000000",
template_id: "12",
variables: JSON.stringify({
"customer_name": "Ama",
"order_id": "ORD-1"
}),
}),
});
console.log(await response.json());<?php
$ch = curl_init('https://smess.io/api/send');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['X-API-Key: SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'],
CURLOPT_POSTFIELDS => http_build_query([
'recipient' => '+233000000000',
'template_id' => '12',
'variables' => json_encode([
'customer_name' => 'Ama',
'order_id' => 'ORD-1'
]),
]),
]);
$response = curl_exec($ch);
curl_close($ch);
print_r(json_decode($response, true));// Java 17+ (java.net.http, text blocks)
HttpClient client = HttpClient.newHttpClient();
String body = "recipient=%2B233000000000&template_id=12&variables=%7B%22customer_name%22%3A%22Ama%22%2C%22order_id%22%3A%22ORD-1%22%7D";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://smess.io/api/send"))
.header("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());package main
import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
)
func main() {
form := url.Values{}
form.Set("recipient", "+233000000000")
form.Set("template_id", "12")
form.Set("variables", `{"customer_name":"Ama","order_id":"ORD-1"}`)
req, _ := http.NewRequest("POST", "https://smess.io/api/send", strings.NewReader(form.Encode()))
req.Header.Set("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
}using var client = new HttpClient();
var content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["recipient"] = "+233000000000",
["template_id"] = "12",
["variables"] = @"{""customer_name"":""Ama"",""order_id"":""ORD-1""}",
});
var request = new HttpRequestMessage(HttpMethod.Post, "https://smess.io/api/send")
{
Content = content,
};
request.Headers.Add("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
var response = await client.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());require 'net/http'
require 'json'
require 'uri'
uri = URI('https://smess.io/api/send')
request = Net::HTTP::Post.new(uri)
request['X-API-Key'] = 'SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
request.set_form_data(
'recipient' => '+233000000000',
'template_id' => '12',
'variables' => {
'customer_name' => 'Ama',
'order_id' => 'ORD-1'
}.to_json,
)
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(request)
end
puts JSON.parse(response.body)The same send with individual fields instead of a JSON string — often easier from a form-encoded client:
curl -X POST https://smess.io/api/send \
-H "X-API-Key: SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
-d "recipient=+233000000000" \
-d "template_id=12" \
-d "var_customer_name=Ama" \
-d "var_order_id=ORD-1"import requests
response = requests.post(
"https://smess.io/api/send",
headers={"X-API-Key": "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"},
data={
"recipient": "+233000000000",
"template_id": "12",
"var_customer_name": "Ama",
"var_order_id": "ORD-1",
},
)
print(response.json())// Node.js 18+ — fetch is built in. Run this server-side only;
// never ship your API key to a browser.
const response = await fetch("https://smess.io/api/send", {
method: "POST",
headers: {
"X-API-Key": "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
recipient: "+233000000000",
template_id: "12",
var_customer_name: "Ama",
var_order_id: "ORD-1",
}),
});
console.log(await response.json());<?php
$ch = curl_init('https://smess.io/api/send');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['X-API-Key: SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'],
CURLOPT_POSTFIELDS => http_build_query([
'recipient' => '+233000000000',
'template_id' => '12',
'var_customer_name' => 'Ama',
'var_order_id' => 'ORD-1',
]),
]);
$response = curl_exec($ch);
curl_close($ch);
print_r(json_decode($response, true));// Java 17+ (java.net.http, text blocks)
HttpClient client = HttpClient.newHttpClient();
String body = "recipient=%2B233000000000&template_id=12&var_customer_name=Ama&var_order_id=ORD-1";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://smess.io/api/send"))
.header("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());package main
import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
)
func main() {
form := url.Values{}
form.Set("recipient", "+233000000000")
form.Set("template_id", "12")
form.Set("var_customer_name", "Ama")
form.Set("var_order_id", "ORD-1")
req, _ := http.NewRequest("POST", "https://smess.io/api/send", strings.NewReader(form.Encode()))
req.Header.Set("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
}using var client = new HttpClient();
var content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["recipient"] = "+233000000000",
["template_id"] = "12",
["var_customer_name"] = "Ama",
["var_order_id"] = "ORD-1",
});
var request = new HttpRequestMessage(HttpMethod.Post, "https://smess.io/api/send")
{
Content = content,
};
request.Headers.Add("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
var response = await client.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());require 'net/http'
require 'json'
require 'uri'
uri = URI('https://smess.io/api/send')
request = Net::HTTP::Post.new(uri)
request['X-API-Key'] = 'SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
request.set_form_data(
'recipient' => '+233000000000',
'template_id' => '12',
'var_customer_name' => 'Ama',
'var_order_id' => 'ORD-1',
)
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(request)
end
puts JSON.parse(response.body)And as a JSON body, where variables is a real object:
curl -X POST https://smess.io/api/send \
-H "Content-Type: application/json" \
-H "X-API-Key: SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
-d '{
"recipient": "+233000000000",
"template_id": 12,
"variables": {
"customer_name": "Ama",
"order_id": "ORD-1"
}
}'import requests
response = requests.post(
"https://smess.io/api/send",
headers={"X-API-Key": "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"},
json={
"recipient": "+233000000000",
"template_id": 12,
"variables": {
"customer_name": "Ama",
"order_id": "ORD-1"
},
},
)
print(response.json())// Node.js 18+ — fetch is built in. Run this server-side only;
// never ship your API key to a browser.
const response = await fetch("https://smess.io/api/send", {
method: "POST",
headers: {
"X-API-Key": "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"Content-Type": "application/json",
},
body: JSON.stringify({
"recipient": "+233000000000",
"template_id": 12,
"variables": {
"customer_name": "Ama",
"order_id": "ORD-1"
}
}),
});
console.log(await response.json());<?php
$ch = curl_init('https://smess.io/api/send');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['X-API-Key: SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'recipient' => '+233000000000',
'template_id' => 12,
'variables' => [
'customer_name' => 'Ama',
'order_id' => 'ORD-1'
],
]),
]);
$response = curl_exec($ch);
curl_close($ch);
print_r(json_decode($response, true));// Java 17+ (java.net.http, text blocks)
HttpClient client = HttpClient.newHttpClient();
String body = """
{
"recipient": "+233000000000",
"template_id": 12,
"variables": {
"customer_name": "Ama",
"order_id": "ORD-1"
}
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://smess.io/api/send"))
.header("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := `{
"recipient": "+233000000000",
"template_id": 12,
"variables": {
"customer_name": "Ama",
"order_id": "ORD-1"
}
}`
req, _ := http.NewRequest("POST", "https://smess.io/api/send", strings.NewReader(body))
req.Header.Set("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
}using var client = new HttpClient();
var json = @"{
""recipient"": ""+233000000000"",
""template_id"": 12,
""variables"": {
""customer_name"": ""Ama"",
""order_id"": ""ORD-1""
}
}";
var content = new StringContent(json, Encoding.UTF8, "application/json");
var request = new HttpRequestMessage(HttpMethod.Post, "https://smess.io/api/send")
{
Content = content,
};
request.Headers.Add("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
var response = await client.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());require 'net/http'
require 'json'
require 'uri'
uri = URI('https://smess.io/api/send')
request = Net::HTTP::Post.new(uri)
request['X-API-Key'] = 'SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
request['Content-Type'] = 'application/json'
request.body = {
'recipient' => '+233000000000',
'template_id' => 12,
'variables' => {
'customer_name' => 'Ama',
'order_id' => 'ORD-1'
},
}.to_json
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(request)
end
puts JSON.parse(response.body)The buttons live in the template — you only fill in the placeholders:
curl -X POST https://smess.io/api/send \
-H "X-API-Key: SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
-d "recipient=+233000000000" \
-d "template_id=18" \
-d "var_customer_name=Ama"
# Delivered as the template defines it, e.g.
# Hi Ama, is 2pm tomorrow still good for your delivery?
# [ Yes ] [ No ]import requests
response = requests.post(
"https://smess.io/api/send",
headers={"X-API-Key": "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"},
data={
"recipient": "+233000000000",
"template_id": "18",
"var_customer_name": "Ama",
},
)
print(response.json())
# Delivered as the template defines it, e.g.
# Hi Ama, is 2pm tomorrow still good for your delivery?
# [ Yes ] [ No ]// Node.js 18+ — fetch is built in. Run this server-side only;
// never ship your API key to a browser.
const response = await fetch("https://smess.io/api/send", {
method: "POST",
headers: {
"X-API-Key": "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
recipient: "+233000000000",
template_id: "18",
var_customer_name: "Ama",
}),
});
console.log(await response.json());
// Delivered as the template defines it, e.g.
// Hi Ama, is 2pm tomorrow still good for your delivery?
// [ Yes ] [ No ]<?php
$ch = curl_init('https://smess.io/api/send');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['X-API-Key: SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'],
CURLOPT_POSTFIELDS => http_build_query([
'recipient' => '+233000000000',
'template_id' => '18',
'var_customer_name' => 'Ama',
]),
]);
$response = curl_exec($ch);
curl_close($ch);
print_r(json_decode($response, true));
// Delivered as the template defines it, e.g.
// Hi Ama, is 2pm tomorrow still good for your delivery?
// [ Yes ] [ No ]// Java 17+ (java.net.http, text blocks)
HttpClient client = HttpClient.newHttpClient();
String body = "recipient=%2B233000000000&template_id=18&var_customer_name=Ama";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://smess.io/api/send"))
.header("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
// Delivered as the template defines it, e.g.
// Hi Ama, is 2pm tomorrow still good for your delivery?
// [ Yes ] [ No ]package main
import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
)
func main() {
form := url.Values{}
form.Set("recipient", "+233000000000")
form.Set("template_id", "18")
form.Set("var_customer_name", "Ama")
req, _ := http.NewRequest("POST", "https://smess.io/api/send", strings.NewReader(form.Encode()))
req.Header.Set("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
}
// Delivered as the template defines it, e.g.
// Hi Ama, is 2pm tomorrow still good for your delivery?
// [ Yes ] [ No ]using var client = new HttpClient();
var content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["recipient"] = "+233000000000",
["template_id"] = "18",
["var_customer_name"] = "Ama",
});
var request = new HttpRequestMessage(HttpMethod.Post, "https://smess.io/api/send")
{
Content = content,
};
request.Headers.Add("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
var response = await client.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());
// Delivered as the template defines it, e.g.
// Hi Ama, is 2pm tomorrow still good for your delivery?
// [ Yes ] [ No ]require 'net/http'
require 'json'
require 'uri'
uri = URI('https://smess.io/api/send')
request = Net::HTTP::Post.new(uri)
request['X-API-Key'] = 'SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
request.set_form_data(
'recipient' => '+233000000000',
'template_id' => '18',
'var_customer_name' => 'Ama',
)
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(request)
end
puts JSON.parse(response.body)
# Delivered as the template defines it, e.g.
# Hi Ama, is 2pm tomorrow still good for your delivery?
# [ Yes ] [ No ]A single label can still be overridden per send — add -d "button1=Yes, 2pm works" and the rest of the template is untouched.
curl -X POST https://smess.io/api/send \
-H "X-API-Key: SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
-d "recipient=+233000000000" \
-d "template_id=18" \
-d "text=Hi Ama, is 2pm tomorrow still good?"import requests
response = requests.post(
"https://smess.io/api/send",
headers={"X-API-Key": "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"},
data={
"recipient": "+233000000000",
"template_id": "18",
"text": "Hi Ama, is 2pm tomorrow still good?",
},
)
print(response.json())// Node.js 18+ — fetch is built in. Run this server-side only;
// never ship your API key to a browser.
const response = await fetch("https://smess.io/api/send", {
method: "POST",
headers: {
"X-API-Key": "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
recipient: "+233000000000",
template_id: "18",
text: "Hi Ama, is 2pm tomorrow still good?",
}),
});
console.log(await response.json());<?php
$ch = curl_init('https://smess.io/api/send');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['X-API-Key: SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'],
CURLOPT_POSTFIELDS => http_build_query([
'recipient' => '+233000000000',
'template_id' => '18',
'text' => 'Hi Ama, is 2pm tomorrow still good?',
]),
]);
$response = curl_exec($ch);
curl_close($ch);
print_r(json_decode($response, true));// Java 17+ (java.net.http, text blocks)
HttpClient client = HttpClient.newHttpClient();
String body = "recipient=%2B233000000000&template_id=18&text=Hi+Ama%2C+is+2pm+tomorrow+still+good%3F";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://smess.io/api/send"))
.header("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());package main
import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
)
func main() {
form := url.Values{}
form.Set("recipient", "+233000000000")
form.Set("template_id", "18")
form.Set("text", "Hi Ama, is 2pm tomorrow still good?")
req, _ := http.NewRequest("POST", "https://smess.io/api/send", strings.NewReader(form.Encode()))
req.Header.Set("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
}using var client = new HttpClient();
var content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["recipient"] = "+233000000000",
["template_id"] = "18",
["text"] = "Hi Ama, is 2pm tomorrow still good?",
});
var request = new HttpRequestMessage(HttpMethod.Post, "https://smess.io/api/send")
{
Content = content,
};
request.Headers.Add("X-API-Key", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
var response = await client.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());require 'net/http'
require 'json'
require 'uri'
uri = URI('https://smess.io/api/send')
request = Net::HTTP::Post.new(uri)
request['X-API-Key'] = 'SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
request.set_form_data(
'recipient' => '+233000000000',
'template_id' => '18',
'text' => 'Hi Ama, is 2pm tomorrow still good?',
)
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(request)
end
puts JSON.parse(response.body)Your text replaces the template's body; the template's buttons still ride along.
{
"success": true,
"message": "Message queued successfully. It will be sent by the queue worker.",
"data": {
"queue_id": 42,
"recipient": "+233000000000",
"message_type": "buttons",
"status": "queued",
"template_id": 18
}
}
A template_id that cannot be used is always an error — it never falls through to an empty message.
| HTTP | error_code | Meaning |
|---|---|---|
| 404 | TEMPLATE_NOT_FOUND | That ID is neither one of your own templates nor a SMESS Library template. Another client's private template is invisible to you, so their ID simply does not exist for your account. |
| 404 | TEMPLATE_INACTIVE | The template exists but is switched off. Switch one of your own back on from the Templates page; for a SMESS Library template, ask us. |
| 400 | TEMPLATE_INVALID | variables was not a JSON object, or the template resolved to no sendable content. |
{
"success": false,
"error": "Template not found for this account.",
"error_code": "TEMPLATE_NOT_FOUND",
"code": 404
}
For campaigns, submit many messages in one call with POST /api/bulk Premium plan and above. The queue worker paces delivery automatically (smart-sending gaps and caps still apply per recipient).
curl -X POST https://smess.io/api/bulk \
-d "apikey=SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
-d 'messages=[{"recipient":"+233000000000","text":"Hello Ama!"},{"recipient":"+233000000000","text":"Hello Kojo!","document":"https://example.com/invoice.pdf"}]'import requests
import json
response = requests.post(
"https://smess.io/api/bulk",
data={
"apikey": "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"messages": json.dumps([
{
"recipient": "+233000000000",
"text": "Hello Ama!"
},
{
"recipient": "+233000000000",
"text": "Hello Kojo!",
"document": "https://example.com/invoice.pdf"
}
]),
},
)
print(response.json())// Node.js 18+ — fetch is built in. Run this server-side only;
// never ship your API key to a browser.
const response = await fetch("https://smess.io/api/bulk", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
apikey: "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
messages: JSON.stringify([
{
"recipient": "+233000000000",
"text": "Hello Ama!"
},
{
"recipient": "+233000000000",
"text": "Hello Kojo!",
"document": "https://example.com/invoice.pdf"
}
]),
}),
});
console.log(await response.json());<?php
$ch = curl_init('https://smess.io/api/bulk');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => http_build_query([
'apikey' => 'SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
'messages' => json_encode([
[
'recipient' => '+233000000000',
'text' => 'Hello Ama!'
],
[
'recipient' => '+233000000000',
'text' => 'Hello Kojo!',
'document' => 'https://example.com/invoice.pdf'
]
]),
]),
]);
$response = curl_exec($ch);
curl_close($ch);
print_r(json_decode($response, true));// Java 17+ (java.net.http, text blocks)
HttpClient client = HttpClient.newHttpClient();
String body = "apikey=SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&messages=%5B%7B%22recipient%22%3A%22%2B233000000000%22%2C%22text%22%3A%22Hello+Ama%21%22%7D%2C%7B%22recipient%22%3A%22%2B233000000000%22%2C%22text%22%3A%22Hello+Kojo%21%22%2C%22document%22%3A%22https%3A%2F%2Fexample.com%2Finvoice.pdf%22%7D%5D";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://smess.io/api/bulk"))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());package main
import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
)
func main() {
form := url.Values{}
form.Set("apikey", "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
form.Set("messages", `[{"recipient":"+233000000000","text":"Hello Ama!"},{"recipient":"+233000000000","text":"Hello Kojo!","document":"https://example.com/invoice.pdf"}]`)
req, _ := http.NewRequest("POST", "https://smess.io/api/bulk", strings.NewReader(form.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
}using var client = new HttpClient();
var content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["apikey"] = "SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
["messages"] = @"[{""recipient"":""+233000000000"",""text"":""Hello Ama!""},{""recipient"":""+233000000000"",""text"":""Hello Kojo!"",""document"":""https://example.com/invoice.pdf""}]",
});
var request = new HttpRequestMessage(HttpMethod.Post, "https://smess.io/api/bulk")
{
Content = content,
};
var response = await client.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());require 'net/http'
require 'json'
require 'uri'
uri = URI('https://smess.io/api/bulk')
request = Net::HTTP::Post.new(uri)
request.set_form_data(
'apikey' => 'SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
'messages' => [
{
'recipient' => '+233000000000',
'text' => 'Hello Ama!'
},
{
'recipient' => '+233000000000',
'text' => 'Hello Kojo!',
'document' => 'https://example.com/invoice.pdf'
}
].to_json,
)
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(request)
end
puts JSON.parse(response.body)| Parameter | Type | Description |
|---|---|---|
| messages | JSON array | Array of {recipient, text, document} objects — each may carry media_id (an uploaded file) instead of document. Alternative: upload a CSV file as csv_file with header recipient, text, document, media_id. |
| media_id | optional | One uploaded file attached to every message in the batch that does not name its own media_id. Upload once with POST /api/media; SMESS destroys it after the whole batch has gone out. |
| campaign_id | optional | Your identifier for the batch — returned in the response and attached to every queued message. Auto-generated if omitted. |
| scheduled_at | optional | YYYY-MM-DD HH:MM:SS — queue now, deliver later. Great for campaigns timed to business hours. |
| priority | optional | Defaults to bulk priority (paced slower than transactional sends so they never block OTPs or receipts). |
Response:
{
"success": true,
"queued": 2,
"total_submitted": 2,
"campaign_id": "spring-promo-01",
"message": "2 messages queued for delivery"
}
errors array while the rest queue normally. Delivery outcomes arrive per message via Delivery Webhooks or the Queue page.
After queuing, track message status in the client portal or via the message detail API:
| Resource | URL | Purpose |
|---|---|---|
| Queue | /my-queue | Pending / processing / sent / failed messages |
| Message History | /my-messages | Delivered messages and delivery receipts |
queue_id returned in the 202 response and use it to correlate with status updates in your dashboard or webhooks.
Prefer push over polling? Set up a delivery-status webhook yourself in API Keys → Delivery Webhook: save your HTTPS endpoint, generate a signing secret, and hit Send test event to confirm your endpoint verifies correctly. SMESS then POSTs a JSON event whenever one of your messages reaches a final state:
{
"event": "message.status",
"queue_id": 12345,
"status": "sent",
"recipient": "233000000000",
"error": null,
"attempts": 1,
"timestamp": "2026-07-24T15:04:05+00:00"
}
| Detail | Behaviour |
|---|---|
| status | sent or failed — on failure, error carries the reason (delivery error, spam-rule block, recipient cap, …) |
| Signature | Every request carries X-SMESS-Signature: sha256=<hex> — the HMAC-SHA256 of the raw request body using your signing secret. Strip the sha256= prefix, hash the raw bytes before JSON parsing, and use a constant-time comparison — those three details cause most 401s. |
| Two credentials | Your API key (SM-…) authenticates sends; the signing secret (whsec_…) verifies webhooks and is never transmitted. Keep them separate — signing with the API key means rotating it silently breaks your webhooks. |
| Rotating safely | A new secret replaces the old one immediately and is shown only once. Deploy verification that accepts a list of secrets first, then rotate, confirm with Send test event, and drop the old value — otherwise events fail during the gap and are not replayed. |
| Delivery | 5-second timeout, one automatic retry, best-effort — a webhook outage never delays your messages. Respond with any 2xx quickly; process async. |
| Held messages | Gap/cap delays don't fire events — only final sent / failed outcomes do. Correlate using the queue_id from your send response. |
The API returns JSON error responses with an HTTP status code and an error_code when available:
| 400 | Bad Request — missing or invalid parameters (e.g., wrong phone format), or a template that produced nothing sendable (TEMPLATE_INVALID) |
| 401 | Unauthorized — API key missing or invalid (INVALID_KEY) |
| 402 | Payment Required — quota exceeded (QUOTA_EXCEEDED), trial allowance used up (TRIAL_QUOTA_EXCEEDED), or subscription/trial expired |
| 403 | Forbidden — key inactive/revoked (KEY_INACTIVE) or account suspended |
| 404 | Not Found — template_id is neither one of your own templates nor a SMESS Library one (TEMPLATE_NOT_FOUND), or it is switched off (TEMPLATE_INACTIVE) — see Message Templates; or media_id is not one of your uploads (MEDIA_NOT_FOUND) |
| 410 | Gone — the uploaded file behind media_id has already been destroyed (MEDIA_PURGED; the message says why) — upload it again, see Upload the file |
| 413 | Payload Too Large — media file or upload exceeds your plan limit (FILE_TOO_LARGE) |
| 429 | Too Many Requests — per-minute rate limit (RATE_LIMITED), per-recipient cooldown (OTP_COOLDOWN), or too many not-yet-destroyed uploads (STORAGE_QUOTA_EXCEEDED) |
| 503 | Service Unavailable — WhatsApp connection not ready (WHATSAPP_DISCONNECTED / NOT_ACTIVATED), or file uploads switched off (MEDIA_UPLOAD_DISABLED) |
{
"success": false,
"error": "Invalid API key",
"error_code": "INVALID_KEY",
"code": 401
}
Limits are per API key and depend on your plan:
| Limit | Starter & Standard | Premium | Ultimate |
|---|---|---|---|
| API submissions / minute | 100 | 300 | 500 |
| Attachment max size (URL or upload) | 25 MB | 50 MB | 50 MB |
| Per-recipient minimum gap | 60 seconds (configurable 0–600s per key) | ||
| Per-recipient daily cap | 3 / day by default — a safety limit, not a plan feature. Email support@smess.io to raise it for a real campaign or alert stream. | ||
priority=bulk and submit via POST /api/bulk (Premium plan and above). The queue worker spaces out delivery automatically.
Common mistakes that cause API errors:
The server did not receive a valid key. Common causes:
| ❌ Wrong (will fail) | ✅ Correct |
|---|---|
api_key (with underscore) | apikey |
key or token | apikey / X-API-Key / Authorization: Bearer |
Key nested inside another object, e.g. {"auth":{"apikey":"…"}} | A top-level apikey field — JSON bodies (Content-Type: application/json) are fully supported, but only top-level fields are read |
Query string ?apikey=... | POST field or header (query string is deprecated and logged) |
| ❌ Wrong (will fail) | ✅ Correct |
|---|---|
phone, number, to | recipient |
message, msg, body | text |
image, photo | file (images) or document (any file) |
curl -X POST https://smess.io/api/send \
-H "X-API-Key: SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
-d "recipient=+233000000000" \
-d "text=Hello, this works!"
# WRONG - these parameter names are NOT accepted
curl -X POST https://smess.io/api/send \
-H "Content-Type: application/json" \
-d '{"api_key":"SM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","phone":"+233000000000","message":"This will fail"}'