Messaging Campaigns
Campaigns let you send bulk messages to contact lists across WhatsApp, Telegram, Messenger, Instagram, and SMS. This guide covers creating, scheduling, sending, and analyzing campaigns.
Required Scopes
| Scope | Purpose |
|---|---|
campaigns:read | List and view campaigns and analytics |
campaigns:create | Create new campaigns |
campaigns:update | Edit campaign content/settings and schedule/cancel-schedule |
campaigns:send | Send, pause, resume, and cancel campaigns |
campaigns:delete | Delete draft campaigns |
Campaign Lifecycle
| Status | Description |
|---|---|
draft | Campaign is being prepared, not yet sent |
scheduled | Campaign is scheduled for future delivery |
sending | Campaign is actively being sent |
sent | Campaign has finished sending |
paused | Campaign was paused during sending |
cancelled | Campaign was cancelled before completion |
Campaign Pricing
Campaign messages are billed at the same unified per-unit rate as conversation messages and KB queries, and draw from the same monthly included pool that comes with your plan's base fee:
| Plan | Base fee | Included pool / month | Per campaign message beyond the pool |
|---|---|---|---|
| Basic | EUR 49 | 2,500 | EUR 0.020 |
| Professional | EUR 79 | 2,500 | EUR 0.020 |
| Scale | EUR 199 | 10,000 | EUR 0.015 |
| Enterprise | EUR 499 | 40,000 | EUR 0.010 |
| API Only | EUR 9 per connected channel | 1,000 per connected channel | EUR 0.005 |
Incoming messages are not charged. Campaign emails have their own (lower) rate — see Email Campaigns. Carrier/network fees for SMS are passed through on top of these rates.
Create a Campaign
POST /api/v1/campaigns
Campaign content is per-channel, supplied via the channel_content object. Include only the channels the campaign targets. Each channel has its own content shape — text channels (Telegram, SMS, Messenger) take a message_text, WhatsApp requires a pre-approved template, and browser push takes a title/body.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Campaign name |
list_ids | array | Yes | Array of contact list IDs to send to |
channel_content | object | Yes* | Per-channel content (see shapes below). *Legacy single-channel campaigns may instead use top-level message_text + message_type. |
description | string | No | Internal description |
scheduled_at | string | No | ISO 8601 datetime. Can also be set later via the dedicated schedule endpoint. |
timezone | string | No | IANA timezone (e.g. Europe/Berlin) for scheduled_at |
should_skip_duplicates | boolean | No | Default true. Skip contacts already messaged within the duplicate window. |
duplicate_window_hours | integer | No | Default 24. Window for duplicate skipping. |
channel_content shapes
| Channel | Key | Fields |
|---|---|---|
| Telegram | telegram | message_text (required), message_type (text/image/video/document), attachment_id, channel_id |
| SMS | sms | message_text (required), channel_id |
| Messenger | messenger | message_text (required), message_type, attachment_id, channel_id |
whatsapp | channel_ids (required), template_id (required), template_name (required), template_language, variable_mapping | |
| Browser Push | browser_push | title (required), text (required), url, image_attachment_id, channel_id |
Business-initiated WhatsApp campaigns can only send approved message templates — you cannot send free-form text. See WhatsApp Templates for how to create and reference a template, then pass its template_id/template_name in channel_content.whatsapp.
Create a Draft (Telegram)
curl -X POST "https://api.sendseven.com/api/v1/campaigns" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"name": "Weekend Sale Reminder",
"list_ids": ["list_active_customers", "list_newsletter"],
"channel_content": {
"telegram": {
"message_type": "text",
"message_text": "Our weekend sale starts tomorrow! 30% off all items."
}
}
}'
Response (201 Created)
The created campaign starts in draft status. Recipient and delivery counters are returned as discrete fields:
{
"id": "c3d4e5f6-7a8b-49c0-d1e2-f3a4b5c6d7e8",
"name": "Weekend Sale Reminder",
"status": "draft",
"channel_content": {
"telegram": { "message_type": "text", "message_text": "Our weekend sale..." }
},
"list_ids": ["list_active_customers", "list_newsletter"],
"total_recipients": 3800,
"sent_count": 0,
"delivered_count": 0,
"read_count": 0,
"failed_count": 0,
"scheduled_at": null,
"timezone": null,
"created_at": "2026-02-10T16:30:00Z",
"updated_at": "2026-02-10T16:30:00Z"
}
Schedule a Campaign
Scheduling uses a dedicated endpoint that accepts a local datetime plus an IANA timezone (the backend converts to UTC for storage). The scheduled time must be at least 5 minutes in the future. Requires the campaigns:update scope.
POST /api/v1/campaigns/{campaign_id}/schedule
curl -X POST "https://api.sendseven.com/api/v1/campaigns/c3d4e5f6-7a8b-49c0-d1e2-f3a4b5c6d7e8/schedule" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"scheduled_at": "2026-02-15T08:00:00",
"timezone": "Europe/Berlin"
}'
The campaign moves to scheduled status. To unschedule, call POST /api/v1/campaigns/{campaign_id}/cancel-schedule.
Send a Campaign
Immediately begin sending a draft campaign. Requires the campaigns:send scope.
POST /api/v1/campaigns/{campaign_id}/send
curl -X POST "https://api.sendseven.com/api/v1/campaigns/c3d4e5f6-7a8b-49c0-d1e2-f3a4b5c6d7e8/send" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json"
{
"success": true,
"campaign_id": "c3d4e5f6-7a8b-49c0-d1e2-f3a4b5c6d7e8",
"recipient_count": 3800,
"published_count": 3800,
"message": "Campaign queued for sending to 3800 recipients (3800 messages published)"
}
A sending campaign can be paused, resumed, or cancelled (all require campaigns:send):
POST /api/v1/campaigns/{campaign_id}/pause
POST /api/v1/campaigns/{campaign_id}/resume
POST /api/v1/campaigns/{campaign_id}/cancel
Get Campaign Statistics
Retrieve delivery metrics for a campaign. Requires the campaigns:read scope.
GET /api/v1/campaigns/{campaign_id}/statistics
curl -X GET "https://api.sendseven.com/api/v1/campaigns/c3d4e5f6-7a8b-49c0-d1e2-f3a4b5c6d7e8/statistics" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json"
{
"campaign_id": "c3d4e5f6-7a8b-49c0-d1e2-f3a4b5c6d7e8",
"campaign_name": "Weekend Sale Reminder",
"status": "sent",
"total_recipients": 2450,
"sent_count": 2450,
"delivered_count": 2398,
"read_count": 1120,
"failed_count": 52,
"pending_count": 0,
"skipped_count": 0,
"status_counts": { "sent": 2450, "delivered": 2398, "read": 1120, "failed": 52 },
"started_at": "2026-02-15T08:00:05Z",
"completed_at": "2026-02-15T08:14:32Z"
}
Messaging campaigns track delivery stats (sent/delivered/read/failed). Open-rate, click-rate, and bounce metrics are specific to Email Campaigns, which expose them via GET /api/v1/email-campaigns/{id}/analytics.
List Campaigns
GET /api/v1/campaigns
Returns a plain JSON array of campaigns (not a paginated items/pagination envelope). Paginate with limit (default 50) and offset, and optionally filter by campaign_status.
curl -X GET "https://api.sendseven.com/api/v1/campaigns?limit=20&offset=0&campaign_status=sent" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00"
Python Example
import requests
BASE_URL = "https://api.sendseven.com/api/v1"
HEADERS = {
"Authorization": "Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00",
"Content-Type": "application/json",
}
# Create a draft campaign (Telegram)
campaign = requests.post(
f"{BASE_URL}/campaigns",
headers=HEADERS,
json={
"name": "Flash Sale Alert",
"list_ids": ["list_active_customers"],
"channel_content": {
"telegram": {
"message_type": "text",
"message_text": "24-hour flash sale! 50% off everything. Shop now: shop.example.com",
}
},
},
).json()
print(f"Created: {campaign['id']} ({campaign['total_recipients']} recipients)")
# Send it immediately
result = requests.post(f"{BASE_URL}/campaigns/{campaign['id']}/send", headers=HEADERS).json()
print(result["message"])
# Check statistics later
stats = requests.get(
f"{BASE_URL}/campaigns/{campaign['id']}/statistics",
headers=HEADERS,
).json()
print(f"Delivered: {stats['delivered_count']}/{stats['sent_count']}, read: {stats['read_count']}")
JavaScript Example
const BASE_URL = "https://api.sendseven.com/api/v1";
const HEADERS = {
"Authorization": "Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00",
"Content-Type": "application/json",
};
// Create a draft campaign
const createRes = await fetch(`${BASE_URL}/campaigns`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({
name: "Valentine's Day Special",
list_ids: ["list_newsletter"],
channel_content: {
telegram: {
message_type: "text",
message_text: "Show someone you care with our Valentine's collection. Order by Feb 12!",
},
},
}),
});
const campaign = await createRes.json();
// Schedule it for a future time (local datetime + IANA timezone)
await fetch(`${BASE_URL}/campaigns/${campaign.id}/schedule`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({ scheduled_at: "2026-02-12T08:00:00", timezone: "Europe/Berlin" }),
});
console.log(`Campaign ${campaign.id} scheduled.`);
Error Responses
| Status | Error Code | Description |
|---|---|---|
| 401 | INVALID_TOKEN | Token is invalid or expired |
| 403 | INSUFFICIENT_SCOPE | Token lacks required campaigns scope |
| 404 | RESOURCE_NOT_FOUND | Campaign not found |
| 409 | CONFLICT | Cannot modify a campaign that is already sending or sent |
| 422 | VALIDATION_ERROR | Invalid request (e.g., empty list_ids) |
Next Steps
- Email Campaigns -- HTML email campaigns with templates
- Tags & Lists -- manage recipient lists
- Manage Contacts -- import and organize contacts