Campaigns let you send a single message to many recipients at once. Instead of calling POST /v1/messages in a loop, you create a campaign with a recipient list and senderZ handles delivery, compliance, rate limiting, and status tracking for every message in the batch.
How Campaigns Work
- You create a campaign with a list of recipients (contact IDs or phone numbers) and a message body or template.
- senderZ enqueues every message into the Cloudflare Queue, one per recipient.
- The router processes each message individually — checking opt-outs, enforcing quiet hours, resolving the best phone, and sending via iMessage or SMS.
- Campaign status updates as messages are delivered, and webhook events fire for each delivery.
Create a Campaign
/v1/campaigns Create and launch a new campaign.
Parameters
name string
Required
A descriptive name for the campaign (e.g. “April Newsletter”, “Flash Sale Alert”).
body string The message text to send to all recipients. Required if template is not provided.
template string Template name to use. Required if body is not provided. Template variables are resolved per-contact using the contact’s metadata and name.
data object Global template variables applied to all recipients. Contact-specific fields like {{name}} are resolved automatically from the contact record.
recipients array
Required
Array of recipient identifiers. Each item can be a contact ID ("01JCON123ABC") or an E.164 phone number ("+15551234567"). Maximum 10,000 recipients per campaign.
group_id string Send to all contacts in a group. When provided, the recipients field is ignored.
channel string Delivery channel: auto (default), imessage, or sms. Applied to all messages in the campaign.
scheduled_at string ISO 8601 timestamp to schedule the campaign for future delivery. If omitted, the campaign starts immediately.
Example: Send to a contact group
curl -X POST https://api.senderz.com/v1/campaigns \
-H "Authorization: Bearer sz_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "April Newsletter",
"template": "monthly_newsletter",
"data": {
"month": "April",
"promo_code": "SPRING20"
},
"group_id": "01JGRP001ABC",
"channel": "auto"
}'
Example: Send to specific recipients
curl -X POST https://api.senderz.com/v1/campaigns \
-H "Authorization: Bearer sz_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Flash Sale",
"body": "Flash sale! 30% off all items for the next 2 hours. Reply STOP to unsubscribe.",
"recipients": ["+15551111111", "+15552222222", "+15553333333"],
"channel": "auto"
}'
{
"data": {
"id": "01JCMP456DEF",
"name": "April Newsletter",
"status": "sending",
"total_recipients": 2048,
"messages_sent": 0,
"messages_delivered": 0,
"messages_failed": 0,
"messages_blocked": 0,
"channel": "auto",
"scheduled_at": null,
"created_at": "2026-04-15T10:30:00Z"
}
} Get Campaign Status
/v1/campaigns/:id Retrieve campaign details and delivery progress.
id string
Required
The ULID of the campaign.
curl https://api.senderz.com/v1/campaigns/01JCMP456DEF \
-H "Authorization: Bearer sz_live_YOUR_KEY"
{
"data": {
"id": "01JCMP456DEF",
"name": "April Newsletter",
"status": "completed",
"total_recipients": 2048,
"messages_sent": 2048,
"messages_delivered": 1987,
"messages_failed": 12,
"messages_blocked": 49,
"channel": "auto",
"channel_breakdown": {
"imessage": 1654,
"sms": 333
},
"scheduled_at": null,
"started_at": "2026-04-15T10:30:05Z",
"completed_at": "2026-04-15T10:47:22Z",
"created_at": "2026-04-15T10:30:00Z"
}
} Campaign Statuses
| Status | Meaning |
|---|---|
draft | Created but not yet started (scheduled for future) |
sending | Messages are being processed and delivered |
paused | Campaign has been paused by the operator |
completed | All messages have been processed |
cancelled | Campaign was cancelled before completion |
Blocked Messages
Messages are blocked (not failed) when:
- The recipient has opted out (sent STOP)
- The recipient’s phone number is invalid
- Your daily new-contact quota has been reached for a new number
Blocked messages do not count against your quota or appear as delivery failures.
List Campaign Messages
/v1/campaigns/:id/messages List individual messages within a campaign.
Returns every message in the campaign with its delivery status. Useful for identifying failed deliveries and understanding per-recipient outcomes.
Query Parameters
status string Filter by message status: queued, sent, delivered, failed, blocked.
page number Page number, starting at 1. Defaults to 1.
limit number Results per page. Defaults to 25, maximum 100.
curl "https://api.senderz.com/v1/campaigns/01JCMP456DEF/messages?status=failed&limit=50" \
-H "Authorization: Bearer sz_live_YOUR_KEY"
{
"data": [
{
"message_id": "01JMSG789GHI",
"to": "+15559876543",
"channel": "sms",
"status": "failed",
"error_code": "DELIVERY_FAILED",
"sent_at": "2026-04-15T10:35:12Z"
},
{
"message_id": "01JMSG790JKL",
"to": "+15558765432",
"channel": "imessage",
"status": "failed",
"error_code": "NO_PHONE_AVAILABLE",
"sent_at": "2026-04-15T10:36:44Z"
}
],
"meta": {
"total": 12,
"page": 1,
"limit": 50
}
} Campaign Quota Interaction
Campaigns interact with your plan quotas in two ways:
New-contact quota: Each phone number that has never been messaged by your tenant before counts as a new contact. If a campaign targets 500 recipients and 100 of them are new, those 100 count against your daily new-contact limit. When the limit is reached, remaining new contacts are queued for the next day.
API call quota: Creating a campaign counts as a single API call, regardless of the number of recipients. Individual message deliveries do not consume additional API calls.
Webhook Events
Campaigns fire webhook events as messages are delivered. If you have a webhook registered for message events, you will receive individual delivery notifications for each campaign message.
| Event | Fires When |
|---|---|
message.sent | A campaign message is sent to the carrier |
message.delivered | A campaign message is confirmed delivered |
message.failed | A campaign message fails to deliver |
campaign.completed | All messages in the campaign have been processed |
Each webhook payload includes the campaign_id field so you can correlate events back to the campaign. See the Webhooks documentation for setup instructions.
Error Responses
| Status | Code | Description |
|---|---|---|
| 400 | VALIDATION_ERROR | Missing required field or invalid parameter |
| 400 | INVALID_TEMPLATE | Template name not found |
| 400 | EMPTY_RECIPIENTS | No valid recipients provided |
| 401 | INVALID_API_KEY | Bad or missing API key |
| 404 | CAMPAIGN_NOT_FOUND | Campaign ID does not exist or belongs to another tenant |
| 429 | RATE_LIMIT_EXCEEDED | Too many requests |
Best Practices
- Always include opt-out language in marketing campaigns. Append “Reply STOP to unsubscribe.” to your message body. senderZ processes STOP keywords automatically, but the language is required by TCPA.
- Use contact groups instead of raw phone number lists. Groups make it easy to reuse recipient lists across campaigns and keep your contacts organized.
- Schedule campaigns outside quiet hours. While senderZ automatically delays quiet-hours messages until morning, scheduling your campaign for 9 AM local time gives a better recipient experience than trickling messages in at 8 AM.
- Monitor the campaign status endpoint or subscribe to the
campaign.completedwebhook to know when all messages have been processed. - Test with small batches first. Create a campaign with 5-10 recipients using test keys (
sz_test_) to verify your template renders correctly before sending to your full list.