Advanced bulk
Beyond simple sends, bulk messaging supports per-recipient customization, mixed channels, scheduling, webhooks, and detailed status tracking and analytics. This page covers those advanced features and common errors. For the basics, start with the Bulk messaging overview.
Individual customization
Personalize each recipient's message with its own body and template_params while sharing a single template_id.
{
"messages": [
{
"to": "+233593152134",
"body": "Hi John, your order #12345 has been shipped!",
"template_params": {
"customer_name": "John",
"order_number": "12345",
"tracking_url": "https://track.com/12345"
}
},
{
"to": "+233593152135",
"body": "Hi Sarah, your order #12346 has been shipped!",
"template_params": {
"customer_name": "Sarah",
"order_number": "12346",
"tracking_url": "https://track.com/12346"
}
}
],
"template_id": "12345678912345",
"preferred_channels": ["whatsapp", "sms"]
}
Mixed channels
Set preferred_channels per message to route recipients to different channels based on their preference. Combine with a top-level fallback_enabled for resilience.
{
"messages": [
{
"to": "+233593152134",
"body": "Your appointment reminder",
"template_params": {
"appointment_time": "2:00 PM",
"location": "Main Office"
},
"preferred_channels": ["whatsapp"]
},
{
"to": "+233593152135",
"body": "Your appointment reminder",
"template_params": {
"appointment_time": "3:00 PM",
"location": "Branch Office"
},
"preferred_channels": ["sms"]
}
],
"template_id": "12345678912345",
"fallback_enabled": true
}
Scheduling
Schedule a bulk send for future delivery with scheduled_for (ISO 8601 timestamp).
{
"messages": [
{"to": "+233593152134", "body": "Happy Birthday! 🎉"},
{"to": "+233593152135", "body": "Happy Birthday! 🎉"},
{"to": "+233593152136", "body": "Happy Birthday! 🎉"}
],
"preferred_channels": ["whatsapp", "sms"],
"scheduled_for": "2024-01-15T09:00:00Z"
}
Webhooks
Receive delivery status updates for all messages in the batch by setting webhook_url. Zend posts status changes to that endpoint as they occur.
{
"messages": [
{"to": "+233593152134", "body": "System maintenance notification"},
{"to": "+233593152135", "body": "System maintenance notification"},
{"to": "+233593152136", "body": "System maintenance notification"}
],
"preferred_channels": ["sms"],
"webhook_url": "https://yourapp.com/webhooks/bulk-status"
}
Advanced request fields
Status tracking
Check bulk status
Retrieve aggregate progress for a batch.
curl "https://api.tryzend.com/messages?bulk_id=bulk_1705312345678" \
-H "x-api-key: YOUR_API_KEY"
Response
{
"id": "bulk_1705312345678",
"status": "processing",
"total_recipients": 1000,
"sent": 750,
"delivered": 720,
"failed": 30,
"pending": 250,
"total_cost": 15.20,
"created_at": "2024-01-15T10:00:00Z",
"estimated_completion": "2024-01-15T10:15:00Z"
}
Get individual message statuses
Page through the individual messages in a batch with limit and offset.
curl "https://api.tryzend.com/messages?bulk_id=bulk_1705312345678&limit=50&offset=0" \
-H "x-api-key: YOUR_API_KEY"
Response
{
"messages": [
{
"id": "6884da240f0e633b7b979bff",
"status": "delivered",
"channel_used": "whatsapp",
"to": "+233593152134",
"total_cost": 0.008,
"created_at": "2024-01-15T10:00:05Z"
}
],
"total": 1000,
"page": 1,
"pages": 20
}
Batching & rate limiting
For large sends, split recipients into batches and space them out to stay within the 1,000 messages-per-hour rate limit.
// Optimal batch sizes
const optimalBatchSizes = {
sms: 1000, // SMS: 1000 per batch
whatsapp: 500, // WhatsApp: 500 per batch
mixed: 750 // Mixed channels: 750 per batch
};
// Implement rate limiting for large batches
const sendBulkWithRateLimit = async (messages, batchSize = 500) => {
const batches = [];
for (let i = 0; i < messages.length; i += batchSize) {
batches.push(messages.slice(i, i + batchSize));
}
for (const batch of batches) {
await sendBulkMessage(batch);
// Wait 1 minute between batches
await new Promise(resolve => setTimeout(resolve, 60000));
}
};
Progress monitoring
Poll the status endpoint until the batch completes.
const monitorBulkProgress = async (bulkId) => {
const checkStatus = async () => {
const response = await fetch(`https://api.tryzend.com/messages?bulk_id=${bulkId}`, {
headers: { 'x-api-key': 'YOUR_API_KEY' }
});
const status = await response.json();
console.log(`Progress: ${status.sent}/${status.total_recipients} sent`);
if (status.status === 'completed') {
console.log('Bulk send completed!');
return;
}
// Check again in 30 seconds
setTimeout(checkStatus, 30000);
};
checkStatus();
};
Analytics
Get bulk statistics
Retrieve delivery, channel, cost, and delivery-time breakdowns for a batch.
curl "https://api.tryzend.com/analytics/overview" \
-H "x-api-key: YOUR_API_KEY"
Response
{
"bulk_id": "bulk_1705312345678",
"total_recipients": 1000,
"delivery_stats": {
"sent": 950,
"delivered": 920,
"failed": 30,
"pending": 50
},
"channel_stats": {
"whatsapp": {
"sent": 600,
"delivered": 580,
"failed": 20
},
"sms": {
"sent": 350,
"delivered": 340,
"failed": 10
}
},
"cost_breakdown": {
"whatsapp": 4.64,
"sms": 6.80,
"total": 11.44
},
"delivery_times": {
"average": 45,
"median": 32,
"p95": 120
}
}
Errors
Insufficient credits
{
"statusCode": 400,
"message": "Insufficient credits for bulk send. Required: 20.000, Available: 15.500. Please purchase more credits.",
"error": "Bad Request"
}
Invalid recipients
{
"statusCode": 400,
"message": "Invalid phone numbers found: +233INVALID, +233INVALID2. Please fix and retry.",
"error": "Bad Request"
}
Template errors
{
"statusCode": 400,
"message": "Template 'welcome_template' not found or not accessible",
"error": "Bad Request"
}
Note
Next steps
- Bulk messaging overview — basic bulk SMS and WhatsApp sends, limits, and best practices.
- Templates and Variables & variants — the templates and variables used in bulk sends.