Scheduler API
The Scheduler sends a message or email at a set time, once or on a repeating schedule. It has two parts:
- Scheduled sends: one message to one contact at
send_at, optionally repeating. - Rules: a sender that creates scheduled sends for many contacts by itself: on each contact's birthday, on a recurring date for a segment, or once to a segment.
The Scheduler is available on every plan except Basic (including API Only). On a plan without it, every Scheduler call returns 403 with {"detail": {"code": "feature_disabled", "feature": "scheduler"}}.
To run a multi-step automation on a schedule rather than send one message, use a flow with a schedule trigger.
Required Scopes
| Scope | Purpose |
|---|---|
scheduler:read | List and view scheduled sends and rules |
scheduler:write | Create, change, pause and cancel them |
scheduler:read + contacts:read | GET /contacts/{contact_id}/scheduled-sends |
Schedule a Send
curl -X POST "https://api.sendseven.com/api/v1/scheduler/sends" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"contact_id": "contact_d4e5f6a7",
"channel_type": "telegram",
"channel_id": "ch_telegram_main",
"payload": {"text": "Hi {{contact.first_name}}, your appointment is tomorrow at 10:00."},
"send_at": "2026-11-04T08:00:00Z"
}'
Returns 201 with the scheduled send.
| Field | Required | Description |
|---|---|---|
contact_id | Yes | The recipient. 404 if unknown |
channel_type | Yes | whatsapp, sms, telegram, messenger, instagram, rcs or email |
channel_id | All except email | The channel that sends it (for example the WhatsApp number or Telegram bot) |
email_mailbox_id | The mailbox that sends it (its address is the sender). channel_id stays empty for email | |
payload | Yes | What to send, see Payloads. Up to 64 KB |
send_at | Yes | ISO 8601 with a time zone (Z or +01:00), at least one minute in the future (400 otherwise) |
recurrence_rule | No | iCalendar RRULE, such as FREQ=WEEKLY;BYDAY=MO;BYHOUR=9. The send then stays scheduled after each run and next_run_at moves to the next date. Max 255 characters |
timezone | No | Stored for display. Recurrence is calculated in UTC |
The contact must be reachable on the chosen channel (it needs a matching contact method), otherwise 400.
Payloads
| Channel | payload |
|---|---|
{"template_id": "...", "variables": {"1": "Anna"}}: an approved WhatsApp template (its SendSeven ID or its WhatsApp template ID). Free text is not allowed, because nobody can know at scheduling time whether the 24-hour window will be open | |
| SMS, Telegram, Messenger, Instagram, RCS | {"text": "...", "attachments": ["<attachment id>"]}. Up to 20 attachments from your workspace, each sent as its own message. No templates |
| Email, free text | {"subject": "...", "html": "..."} or "text" instead of "html"; optional include_signature, cc, bcc, attachments |
| Email, saved template | {"template_id": "...", "variables": {"discount_code": "AUTUMN10"}}: saved email content. Cannot be combined with subject |
For WhatsApp, variables fill the template's placeholders; values you leave out come from the template's own variable mapping.
Variables
Emails (free text and templates; subject and body) resolve:
| Variable | Value |
|---|---|
{{contact.name}}, {{contact.first_name}}, {{contact.last_name}}, {{contact.email}}, {{contact.phone}}, {{contact.birthday}} | The recipient |
{{contact.custom.<key>}} | A custom field of the recipient |
{{company.name}}, {{company.legal_name}}, {{company.address}}, {{company.phone}}, {{company.email}}, {{company.website}}, {{company.logo_url}}, {{company.brand_color_*}} | Your company profile |
{{agent.name}}, {{agent.first_name}}, {{agent.last_name}}, {{agent.email}}, {{agent.phone}}, {{agent.job_title}}, {{agent.role}} | The team member who scheduled the send, while they are still an active member |
{{system.year}}, {{system.date}} | The current year and date when the email goes out |
{{your_key}} | Any plain key from payload.variables (keys with dots are ignored). For a template, these override the template's saved values |
Values are HTML-escaped in HTML bodies. A variable without a value renders empty. The older {{now}} and {{tenant.name}} still work.
Chat messages (text) and WhatsApp template variables resolve {{contact.*}}, {{contact.custom.<key>}}, {{now}} and {{tenant.name}}.
Manage scheduled sends
| Method | Path | Description |
|---|---|---|
GET | /scheduler/sends | Filter by status (scheduled, running, completed, failed, canceled), channel_type, contact_id; page, page_size (default 50, max 100) |
GET | /scheduler/sends/{send_id} | One send |
PATCH | /scheduler/sends/{send_id} | Change payload, send_at, recurrence_rule ("" removes the repetition) or email_mailbox_id. Only while status is scheduled, else 409 |
DELETE | /scheduler/sends/{send_id} | Cancel (the send stays visible as canceled). Idempotent, returns {"success": true, "id": "..."} |
GET | /contacts/{contact_id}/scheduled-sends | A contact's upcoming sends (scheduled and running); pass status to see past ones |
A send carries id, contact_id, channel_type, channel_id, email_mailbox_id, payload, send_at, next_run_at, recurrence_rule, status, rule_id (when a rule created it), created_by_user_id, created_at and updated_at.
Rules
A rule creates scheduled sends for every matching contact when they fall due. Rules use the same channel_type, channel_id, email_mailbox_id and payload as a single send.
curl -X POST "https://api.sendseven.com/api/v1/scheduler/rules" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"name": "Birthday email",
"kind": "birthday",
"channel_type": "email",
"email_mailbox_id": "mbx_hello",
"payload": {
"subject": "Happy birthday, {{contact.first_name}}!",
"html": "<p>Enjoy 10% off this week with code <b>{{discount_code}}</b>.</p><p>{{agent.name}}, {{company.name}}</p>",
"variables": {"discount_code": "BDAY10"}
},
"time_of_day_local": "09:00",
"timezone": "Europe/Berlin"
}'
kind | Sends | Required fields |
|---|---|---|
birthday | Every year on each contact's birthday, at time_of_day_local (HH:MM) in timezone (UTC if empty). Contacts without a birthday are skipped | time_of_day_local. No recurrence_rule, no segment_filter |
recurring_cron | To the contacts in segment_filter on every date of recurrence_rule | recurrence_rule |
segment_blast | Once, to the contacts in segment_filter, at next_evaluation_at. The rule is archived afterwards | next_evaluation_at. No recurrence_rule |
Other fields: name (required, max 200), description (max 500), timezone (IANA name; UTC when empty).
segment_filter chooses the audience:
{
"match_op": "all",
"rules": [
{"kind": "tag", "op": "has_any", "values": ["vip", "newsletter"]},
{"kind": "list", "op": "in_list", "values": ["<list id>"]},
{"kind": "language", "op": "in", "values": ["de", "en"]}
]
}
kind | op | values |
|---|---|---|
tag | has_any, has_all | Tag names (case-insensitive) |
list | in_list, not_in_list | List IDs |
language | in, not_in | Two-letter language codes |
match_op is all (every rule must match) or any.
Manage rules
| Method | Path | Description |
|---|---|---|
GET | /scheduler/rules | Filter by status (active, paused, archived) and kind |
GET / PATCH | /scheduler/rules/{rule_id} | Read or change a rule. status can only switch between active and paused; an archived rule returns 409 |
POST | /scheduler/rules/{rule_id}/pause, /resume | Pause or resume |
DELETE | /scheduler/rules/{rule_id} | Archive the rule, returns {"success": true, "id": "..."} |
GET | /scheduler/rules/{rule_id}/sends | The scheduled sends the rule created |
Error Responses
| Status | When |
|---|---|
400 | send_at in the past or less than a minute ahead; contact not reachable on the channel; wrong sender field for the channel; payload not allowed for the channel |
403 | Missing scope, or the plan does not include the Scheduler (feature_disabled) |
404 | Unknown contact, send, rule or attachment |
409 | Changing a send that is no longer scheduled, or an archived rule |
422 | Invalid body (for example a malformed segment_filter or a missing field the kind requires) |