Skip to main content

skrybe campaigns

Create, inspect, edit, test, send and schedule campaigns.

Campaign IDs are the plain integers the web app shows (for example 42). List IDs are the encrypted strings skrybe lists prints. Segment IDs are whole numbers from the segments page in the web app.

CommandDescription
campaignsShow campaigns, newest first
campaigns getOne campaign's details, or its HTML
campaigns statsOpens, clicks, bounces, complaints, unsubscribes
campaigns activityWho opened, clicked, bounced, complained or unsubscribed
campaigns createCreate a campaign, optionally sending or scheduling it
campaigns updateEdit a draft or scheduled campaign
campaigns duplicateCopy a campaign into a new draft
campaigns testSend a test to up to 5 addresses
campaigns sendSend a draft now
campaigns scheduleSchedule a draft, or move a scheduled campaign
campaigns unscheduleTurn a scheduled campaign back into a draft
campaigns deleteDelete a campaign
campaigns stop / resumeNot yet available

campaigns​

skrybe campaigns [--status <status>] [--page <n>] [--limit <n>] [--all]
skrybe campaigns ls [--status <status>] [--page <n>] [--limit <n>] [--all]

Shows the brand's campaigns, newest first. ls and list are aliases for the bare command.

FlagDescription
--status <status>Only campaigns in this state: draft, scheduled, preparing, sending, sent or paused
--page <n>Page to show. Default 1
--limit <n>Campaigns per page, 1 to 100. Default 10
--allFetch every page. Cannot be combined with --page or --limit
skrybe campaigns --status scheduled
 ID  STATUS     DATE              RECIPIENTS  TITLE
51 scheduled 2027-06-15 18:05 1,204 Q4 newsletter
48 scheduled 2027-06-01 09:00 318 Customer update

DATE is the scheduled time for a scheduled campaign and the send time otherwise, in your local timezone. --output json returns the API's fields, including subject, from_email, sent_at and scheduled_at (Unix seconds), timezone and source (ui, or api for campaigns created by emails send).

API: api/campaigns/get-campaigns.php

campaigns get​

skrybe campaigns get <campaign-id> [--content]

Shows one campaign: title, subject, status, sender, recipients, send or scheduled time, lists and segments (included and excluded) and its web version link.

FlagDescription
--contentPrint the campaign's HTML body instead (the plain text body if it has no HTML). With --output json, add html_text and plain_text to the record
skrybe campaigns get 42
skrybe campaigns get 42 --content > q4.html

A scheduled campaign shows its time in the campaign's own timezone, for example 2027-06-15 18:05 Africa/Lagos.

API: api/campaigns/get-campaign.php

campaigns stats​

skrybe campaigns stats <campaign-id>

Shows a campaign's report: recipients, opens and clicks (unique, rate and total), hard and soft bounces, complaints and unsubscribes, followed by a table of clicks per link.

Status        sent
Recipients 1,204
Opens 602 unique (50.4%), 911 total
Clicks 143 unique (11.9%), 201 total
Bounces 9 hard, 4 soft
Complaints 1
Unsubscribes 6

CLICKS UNIQUE LINK
120 98 https://acme.test/q4
81 45 https://acme.test/shop

The open rate is a percentage of delivered emails (recipients less hard bounces). The click rate is a percentage of recipients. Under --output json the links are in a links array.

API: api/campaigns/stats.php

campaigns activity​

skrybe campaigns activity <campaign-id> [--type <type>] [--page <n>] [--limit <n>] [--all]

Lists the subscribers behind a campaign's numbers.

FlagDescription
--type <type>opens (default), clicks, bounces, complaints or unsubscribes
--page <n>Page to show. Default 1
--limit <n>Subscribers per page, 1 to 1000. Default 100
--allFetch every page. Cannot be combined with --page or --limit

Every row has the email and name. Opens add the number of opens and the country of the first open; clicks add the number of clicks and the links clicked.

skrybe campaigns activity 42 --type clicks --all --output json \
| jq -r '.[] | [.email, (.links | join(" "))] | @tsv'

Bounces, complaints and unsubscribes are attributed to a subscriber's latest campaign, as the report in the web app does.

API: api/campaigns/get-activity.php

campaigns create​

