Skip to main content

Campaigns

Campaigns lists every send for the app and hosts the wizard that creates new ones. Transactional notifications sent from the API appear in the same list with their own report.

The campaigns list

Columns: name, status badge, Schedule, Sent, Opens (open rate). Click a row for its report. Drafts and scheduled campaigns have an Cancel campaign button; nothing else can be cancelled, because once fan-out starts the jobs are already spread across the queues.

BadgeStatusMeaning
draftdraftWizard started; not everything is filled in. Open it to continue.
scheduledscheduledWaiting for its time as a delayed job in Redis. Cancellable.
sendingsendingFanning out / pushes in flight. The report auto-refreshes.
sentsentFan-out done; every push has been attempted.
cancelledcancelledCancelled before fan-out.
failedfailedFan-out itself failed (e.g. no credentials for a platform in the audience).
recurring · activeactiveA recurring campaign whose schedule is live. It never sends itself; each tick creates a run, listed on its report page.

The wizard

Click + New campaign. Four steps, each in the URL (?step=segment|content|preview|schedule) so the browser's back button walks the wizard and a reload lands where you were.

Drafts save themselves

The first change creates a campaign row with status draft and puts its id in the URL (?draft=…). Every step change saves, and typing saves 1.5 s after you stop. Close the tab; it will be in the list.

1 Audience

Step 1: audience

  • Name — the campaign's name. Internal; not shown to users.
  • The segment builder. A group is All must match (all of / AND) or Any may match (any of / OR). + Condition adds a condition, + Group nests another group. Each condition is a field, an operator and a value; NOT negates it.
  • Fields offered: platform, country, language, timezone, subscription status, Last seen (last active), Last session / Session count (sessions), Last opened, Last notified, Created, app / OS version, device model, browser — and every tag key the audience actually has (tag: plan, tag: streak…), with suggestions taken from real values.
  • + Interaction adds a behavioural condition: campaign X was opened / clicked / delivered / sent (optionally within -30d); NOT turns it into "did not". Only campaigns that have been sent are offered.
  • Saved segment… loads a saved segment; Save as segment stores the current tree under a name. Editing a loaded segment detaches it — the campaign keeps its own copy.
  • Dates take absolute values (2026-08-01) or relative ones (-7d, -12h).
  • Matching subscribers on the right is a live count, by platform, of opted-in devices with a valid token. It re-queries as you edit.
  • Generated DSL expands to the JSON that will be stored — the same segment DSL the API accepts, so you can copy it into a POST /v1/campaigns body.

The generated DSL

Leave the builder empty to target everyone.

2 Content

Step 2: content

Content is per locale. Tabs across the top: en ★, tr, + Add language. The starred one is _default — what a subscriber gets when none of the locales match their language.

FieldNotes
Title / BodyTitle (≤ 200) and body (≤ 1000). Placeholders like `{{firstName
Image URLBig picture. Android and web show it; iOS shows it via the Notification Service Extension; not on the iOS lock screen.
Tap targetDeep link or URL opened on tap. Placeholders allowed.
ButtonsUp to three action buttons: id, title, optional URL (falls back to the notification's). Chrome shows two, Android three, iOS up to four.
Byte meterWeb push payloads are capped at 4 KB; the bar shows the encrypted size of the current locale.

Above the copy, a strip: Silent push (background push — nothing is drawn, the app gets the Data payload; web is skipped; the copy fields are greyed out), Load from template… and Save as template (see Templates).

Content step with silent push on

Below the copy:

  • Data — the data key/value pairs delivered with the push (message.data in the SDKs). For a silent push this is the whole message.
  • Presentation — sound (Default / None / custom file name), badge count, group (thread-id on iOS, group key on Android), web icon URL, iOS interruption level (interruption level: passive / active / time-sensitive / critical), iOS relevance score (relevance score), Android channel (one of the app's channels). Platforms ignore what they do not support.
  • Variables — chips for the built-ins ({{externalId}}, {{country}}, {{language}}, {{platform}}) and every tag key seen in the audience. Clicking one inserts it at the cursor.
  • A/B test — turns the content into an experiment with 2–4 variants; see A/B testing.

Content step with an A/B test open

3 Preview

Step 3: preview

Device mock-ups for iOS, Android and a browser, per locale (Preview language). The text goes through the same placeholder function the worker uses (core/personalize.ts), so what you see is byte-for-byte what a device gets. Sample values lets you type sample tag values for the preview only; at send time each subscriber's own tags are used. Missing locales and empty titles are flagged here before you can continue.

4 Schedule

Step 4: schedule

OptionBehaviour
Send nowQueued as soon as you confirm.
Specific date and timeInterpreted in your (browser) timezone, stored as UTC, held as a delayed job in Redis.
User's local timeEnter a wall-clock time (19:30). Each subscriber gets it at 19:30 in their own timezone; subscribers without one use the app's default timezone, else UTC. Solved at fan-out per push, no per-timezone campaign copies. See Scheduling.
RecurringEvery day, or chosen weekdays, at a time in one timezone (defaults to your browser's), optionally until a date. Start makes the campaign active; each tick sends a run to whoever matches then. Stop it from the list or its report page. See Recurring.

Recurring schedule

Advanced expands:

Advanced options

  • TTL — how long the push service keeps trying while the device is offline (default 3 days; presets from 1 hour to 4 weeks).
  • Delivery rules — tick to bypass the app's frequency cap and/or quiet hours for this campaign (a password reset must not wait until morning).
  • collapseId — a newer push with the same id replaces an undelivered older one (apns-collapse-id, FCM collapse_key, web tag).

Send a test sends the current draft to every device of one externalId — yours — without touching the campaign's status or counters. The test is a separate transactional send and shows up in the list as such.

The summary card on the right recaps everything. Send / Schedule validates the draft against the strict content schema (all locales complete, URLs well-formed), writes the final content, and either queues or schedules. From here on the draft cannot be edited.

Editing and duplicating

A draft can be reopened from the list until it is sent. Sent campaigns are immutable — that is what makes their reports trustworthy. To send again, save the copy as a template and start a new campaign from it, or paste the DSL / copy from the report page.