Bulk Messaging
The six endpoints that send one message to many: choosing the audience, counting it, testing and sending.
Overview
These six endpoints run every step of sending one message to many people: choosing who to target, seeing how many that is, trying it on yourself, and sending it.
The audience comes from one of two places. Filters pick clients by group, country or the products they hold. A newsletter list is a hand-kept list of addresses whose members need not be clients. Give a newsletter key and the filters are ignored.
Sending is not immediate: the messages go into the notification queue and are worked through in the background. The batch id on the response is how you find that batch's rows in the queue.
Reference
Reading the Filter Options
Returns the values you can use in the recipient filters.
curl 'https://panel.example.com/api/v1/admin/tools/bulk/lookups' \
-H "Authorization: Bearer $API_KEY"const res = await fetch('https://panel.example.com/api/v1/admin/tools/bulk/lookups', {
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/tools/bulk/lookups');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// Take the filter values from HERE: guessing ids quietly produces an empty audience.
$lookups = Api::Tools()->GetBulkLookups()['data'];
$groupIds = array_column($lookups['user_groups'], 'id');Counting the Recipients
Tells you how many people your filters reach, without sending anything.
client or staff. It defaults to clients.mail or sms. The channel changes who counts as reachable.curl -X POST 'https://panel.example.com/api/v1/admin/tools/bulk/contacts' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"user_type":"client","notification_type":"mail","user_groups":[1]}'const res = await fetch('https://panel.example.com/api/v1/admin/tools/bulk/contacts', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
user_type: 'client',
notification_type: 'mail',
user_groups: [1],
}),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/tools/bulk/contacts');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'user_type' => 'client',
'notification_type' => 'mail',
'user_groups' => [1],
]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// Count BEFORE sending: give the same body to the submit endpoint and that many people get it.
$audience = ['user_type' => 'client', 'user_groups' => [1]];
$count = Api::Tools()->GetBulkContacts($audience)['data']['count'];
if ($count > 0 && $count < 5000) {
Api::Tools()->SubmitBulkNotification($audience + [
'subject' => 'Notice',
'message' => $html,
]);
}Sending a Test
Sends the message to the addresses you give and to department staff, not to clients.
curl -X POST 'https://panel.example.com/api/v1/admin/tools/bulk/test' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"subject":"Test","message":"Hello.","emails":"[email protected]"}'const res = await fetch('https://panel.example.com/api/v1/admin/tools/bulk/test', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
subject: 'Test',
message: 'Hello.',
emails: '[email protected]',
}),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/tools/bulk/test');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'subject' => 'Test',
'message' => $html,
'emails' => '[email protected]',
]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// The test ignores the FILTERS: it checks how the message looks, not who it would reach.
Api::Tools()->TestBulkNotification([
'subject' => 'Notice',
'message' => $html,
'emails' => '[email protected]',
]);Sending the Bulk Notification
Queues the message for everyone who matched. This is the real send.
single one message each, multiple all in one envelope. In one envelope the recipients can see each other.curl -X POST 'https://panel.example.com/api/v1/admin/tools/bulk/submit' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"notification_type":"mail","user_type":"client","subject":"Notice","message":"Important update.","delivery":"single","user_groups":[1]}'const res = await fetch('https://panel.example.com/api/v1/admin/tools/bulk/submit', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
notification_type: 'mail',
user_type: 'client',
subject: 'Notice',
message: 'Important update.',
delivery: 'single',
user_groups: [1],
}),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/tools/bulk/submit');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'subject' => 'Notice',
'message' => $html,
'delivery' => 'single',
'user_groups' => [1],
]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// A queued send CANNOT be recalled; cancelling in bulk means deleting the queue rows.
$batch = Api::Tools()->SubmitBulkNotification($audience + [
'subject' => 'Notice',
'message' => $html,
])['data'];
// It went out wrong: pull the queued ones back.
$queued = Api::Tools()->GetNotificationQueue([], ['status' => 'pending'])['data'];
$ids = array_column(
array_filter($queued, fn (array $n): bool => $n['batch_id'] === $batch['batch_id']),
'id',
);
Api::Tools()->BulkDeleteNotificationQueue(['ids' => $ids]);{
"data": {
"queued": true,
"recipients": 1842,
"batch_id": "b7f3c1a9"
}
}{
"error": {
"code": "no_recipients",
"message": "No recipient matched the filters."
}
}Reading a Newsletter List
Returns the newsletter subscribers, who need not be clients at all.
email or sms. It defaults to e-mail.curl -G 'https://panel.example.com/api/v1/admin/tools/bulk/newsletters' \
-H "Authorization: Bearer $API_KEY" \
-d type=email \
-d lang=enconst url = new URL('https://panel.example.com/api/v1/admin/tools/bulk/newsletters');
url.searchParams.set('type', 'email');
url.searchParams.set('lang', 'en');
const res = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
const body = await res.json();$url = 'https://panel.example.com/api/v1/admin/tools/bulk/newsletters?' . http_build_query([
'type' => 'email',
'lang' => 'en',
]);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// The response CHANGES SHAPE with the language: give one and you get a flat list, omit it and you get a summary per language.
$one = Api::Tools()->GetBulkNewsletters([], ['lang' => 'en'])['data']['entries'];
$all = Api::Tools()->GetBulkNewsletters()['data']['by_lang'];Writing a Newsletter List
Rewrites the newsletter list for one language and channel.
curl -X PUT 'https://panel.example.com/api/v1/admin/tools/bulk/newsletters' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"lang":"en","type":"email","entries":["[email protected]","[email protected]"]}'const res = await fetch('https://panel.example.com/api/v1/admin/tools/bulk/newsletters', {
method: 'PUT',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
lang: 'en',
type: 'email',
entries: ['[email protected]', '[email protected]'],
}),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/tools/bulk/newsletters');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'lang' => 'en',
'type' => 'email',
'entries' => $entries,
]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// To ADD one address send the existing list too: what you send replaces what was there.
$entries = Api::Tools()->GetBulkNewsletters([], ['lang' => 'en'])['data']['entries'];
$entries[] = '[email protected]';
Api::Tools()->SaveBulkNewsletters(['lang' => 'en', 'entries' => $entries]);Pitfalls
The submit endpoint has no undo. The only way to stop a wrong message is to take the batch id from the response, find the pending rows in the notification queue and delete them; whatever already went is gone. That is why counting first is not a formality but the only safety you have.
The test endpoint never reads the filters: it sends only to the addresses you give and to department staff. So it verifies how the message looks, not who would get it. Verifying the audience is the counting endpoint's job, and the two go together.
Choosing the shared envelope merges the message into one send. That is faster, but the recipient addresses can become visible to each other, and handing out a client list is a data leak. When in doubt, pick the per-recipient form.
The write does not merge: the set you send replaces that language's list. Adding one address means reading the current list and appending to it, or the rest of the subscribers are deleted.
Give a language and you get a flat list; omit it and you get a summary grouped per language. Code expecting one shape reads an empty list when the language parameter is forgotten, and takes that for "no subscribers".
Related Articles
Merci pour votre retour !
Notre équipe d'assistance est disponible 24h/24 pour tout ce que vous ne trouvez pas ci-dessus.