Campaigns, SMS Merge & Outbox
Dynamic variable templating, pre-flight cost estimation, and campaign lifecycle controls.
When sending personalized marketing broadcasts, message lengths vary per recipient. A dynamic variable like {{amount}} might make message A 145 characters (1 unit) and message B 165 characters (2 units).
Nyota SMS provides a Pre-flight Estimation Engine to calculate exact costs, combined with a Local Outbox that allows you to pause or cancel scheduled broadcasts.
1. Template Variables & Merge Syntax
Templates support placeholders in the format {{variable}} with optional fallback values {{variable|fallback}}.
{{firstName}}or{{first_name}}➔ Contact's First Name{{lastName}}or{{last_name}}➔ Contact's Last Name{{phone}}➔ Recipient Phone Number{{any_custom_key}}➔ Matched againstcustomDataobject properties
Example Template:
Hello {{firstName|Valued Customer}}, your account #{{account_no}} has an outstanding balance of KES {{amount}}. Due: {{due_date}}.2. Pre-Flight Cost Estimation (Dry-Run)
Before launching a campaign, dry-run the template against your audience to compute the exact unit requirements, detect Unicode downgrades, and preview rendered samples.
const estimate = await sms.campaigns.estimate({
template:
"Hello {{firstName|Customer}}, your invoice #{{invoice_no}} of KES {{amount}} is due on {{due_date}}.",
recipients: [
{
phone: "0787654321",
firstName: "John",
customData: {
invoice_no: "INV-102",
amount: "4,500",
due_date: "31st March",
},
},
{
phone: "0712345678",
firstName: "Sarah",
customData: {
invoice_no: "INV-103",
amount: "12,000",
due_date: "15th April",
},
},
],
});
console.log(`Total Units Required: ${estimate.totalUnitsRequired}`);
console.log(`Units Covered by Wallet: ${estimate.unitsCoveredByWallet}`);
console.log(`PAYG Top-up Required: KES ${estimate.estimatedPaygCostKes}`);
console.log("Sample Preview:", estimate.samplePreviews[0].renderedMessage);3. Creating & Scheduling Campaigns
Create a campaign targeting either Contact Groups or a Direct Recipient Payload.
const campaign = await sms.campaigns.create({
name: "Q1 Customer Invoice Notifications",
template:
"Hello {{firstName|Customer}}, your invoice #{{invoice_no}} of KES {{amount}} is ready.",
targetType: "direct_recipients",
recipients: [
{
phone: "0787654321",
firstName: "John",
customData: { invoice_no: "INV-102", amount: "4,500" },
},
],
scheduledFor: new Date("2026-03-31T08:00:00Z"),
requireApproval: true, // Holds campaign in 'pending_approval'
});4. Outbox Lifecycle Controls
Because messages sit in your local Nyota outbox until the scheduled trigger, you have complete control over active broadcasts:
// 1. Approve a pending campaign
await sms.campaigns.approve("campaign_id_123");
// 2. Pause an active or scheduled campaign immediately
await sms.campaigns.pause("campaign_id_123");
// 3. Resume a paused campaign
await sms.campaigns.resume("campaign_id_123");
// 4. Cancel a campaign and purge all remaining un-dispatched outbox items
await sms.campaigns.cancel("campaign_id_123");