Skip to main content

Bulk SMS Campaign

Send marketing campaigns to thousands of recipients efficiently with progress tracking and delivery reports.

What You'll Build​

  • ✅ Load recipients from CSV file
  • ✅ Send personalized bulk SMS
  • ✅ Track progress in real-time
  • ✅ Handle errors and retries
  • ✅ Generate delivery reports
  • ✅ Calculate costs

Estimated Time: 25 minutes

Prerequisites​

  • ✅ AfriRoute Account with SMS credits
  • ✅ Python 3.8+ or Node.js 18+
  • ✅ CSV file with recipients

Step 1: Prepare CSV File​

recipients.csv
name,phone,language
John Doe,+254700123456,en
Jane Smith,+254711234567,sw
Bob Johnson,+255700111222,en

Step 2: Bulk SMS Sender (Python)​

bulk_sms.py
import csv
import time
from afriroute import AfriRoute
from dotenv import load_dotenv
import os

load_dotenv()

client = AfriRoute(
api_key=os.getenv('AFRIROUTE_API_KEY'),
api_secret=os.getenv('AFRIROUTE_API_SECRET')
)

class BulkSMSSender:
def __init__(self, csv_file, batch_size=100):
self.csv_file = csv_file
self.batch_size = batch_size
self.results = []

def load_recipients(self):
"""Load recipients from CSV"""
recipients = []
with open(self.csv_file, 'r') as file:
reader = csv.DictReader(file)
for row in reader:
recipients.append(row)

print(f"✅ Loaded {len(recipients)} recipients")
return recipients

def send_batch(self, recipients, message_template):
"""Send SMS to batch of recipients"""
sent = 0
failed = 0
total_cost = 0

for i, recipient in enumerate(recipients, 1):
try:
# Personalize message
message = message_template.format(
name=recipient['name'],
language=recipient.get('language', 'en')
)

# Send SMS
response = client.sms.send(
to=recipient['phone'],
message=message,
sender_id="PROMO"
)

self.results.append({
'phone': recipient['phone'],
'name': recipient['name'],
'status': 'sent',
'message_id': response['message_id'],
'cost': response['cost']
})

sent += 1
total_cost += response['cost']

# Progress bar
self.print_progress(i, len(recipients))

# Rate limiting (10 per second)
time.sleep(0.1)

except Exception as e:
self.results.append({
'phone': recipient['phone'],
'name': recipient['name'],
'status': 'failed',
'error': str(e)
})
failed += 1

return {
'sent': sent,
'failed': failed,
'total_cost': total_cost
}

