SMS
Send SMS messages via the SMPP channel. Two sending patterns are available:
- Transactional — Create a long-lived campaign, then add messages one at a time (OTP, notifications, alerts)
- Promotional — Send to many recipients in a single request (marketing, offers, newsletters)
Your company must have an approved SMS Sender ID before sending SMS messages. This is the alphanumeric name (max 11 characters, e.g., "MyBrand") that appears as the sender on recipients' phones. Register SMS senders in Dashboard → Channels → SMS — they require administrator approval before use. You can also list your approved senders via GET /api/v1/sms-senders.
When using the External API, set smsSenderId on your SMS template at creation time. The sender ID flows from the template to campaigns automatically.
Create Transactional Campaign
Create a long-lived SMS campaign for transactional messages. The campaign stays in InProgress status and accepts messages indefinitely.
POST /api/v1/sms/campaigns
Request Body
{
"name": "Order Notifications",
"description": "Real-time order status updates",
"templateId": "550e8400-e29b-41d4-a716-446655440000"
}
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Campaign name (max 200 chars) |
| description | string | No | Campaign description (max 1000 chars) |
| templateId | UUID | Yes | ID of an SMS template |
The template must be an SMS (SMPP) template. Viber templates will be rejected with a 400 error.
Response
{
"campaignId": "660e8400-e29b-41d4-a716-446655440001",
"name": "Order Notifications",
"status": "InProgress",
"createdAt": "2024-01-15T10:30:00Z"
}
Example
- cURL
- JavaScript
- Python
curl -X POST https://api.transformify.mk/api/v1/sms/campaigns \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"name": "Order Notifications",
"description": "Real-time order status updates",
"templateId": "550e8400-e29b-41d4-a716-446655440000"
}'
const response = await fetch('https://api.transformify.mk/api/v1/sms/campaigns', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'Order Notifications',
description: 'Real-time order status updates',
templateId: '550e8400-e29b-41d4-a716-446655440000'
})
});
const campaign = await response.json();
import requests
response = requests.post(
'https://api.transformify.mk/api/v1/sms/campaigns',
headers={
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
json={
'name': 'Order Notifications',
'description': 'Real-time order status updates',
'templateId': '550e8400-e29b-41d4-a716-446655440000'
}
)
campaign = response.json()
Add Message to Campaign
Add a single message to a transactional SMS campaign. The message is queued for immediate processing.
POST /api/v1/sms/campaigns/{id}/messages
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| id | UUID | Campaign ID |
Request Body
{
"phoneNumber": "38970123456",
"externalUserId": "order_12345",
"placeholders": {
"orderId": "ORD-12345",
"status": "Shipped"
}
}
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| phoneNumber | string | Yes | Recipient phone number (optional + prefix, 10-15 digits; regex: ^\+?\d{10,15}$) |
| externalUserId | string | No | Your identifier for tracking |
| placeholders | object | No | Template substitution values |
Response
{
"messageId": "770e8400-e29b-41d4-a716-446655440002",
"campaignId": "660e8400-e29b-41d4-a716-446655440001",
"phoneNumber": "38970123456",
"status": "Pending",
"queuedAt": "2024-01-15T10:31:00Z"
}
Messages progress through these statuses: Pending -> Sending -> Sent -> Delivered or Failed.
Example
- cURL
- JavaScript
- Python
curl -X POST https://api.transformify.mk/api/v1/sms/campaigns/660e8400-e29b-41d4-a716-446655440001/messages \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"phoneNumber": "38970123456",
"externalUserId": "order_12345",
"placeholders": {
"orderId": "ORD-12345",
"status": "Shipped"
}
}'
const campaignId = '660e8400-e29b-41d4-a716-446655440001';
const response = await fetch(
`https://api.transformify.mk/api/v1/sms/campaigns/${campaignId}/messages`,
{
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
phoneNumber: '38970123456',
externalUserId: 'order_12345',
placeholders: {
orderId: 'ORD-12345',
status: 'Shipped'
}
})
}
);
const message = await response.json();
import requests
campaign_id = '660e8400-e29b-41d4-a716-446655440001'
response = requests.post(
f'https://api.transformify.mk/api/v1/sms/campaigns/{campaign_id}/messages',
headers={
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
json={
'phoneNumber': '38970123456',
'externalUserId': 'order_12345',
'placeholders': {
'orderId': 'ORD-12345',
'status': 'Shipped'
}
}
)
message = response.json()
Send Promotional SMS
Send bulk promotional SMS messages to a list of recipients in a single request. Returns immediately while messages are prepared asynchronously. Once the campaign reaches Ready status, call POST /api/v1/campaigns/{id}/start to begin delivery.
POST /api/v1/sms/promotional
Request Body
{
"templateId": "550e8400-e29b-41d4-a716-446655440000",
"recipients": [
{
"phoneNumber": "38970111111",
"externalUserId": "customer_001",
"placeholders": { "name": "John", "promoCode": "JOHN20" }
},
{
"phoneNumber": "38970222222",
"placeholders": { "name": "Jane", "promoCode": "JANE20" }
}
],
"placeholders": {
"companyName": "Acme Corp"
}
}
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| templateId | UUID | Yes | ID of an SMS template |
| recipients | array | Yes | List of recipients |
| recipients[].phoneNumber | string | Yes | Phone number (optional + prefix, 10-15 digits; regex: ^\+?\d{10,15}$) |
| recipients[].externalUserId | string | No | Your identifier for tracking |
| recipients[].placeholders | object | No | Per-recipient template placeholders |
| placeholders | object | No | Default placeholders (fallback for recipients without their own) |
The template must be an SMS (SMPP) template. Viber templates will be rejected with a 400 error.
Response
{
"campaignId": "660e8400-e29b-41d4-a716-446655440001",
"messageCount": 50000,
"status": "Queuing",
"queuedAt": "2024-01-15T10:30:00Z",
"message": "Campaign accepted. 50000 recipients are being queued for insertion. Poll GET /api/v1/campaigns/{id} for status, then call POST /api/v1/campaigns/{id}/start when Ready."
}
Async Flow
For large sends, the API returns 202 Accepted immediately while messages are prepared in the background. Follow this three-step flow:
Step 1 — Send (POST /api/v1/sms/promotional)
Returns 202 with status: "Queuing" and a campaignId.
Step 2 — Poll until Ready (GET /api/v1/campaigns/{id})
Poll the campaign until status changes from Queuing → Ready.
Step 3 — Start delivery (POST /api/v1/campaigns/{id}/start)
When the campaign is Ready, call this endpoint to begin message delivery. The status moves to InProgress.
POST /api/v1/sms/promotional → 202, campaignId, status: "Queuing"
GET /api/v1/campaigns/{id} → poll until status: "Ready"
POST /api/v1/campaigns/{id}/start → status: "InProgress", delivery begins
Example
- cURL
- JavaScript
- Python
curl -X POST https://api.transformify.mk/api/v1/sms/promotional \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"templateId": "550e8400-e29b-41d4-a716-446655440000",
"recipients": [
{
"phoneNumber": "38970111111",
"placeholders": { "name": "John", "promoCode": "JOHN20" }
},
{
"phoneNumber": "38970222222",
"placeholders": { "name": "Jane", "promoCode": "JANE20" }
}
],
"placeholders": { "companyName": "Acme Corp" }
}'
const response = await fetch('https://api.transformify.mk/api/v1/sms/promotional', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
templateId: '550e8400-e29b-41d4-a716-446655440000',
recipients: [
{
phoneNumber: '38970111111',
placeholders: { name: 'John', promoCode: 'JOHN20' }
},
{
phoneNumber: '38970222222',
placeholders: { name: 'Jane', promoCode: 'JANE20' }
}
],
placeholders: { companyName: 'Acme Corp' }
})
});
const data = await response.json();
import requests
response = requests.post(
'https://api.transformify.mk/api/v1/sms/promotional',
headers={
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
json={
'templateId': '550e8400-e29b-41d4-a716-446655440000',
'recipients': [
{
'phoneNumber': '38970111111',
'placeholders': {'name': 'John', 'promoCode': 'JOHN20'}
},
{
'phoneNumber': '38970222222',
'placeholders': {'name': 'Jane', 'promoCode': 'JANE20'}
}
],
'placeholders': {'companyName': 'Acme Corp'}
}
)
data = response.json()
Send SMS
Send SMS messages without creating a template. Provide raw message content, an approved SMS Sender ID, and a list of recipients.
POST /api/v1/sms/send
Request Body
{
"messageContent": "Your order #12345 has been shipped!",
"smsSenderId": 1,
"recipients": [
{
"phoneNumber": "38970123456",
"externalUserId": "customer_001"
},
{
"phoneNumber": "38970123457"
}
]
}
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| messageContent | string | Yes | SMS message text (1-1,600 characters, no placeholders) |
| smsSenderId | integer | Yes | ID of an approved SMS Sender (see GET /api/v1/sms-senders) |
| recipients | array | Yes | List of recipients |
| recipients[].phoneNumber | string | Yes | Phone number (optional + prefix, 10-15 digits) |
| recipients[].externalUserId | string | No | Your identifier for tracking (max 100 chars) |
The SMS Sender must belong to your company and have Approved status. Use GET /api/v1/sms-senders to list your available senders.
Response
{
"campaignId": "660e8400-e29b-41d4-a716-446655440001",
"messageCount": 2,
"skippedCount": 0,
"invalidPhoneNumbers": [],
"status": "Pending",
"queuedAt": "2024-01-15T10:30:00Z",
"message": "2 messages queued for delivery."
}
Response Fields
| Field | Type | Description |
|---|---|---|
| campaignId | UUID | Campaign identifier for tracking |
| messageCount | integer | Number of valid recipients accepted |
| skippedCount | integer | Number of invalid phone numbers skipped |
| invalidPhoneNumbers | array | List of skipped invalid phone numbers |
| status | string | Always Pending — messages are queued for immediate processing |
| queuedAt | datetime | When the request was accepted |
| message | string | Summary including count of any skipped numbers |
Example
- cURL
- JavaScript
- Python
curl -X POST https://api.transformify.mk/api/v1/sms/send \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"messageContent": "Your order #12345 has been shipped!",
"smsSenderId": 1,
"recipients": [
{ "phoneNumber": "38970123456", "externalUserId": "customer_001" },
{ "phoneNumber": "38970123457" }
]
}'
const response = await fetch('https://api.transformify.mk/api/v1/sms/send', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
messageContent: 'Your order #12345 has been shipped!',
smsSenderId: 1,
recipients: [
{ phoneNumber: '38970123456', externalUserId: 'customer_001' },
{ phoneNumber: '38970123457' }
]
})
});
const data = await response.json();
import requests
response = requests.post(
'https://api.transformify.mk/api/v1/sms/send',
headers={
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
json={
'messageContent': 'Your order #12345 has been shipped!',
'smsSenderId': 1,
'recipients': [
{'phoneNumber': '38970123456', 'externalUserId': 'customer_001'},
{'phoneNumber': '38970123457'}
]
}
)
data = response.json()
Errors
| Status | Error | Description |
|---|---|---|
| 400 | Invalid template channel type | Template is not an SMS (SMPP) template |
| 400 | No valid phone numbers | All recipient phone numbers are invalid |
| 400 | Too many recipients | Exceeds maximum recipients per request |
| 400 | Campaign is not active | Campaign status is not InProgress |
| 400 | Cannot add messages to bulk campaign | Campaign is not transactional |
| 400 | Campaign is not an SMS campaign | Campaign channel type is not SMPP |
| 400 | Invalid SMS Sender | Sender ID not found, not approved, or wrong company (direct send) |
| 401 | Unauthorized | Invalid API key |
| 404 | Not Found | Template or campaign not found |
| 429 | Too many pending messages | Pending message capacity exceeded |