Skip to main content

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

ScopePurpose
campaigns:readList and view campaigns and analytics
campaigns:createCreate new campaigns
campaigns:updateEdit campaign content/settings and schedule/cancel-schedule
campaigns:sendSend, pause, resume, and cancel campaigns
campaigns:deleteDelete draft campaigns

Campaign Lifecycle

StatusDescription
draftCampaign is being prepared, not yet sent
scheduledCampaign is scheduled for future delivery
sendingCampaign is actively being sent
sentCampaign has finished sending
pausedCampaign was paused during sending
cancelledCampaign 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:

PlanBase feeIncluded pool / monthPer campaign message beyond the pool
BasicEUR 492,500EUR 0.020
ProfessionalEUR 792,500EUR 0.020
ScaleEUR 19910,000EUR 0.015
EnterpriseEUR 49940,000EUR 0.010
API OnlyEUR 9 per connected channel1,000 per connected channelEUR 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

FieldTypeRequiredDescription
namestringYesCampaign name
list_idsarrayYesArray of contact list IDs to send to
channel_contentobjectYes*Per-channel content (see shapes below). *Legacy single-channel campaigns may instead use top-level message_text + message_type.
descriptionstringNoInternal description
scheduled_atstringNoISO 8601 datetime. Can also be set later via the dedicated schedule endpoint.
timezonestringNoIANA timezone (e.g. Europe/Berlin) for scheduled_at
should_skip_duplicatesbooleanNoDefault true. Skip contacts already messaged within the duplicate window.
duplicate_window_hoursintegerNoDefault 24. Window for duplicate skipping.

channel_content shapes

ChannelKeyFields
Telegramtelegrammessage_text (required), message_type (text/image/video/document), attachment_id, channel_id
SMSsmsmessage_text (required), channel_id
Messengermessengermessage_text (required), message_type, attachment_id, channel_id
WhatsAppwhatsappchannel_ids (required), template_id (required), template_name (required), template_language, variable_mapping
Browser Pushbrowser_pushtitle (required), text (required), url, image_attachment_id, channel_id
WhatsApp requires templates

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 vs. email metrics

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

StatusError CodeDescription
401INVALID_TOKENToken is invalid or expired
403INSUFFICIENT_SCOPEToken lacks required campaigns scope
404RESOURCE_NOT_FOUNDCampaign not found
409CONFLICTCannot modify a campaign that is already sending or sent
422VALIDATION_ERRORInvalid request (e.g., empty list_ids)

Next Steps