skrybe campaigns create --title <title> --subject <subject> \
(--html-text <html|file://path> --from-name <name> --from-email <email> --reply-to <email> | --template <id>) \
[--list <list-id>]... [--send | --schedule <date-time>] [options]

Creates a campaign. Without --send or --schedule it is saved as a draft, which you can then test, send or schedule.

FlagDescription
--title <title>Title shown in the dashboard. Required
--subject <subject>Subject line. Required
--html-text <html>HTML body, or file:// and a path to it. Required unless --template gives one
--plain-text <text>Plain text body, or file:// and a path to it
--from-name <name>The From name. Required unless --template gives one
--from-email <email>The From address. Required unless --template gives one
--reply-to <email>The Reply-To address. Required unless --template gives one
--template <template-id>Start from a template
--list <list-id>List to send to. Repeatable
--segment <segment-id>Segment to send to. Repeatable
--exclude-list <list-id>List to exclude. Repeatable
--exclude-segment <segment-id>Segment to exclude. Repeatable
--query-string <query>Appended to links, e.g. Google Analytics tags
--track-opens <mode>0 off, 1 on, 2 anonymous
--track-clicks <mode>0 off, 1 on, 2 anonymous
--sendSend immediately instead of saving a draft
--schedule <date-time>Schedule it, e.g. "June 15, 2027 6:05pm". Minutes in steps of 5
--timezone <tz>Timezone for --schedule, e.g. America/New_York. Defaults to the brand's timezone

Recipients (--list, --segment and the excludes) are only used with --send or --schedule, which need at least one list or segment. A draft does not store recipients; you choose them when you send or schedule it.

With --template, the template supplies the HTML, plain text and sender. Any of --html-text, --plain-text, --from-name, --from-email or --reply-to given alongside it take precedence. If the template has no Reply-To, its From address is used.

# A draft from a file
skrybe campaigns create \
--title 'Q4 newsletter' --subject 'Your Q4 update' \
--from-name Acme --from-email hello@acme.test --reply-to hello@acme.test \
--html-text file://campaign.html

# A draft from a template
skrybe campaigns create --template 7 --title 'Q4 newsletter' --subject 'Your Q4 update'

# Create and schedule in one step
skrybe campaigns create --template 7 --title 'Q4 newsletter' --subject 'Your Q4 update' \
--list l7763lkC1Ld892Ty4p3pvxw --schedule 'June 15, 2027 6:05pm' --timezone Africa/Lagos

campaigns create --send does not ask for confirmation. To review the recipient count first, create a draft and use campaigns send, which does.

Output: Campaign created (id 51)., Campaign created and now sending. or Campaign scheduled.. Under --output json: {"outcome": "created", "campaign_id": "51"}, where outcome is created, sending or scheduled. The API only returns the new ID for a draft, so campaign_id is null with --send or --schedule.

API: api/campaigns/create.php; with --template, also api/templates/get-template.php

campaigns update​

skrybe campaigns update <campaign-id> [options]

Edits a draft or scheduled campaign. Only the fields you pass change. At least one is required. Prints the campaign afterwards, as campaigns get does.

FlagDescription
--title <title>Title shown in the dashboard
--subject <subject>Subject line
--preheader <text>Preview text shown after the subject in the inbox
--from-name <name>The From name
--from-email <email>The From address
--reply-to <email>The Reply-To address
--html-text <html>HTML body, or file:// and a path to it
--plain-text <text>Plain text body, or file:// and a path to it
--query-string <query>Appended to links
--track-opens <mode>0 off, 1 on, 2 anonymous
--track-clicks <mode>0 off, 1 on, 2 anonymous
skrybe campaigns update 42 --subject 'Your Q4 update is here' --html-text file://q4-v2.html

A campaign that is sending or already sent cannot be edited (campaign_sending, campaign_not_draft).

API: api/campaigns/update-campaign.php

campaigns duplicate​

skrybe campaigns duplicate <campaign-id> [--title <title>]
skrybe campaigns dup <campaign-id> [--title <title>]

Copies a campaign into a new draft in the same brand. Under table the new campaign's ID goes to stdout, so it can be captured; under json and text the new campaign's record is printed.

FlagDescription
--title <title>Title of the copy. Defaults to the original's
NEW=$(skrybe campaigns duplicate 42 --title 'Q1 newsletter')

API: api/campaigns/duplicate-campaign.php

campaigns test​

skrybe campaigns test <campaign-id> --to <email> [--to <email>]...

Sends the campaign as a test to up to 5 addresses.

FlagDescription
--to <email>Address to send to. Repeatable, or comma-separated. Required
skrybe campaigns test 42 --to ada@example.com --to team@example.com
skrybe campaigns test 42 --to ada@example.com,team@example.com

Duplicate addresses are sent once. Each brand can make 20 test sends an hour, shared with the test-send box in the web app; past that the command fails with rate_limited (exit code 4).

Each address is reported separately. If any fails, the command exits 1 with test_send_failed. --output json prints the result for each address:

{
"campaign_id": 42,
"results": [
{ "email": "ada@example.com", "ok": true, "error": null }
]
}

--output text prints one line per address: email, sent or failed, and the error.

API: api/campaigns/test-send.php

campaigns send​

skrybe campaigns send <campaign-id> (--list <list-id> | --segment <segment-id>)... [options]

Sends a draft now, to the lists and segments you name.

FlagDescription
--list <list-id>List to send to. Repeatable
--segment <segment-id>Segment to send to. Repeatable
--exclude-list <list-id>List to exclude. Repeatable
--exclude-segment <segment-id>Segment to exclude. Repeatable
--dry-runCheck everything and count the recipients, but send nothing
-y, --yesSend without asking. Required when stdin is not a terminal

At least one --list or --segment is required.

skrybe campaigns send 42 --list l7763lkC1Ld892Ty4p3pvxw --exclude-list 3t8NqP0wKqv7X1v892bVZQ --dry-run
# ✓ Campaign 42 would go to 1,204 recipients. Nothing was sent.

skrybe campaigns send 42 --list l7763lkC1Ld892Ty4p3pvxw --exclude-list 3t8NqP0wKqv7X1v892bVZQ
# Send campaign 42 to 1,204 recipients? [y/N] y
# ✓ Campaign 42 is sending to 1,204 recipients.

Before asking, it runs a dry run, so a wrong list ID or an unverified From domain fails before the question. From a script it refuses unless you pass --yes.

Only a draft can be sent. Running the command again after a dropped connection reports campaign_not_draft instead of sending twice. A scheduled campaign reports campaign_scheduled; unschedule it first.

Under --output json: {"outcome": "sending", "campaign_id": 42, "recipients": 1204}, or "outcome": "dry_run" with --dry-run.

Common errors: recipients_missing, list_not_found, segment_not_found, no_recipients, domain_not_verified, unauthorised_from_domain, account_under_review, and quota_exceeded (exit code 4).

API: api/campaigns/send.php

campaigns schedule​

skrybe campaigns schedule <campaign-id> --at <date-time> (--list <list-id> | --segment <segment-id>)... [options]

Schedules a draft, or moves a campaign that is already scheduled. Takes the same recipient flags as send, plus a time.

FlagDescription
--at <date-time>When to send, e.g. "2027-06-15 18:05" or "June 15, 2027 6:05pm". Required
--timezone <tz>IANA timezone, e.g. Africa/Lagos. Defaults to the account's timezone
--list <list-id>List to send to. Repeatable
--segment <segment-id>Segment to send to. Repeatable
--exclude-list <list-id>List to exclude. Repeatable
--exclude-segment <segment-id>Segment to exclude. Repeatable
--dry-runCheck everything and count the recipients, but change nothing
-y, --yesSchedule without asking. Required when stdin is not a terminal
skrybe campaigns schedule 42 --list l7763lkC1Ld892Ty4p3pvxw --at '2027-06-15 18:05' --timezone Africa/Lagos
# Schedule campaign 42 for 2027-06-15 18:05 Africa/Lagos to 1,204 recipients? [y/N]

The time is read in --timezone, or the account's timezone if you leave it out, and must be in the future. The recipients count against the brand's quota as soon as the campaign is scheduled; campaigns unschedule gives the quota back.

Under --output json: {"outcome": "scheduled", "campaign_id": 42, "recipients": 1204, "scheduled_at": 1813079100, "timezone": "Africa/Lagos"}.

Errors specific to scheduling: schedule_missing, invalid_schedule, schedule_in_past, invalid_timezone.

API: api/campaigns/schedule.php

campaigns unschedule​

skrybe campaigns unschedule <campaign-id>

Turns a scheduled campaign back into a draft and returns its quota reservation. Does not ask for confirmation. A campaign that is not scheduled fails with campaign_not_scheduled.

Under --output json: {"outcome": "draft", "campaign_id": 42}.

API: api/campaigns/unschedule.php

campaigns delete​

skrybe campaigns delete <campaign-id> [--yes]
skrybe campaigns rm <campaign-id> [--yes]

Deletes a campaign. A sent campaign loses its report, and a scheduled one has its quota reservation returned. A campaign that is preparing or sending cannot be deleted.

FlagDescription
-y, --yesDelete without asking. Required when stdin is not a terminal
Delete sent campaign 42 "Q4 newsletter" and its report? This cannot be undone. [y/N]

Under --output json: {"outcome": "deleted", "campaign_id": 42}.

API: api/campaigns/delete-campaign.php

Not yet available​

These commands exist so that they explain themselves rather than failing with "unknown command", but the HTTP API has no endpoint for them yet. They always fail with not_implemented and exit code 2. Use the web app for now.

CommandPurpose
skrybe campaigns stop <campaign-id>Stop a sending campaign
skrybe campaigns resume <campaign-id>Resume a stopped campaign