def print_progress(self, current, total):
"""Print progress bar"""
percent = (current / total) * 100
bar_length = 40
filled = int(bar_length * current // total)
bar = '█' * filled + '-' * (bar_length - filled)
print(f'\r[💬 {bar}] {percent:.1f}% ({current}/{total})', end='', flush=True)

def generate_report(self):
"""Generate delivery report"""
total = len(self.results)
sent = sum(1 for r in self.results if r['status'] == 'sent')
failed = total - sent
total_cost = sum(r.get('cost', 0) for r in self.results if r['status'] == 'sent')

report = f"""
┌────────────────────────────────────────┐
│ 📊 BULK SMS REPORT │
├────────────────────────────────────────┤
│ Total Recipients: {total:>15} │
│ ✅ Sent: {sent:>24} │
│ ❌ Failed: {failed:>21} │
│ 💰 Total Cost: ${total_cost:>17.2f} │
│ 📈 Success Rate: {(sent/total*100):>13.1f}% │
└────────────────────────────────────────┘
"""

print(report)

# Save to file
with open('bulk_sms_report.csv', 'w', newline='') as file:
writer = csv.DictWriter(file, fieldnames=['phone', 'name', 'status', 'message_id', 'error'])
writer.writeheader()
writer.writerows(self.results)

print("💾 Report saved to: bulk_sms_report.csv")

if __name__ == '__main__':
# Initialize sender
sender = BulkSMSSender('recipients.csv')

# Load recipients
recipients = sender.load_recipients()

# Message template
message = """Hi {name}! 🎉

Special offer: Get 20% OFF on all products this week!

Use code: SAVE20

Shop now: https://shop.example.com

Reply STOP to unsubscribe."""

# Send bulk SMS
print("\n🚀 Starting bulk SMS campaign...\n")
summary = sender.send_batch(recipients, message)

# Generate report
print("\n")
sender.generate_report()

Step 3: Node.js Implementation​

bulk-sms.js
const fs = require('fs');
const csv = require('csv-parser');
const { AfriRoute } = require('afriroute-sdk');
require('dotenv').config();

const client = new AfriRoute({
apiKey: process.env.AFRIROUTE_API_KEY,
apiSecret: process.env.AFRIROUTE_API_SECRET
});

class BulkSMSSender {
constructor(csvFile, batchSize = 100) {
this.csvFile = csvFile;
this.batchSize = batchSize;
this.results = [];
}

async loadRecipients() {
const recipients = [];

return new Promise((resolve, reject) => {
fs.createReadStream(this.csvFile)
.pipe(csv())
.on('data', (row) => recipients.push(row))
.on('end', () => {
console.log(`✅ Loaded ${recipients.length} recipients`);
resolve(recipients);
})
.on('error', reject);
});
}

async sendBatch(recipients, messageTemplate) {
let sent = 0;
let failed = 0;
let totalCost = 0;

for (let i = 0; i < recipients.length; i++) {
const recipient = recipients[i];

try {
const message = messageTemplate
.replace('{name}', recipient.name)
.replace('{language}', recipient.language || 'en');

const response = await client.sms.send({
to: recipient.phone,
message: message,
sender_id: 'PROMO'
});

this.results.push({
phone: recipient.phone,
name: recipient.name,
status: 'sent',
message_id: response.message_id,
cost: response.cost
});

sent++;
totalCost += response.cost;

this.printProgress(i + 1, recipients.length);

// Rate limiting
await new Promise(resolve => setTimeout(resolve, 100));

} catch (error) {
this.results.push({
phone: recipient.phone,
name: recipient.name,
status: 'failed',
error: error.message
});
failed++;
}
}

return { sent, failed, totalCost };
}

printProgress(current, total) {
const percent = (current / total) * 100;
const filled = Math.floor(40 * current / total);
const bar = '█'.repeat(filled) + '-'.repeat(40 - filled);
process.stdout.write(`\r[💬 ${bar}] ${percent.toFixed(1)}% (${current}/${total})`);
}

generateReport() {
const total = this.results.length;
const sent = this.results.filter(r => r.status === 'sent').length;
const failed = total - sent;
const totalCost = this.results
.filter(r => r.status === 'sent')
.reduce((sum, r) => sum + r.cost, 0);

console.log(`\n\n┌${────────────────────────────────────────}┐`);
console.log(`│ 📊 BULK SMS REPORT │`);
console.log(`├${────────────────────────────────────────}┤`);
console.log(`│ Total: ${total.toString().padStart(26)} │`);
console.log(`│ ✅ Sent: ${sent.toString().padStart(25)} │`);
console.log(`│ ❌ Failed: ${failed.toString().padStart(22)} │`);
console.log(`│ 💰 Cost: $${totalCost.toFixed(2).padStart(22)} │`);
console.log(`└${────────────────────────────────────────}┘`);
}
}

async function main() {
const sender = new BulkSMSSender('recipients.csv');

const recipients = await sender.loadRecipients();

const message = `Hi {name}! 🎉\n\nSpecial offer: 20% OFF!\n\nCode: SAVE20\n\nReply STOP to unsubscribe.`;

console.log('\n🚀 Starting campaign...\n');
await sender.sendBatch(recipients, message);

sender.generateReport();
}

main();

Best Practices​

1. Segmentation​

# Group by language, location, or preference
def segment_recipients(recipients):
by_language = {}
for recipient in recipients:
lang = recipient.get('language', 'en')
if lang not in by_language:
by_language[lang] = []
by_language[lang].append(recipient)
return by_language

2. Personalization​

message = "Hi {name}, special offer in {city}: {offer}"

3. Opt-out Management​

# Always include unsubscribe option
footer = "\n\nReply STOP to unsubscribe"
message += footer

4. Cost Estimation​

def estimate_cost(recipients, message):
segments = (len(message) + 159) // 160
cost_per_sms = 0.03
return len(recipients) * segments * cost_per_sms

Troubleshooting​

  • Rate Limits: Add delays between requests
  • CSV Errors: Validate CSV format before sending
  • Failed Messages: Implement retry logic
  • Cost Tracking: Monitor spending in dashboard

Next Steps​

Pro Tip

Test with small batches first and always include opt-out options!