Send Bulk Messages
Send messages to multiple recipients at once with variable substitution support.
Endpoint
POST /api/v1/messages/bulkAuthentication
Requires API Key authentication via X-API-Key header.
Rate Limiting
Bulk send endpoint is rate limited to 10 requests per 5 minutes to prevent abuse.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
channel | string | Yes | Message channel: sms, whatsapp, email, or voice |
content | object | Yes | Message content with template variables |
content.text | string | Yes | Message template with placeholders |
recipients | array | Yes | Array of recipient objects (max 1000 per request) |
recipients[].to | string | Yes | Recipient phone/email in E.164 format |
recipients[].* | any | No | Additional fields for template variable substitution |
Template Variables
Use syntax in your message text. Variables are replaced with values from each recipient object.
Example:
json
{
"content": {
"text": "Hi {{name}}, your order {{order_id}} is ready!"
},
"recipients": [
{ "to": "+1234567890", "name": "John", "order_id": "12345" },
{ "to": "+0987654321", "name": "Jane", "order_id": "67890" }
]
}Try It Now
Send Bulk Messages
Your API key is stored locally and never sent to our servers. Get an API key →
Example Requests
Basic Bulk SMS
bash
curl -X POST https://api.yebolink.com/api/v1/messages/bulk \
-H "Content-Type: application/json" \
-H "X-API-Key: ybk_live_your_api_key" \
-d '{
"channel": "sms",
"content": {
"text": "Hi {{name}}, welcome to YeboLink!"
},
"recipients": [
{ "to": "+1234567890", "name": "John" },
{ "to": "+0987654321", "name": "Jane" },
{ "to": "+1122334455", "name": "Bob" }
]
}'javascript
const sendBulkSMS = async (template, recipients) => {
const response = await fetch('https://api.yebolink.com/api/v1/messages/bulk', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.YEBOLINK_API_KEY
},
body: JSON.stringify({
channel: 'sms',
content: {
text: template
},
recipients
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message);
}
return await response.json();
};
// Usage
const result = await sendBulkSMS(
'Hi {{name}}, welcome to YeboLink!',
[
{ to: '+1234567890', name: 'John' },
{ to: '+0987654321', name: 'Jane' },
{ to: '+1122334455', name: 'Bob' }
]
);
console.log(`Queued: ${result.data.queued}, Failed: ${result.data.failed}`);python
import os
import requests
def send_bulk_sms(template: str, recipients: list):
"""Send bulk SMS messages"""
response = requests.post(
'https://api.yebolink.com/api/v1/messages/bulk',
headers={
'X-API-Key': os.environ.get('YEBOLINK_API_KEY')
},
json={
'channel': 'sms',
'content': {
'text': template
},
'recipients': recipients
}
)
response.raise_for_status()
return response.json()
# Usage
result = send_bulk_sms(
'Hi {{name}}, welcome to YeboLink!',
[
{'to': '+1234567890', 'name': 'John'},
{'to': '+0987654321', 'name': 'Jane'},
{'to': '+1122334455', 'name': 'Bob'}
]
)
print(f"Queued: {result['data']['queued']}, Failed: {result['data']['failed']}")php
<?php
function sendBulkSMS($template, $recipients) {
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => 'https://api.yebolink.com/api/v1/messages/bulk',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-API-Key: ' . getenv('YEBOLINK_API_KEY')
],
CURLOPT_POSTFIELDS => json_encode([
'channel' => 'sms',
'content' => [
'text' => $template
],
'recipients' => $recipients
])
]);
$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
if ($httpCode !== 201) {
throw new Exception(json_decode($response)->message);
}
return json_decode($response, true);
}
// Usage
$result = sendBulkSMS(
'Hi {{name}}, welcome to YeboLink!',
[
['to' => '+1234567890', 'name' => 'John'],
['to' => '+0987654321', 'name' => 'Jane'],
['to' => '+1122334455', 'name' => 'Bob']
]
);
echo "Queued: {$result['data']['queued']}, Failed: {$result['data']['failed']}\n";
?>ruby
require 'net/http'
require 'json'
def send_bulk_sms(template, recipients)
uri = URI('https://api.yebolink.com/api/v1/messages/bulk')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path)
request['Content-Type'] = 'application/json'
request['X-API-Key'] = ENV['YEBOLINK_API_KEY']
request.body = {
channel: 'sms',
content: {
text: template
},
recipients: recipients
}.to_json
response = http.request(request)
if response.code.to_i != 201
error = JSON.parse(response.body)
raise "Failed: #{error['message']}"
end
JSON.parse(response.body)
end
# Usage
result = send_bulk_sms(
'Hi {{name}}, welcome to YeboLink!',
[
{ to: '+1234567890', name: 'John' },
{ to: '+0987654321', name: 'Jane' },
{ to: '+1122334455', name: 'Bob' }
]
)
puts "Queued: #{result['data']['queued']}, Failed: #{result['data']['failed']}"go
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
)
type BulkRequest struct {
Channel string `json:"channel"`
Content map[string]string `json:"content"`
Recipients []map[string]interface{} `json:"recipients"`
}
type BulkResponse struct {
Success bool `json:"success"`
Data struct {
Queued int `json:"queued"`
Failed int `json:"failed"`
MessageIDs []string `json:"message_ids"`
} `json:"data"`
}
func sendBulkSMS(template string, recipients []map[string]interface{}) (*BulkResponse, error) {
url := "https://api.yebolink.com/api/v1/messages/bulk"
payload := BulkRequest{
Channel: "sms",
Content: map[string]string{
"text": template,
},
Recipients: recipients,
}
jsonPayload, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonPayload))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-API-Key", os.Getenv("YEBOLINK_API_KEY"))
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result BulkResponse
json.NewDecoder(resp.Body).Decode(&result)
return &result, nil
}
func main() {
result, _ := sendBulkSMS(
"Hi {{name}}, welcome to YeboLink!",
[]map[string]interface{}{
{"to": "+1234567890", "name": "John"},
{"to": "+0987654321", "name": "Jane"},
{"to": "+1122334455", "name": "Bob"},
},
)
fmt.Printf("Queued: %d, Failed: %d\n", result.Data.Queued, result.Data.Failed)
}Personalized Campaign
javascript
const sendPersonalizedCampaign = async () => {
const customers = [
{
to: '+1234567890',
name: 'John Doe',
discount: '20%',
code: 'JOHN20',
expiry: 'Nov 30'
},
{
to: '+0987654321',
name: 'Jane Smith',
discount: '15%',
code: 'JANE15',
expiry: 'Nov 30'
}
];
const result = await sendBulkSMS(
'Hi {{name}}! Get {{discount}} off with code {{code}}. Valid until {{expiry}}. Shop now!',
customers
);
return result;
};python
def send_personalized_campaign():
customers = [
{
'to': '+1234567890',
'name': 'John Doe',
'discount': '20%',
'code': 'JOHN20',
'expiry': 'Nov 30'
},
{
'to': '+0987654321',
'name': 'Jane Smith',
'discount': '15%',
'code': 'JANE15',
'expiry': 'Nov 30'
}
]
result = send_bulk_sms(
'Hi {{name}}! Get {{discount}} off with code {{code}}. Valid until {{expiry}}. Shop now!',
customers
)
return resultResponse
Success Response (201 Created)
json
{
"success": true,
"data": {
"queued": 3,
"failed": 0,
"message_ids": [
"550e8400-e29b-41d4-a716-446655440000",
"550e8400-e29b-41d4-a716-446655440001",
"550e8400-e29b-41d4-a716-446655440002"
]
}
}Response Fields:
| Field | Type | Description |
|---|---|---|
queued | number | Number of messages successfully queued |
failed | number | Number of messages that failed validation |
message_ids | array | Array of message IDs for queued messages |
Error Responses
Too Many Recipients (400)
json
{
"success": false,
"error": "validation_error",
"message": "Maximum 1000 recipients per bulk request"
}Invalid Recipients (400)
json
{
"success": false,
"error": "validation_error",
"message": "All recipients must have a 'to' field"
}Rate Limit Exceeded (429)
json
{
"success": false,
"error": "rate_limit_exceeded",
"message": "Bulk send limited to 10 requests per 5 minutes"
}Insufficient Credits (402)
json
{
"success": false,
"error": "insufficient_credits",
"message": "Not enough credits. You need 100 credits but have 50."
}Best Practices
1. Batch Large Sends
For more than 1000 recipients, split into multiple requests:
javascript
const sendToAllCustomers = async (customers, template) => {
const BATCH_SIZE = 1000;
const results = [];
for (let i = 0; i < customers.length; i += BATCH_SIZE) {
const batch = customers.slice(i, i + BATCH_SIZE);
try {
const result = await sendBulkSMS(template, batch);
results.push(result);
// Wait 5 minutes between batches to respect rate limits
if (i + BATCH_SIZE < customers.length) {
await new Promise(resolve => setTimeout(resolve, 5 * 60 * 1000));
}
} catch (error) {
console.error(`Batch ${i / BATCH_SIZE + 1} failed:`, error);
}
}
return results;
};2. Validate Recipients Before Sending
javascript
const validateRecipient = (recipient) => {
if (!recipient.to) {
return false;
}
const e164Regex = /^\+[1-9]\d{1,14}$/;
if (!e164Regex.test(recipient.to)) {
return false;
}
return true;
};
const validRecipients = recipients.filter(validateRecipient);
if (validRecipients.length !== recipients.length) {
console.warn(`Filtered out ${recipients.length - validRecipients.length} invalid recipients`);
}3. Handle Partial Failures
javascript
const result = await sendBulkSMS(template, recipients);
if (result.data.failed > 0) {
console.warn(`${result.data.failed} messages failed validation`);
// Log failed recipients for manual review
}
console.log(`Successfully queued ${result.data.queued} messages`);4. Estimate Credits Before Sending
javascript
const estimateCreditsCost = (messageTemplate, recipients) => {
// Assume average message length after template substitution
const avgLength = 160;
const segmentsPerMessage = Math.ceil(avgLength / 160);
const totalCredits = recipients.length * segmentsPerMessage;
return totalCredits;
};
const estimatedCost = estimateCreditsCost(template, recipients);
console.log(`Estimated cost: ${estimatedCost} credits`);5. Use Webhooks for Status Tracking
Set up webhooks to track individual message delivery:
javascript
// Your webhook handler
app.post('/webhooks/yebolink', (req, res) => {
const { event, data } = req.body;
if (event === 'message.delivered') {
// Update delivery status in your database
updateMessageStatus(data.message_id, 'delivered');
} else if (event === 'message.failed') {
// Log failure for investigation
logMessageFailure(data.message_id, data.error_message);
}
res.status(200).send('OK');
});Use Cases
Order Notifications
javascript
const notifyOrderUpdates = async (orders) => {
const recipients = orders.map(order => ({
to: order.customer_phone,
name: order.customer_name,
order_id: order.id,
status: order.status,
tracking_url: `https://example.com/track/${order.id}`
}));
return await sendBulkSMS(
'Hi {{name}}, your order #{{order_id}} is {{status}}. Track it here: {{tracking_url}}',
recipients
);
};Appointment Reminders
javascript
const sendAppointmentReminders = async (appointments) => {
const recipients = appointments.map(apt => ({
to: apt.patient_phone,
name: apt.patient_name,
date: apt.date,
time: apt.time,
doctor: apt.doctor_name
}));
return await sendBulkSMS(
'Hi {{name}}, reminder: Your appointment with Dr. {{doctor}} is on {{date}} at {{time}}.',
recipients
);
};Marketing Campaigns
javascript
const sendPromotionalCampaign = async (subscribers) => {
const recipients = subscribers.map(sub => ({
to: sub.phone,
name: sub.first_name,
discount_code: generateUniqueCode(sub.id),
expiry_date: getExpiryDate()
}));
return await sendBulkSMS(
'Hi {{name}}! Exclusive offer: Use code {{discount_code}} for 20% off. Expires {{expiry_date}}.',
recipients
);
};Event Notifications
javascript
const notifyEventAttendees = async (event, attendees) => {
const recipients = attendees.map(attendee => ({
to: attendee.phone,
name: attendee.name,
event_name: event.name,
venue: event.venue,
date: event.date,
ticket_link: `https://example.com/tickets/${attendee.id}`
}));
return await sendBulkSMS(
'Hi {{name}}! {{event_name}} is happening on {{date}} at {{venue}}. Your ticket: {{ticket_link}}',
recipients
);
};Performance Tips
1. Prepare Recipients in Advance
Fetch and validate all recipient data before calling the API:
javascript
const prepareRecipients = async () => {
// Fetch from database
const users = await db.users.findAll({ where: { subscribed: true } });
// Transform and validate
return users
.filter(user => user.phone && isValidE164(user.phone))
.map(user => ({
to: user.phone,
name: user.first_name,
// ... other template variables
}));
};2. Monitor Queue Status
Track your bulk send operations:
javascript
const bulkSendWithTracking = async (template, recipients) => {
const startTime = Date.now();
try {
const result = await sendBulkSMS(template, recipients);
const duration = Date.now() - startTime;
console.log({
total: recipients.length,
queued: result.data.queued,
failed: result.data.failed,
duration_ms: duration,
credits_used: result.data.queued
});
return result;
} catch (error) {
console.error('Bulk send failed:', error);
throw error;
}
};Limitations
- Max Recipients: 1000 per request
- Rate Limit: 10 requests per 5 minutes
- Template Variables: Unlimited variables supported
- Message Length: Same as single messages (160 chars for GSM-7, 70 for Unicode)
Credits & Pricing
- Each message in the bulk send costs the same as a single message
- Example: Bulk send to 100 recipients = 100 credits minimum
- Concatenated messages cost multiple credits per recipient
Next Steps
- Retrieve Messages - Check delivery status
- Setup Webhooks - Track individual deliveries
- Manage Contacts - Organize recipient lists
- Purchase Credits - Add credits for large campaigns