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!