Skip to content

Campaigns

A campaign = message + audience + schedule. Compose in the dashboard or via POST /v1/apps/:appId/campaigns.

Scheduling options

  • Send now / schedule — one-shot delivery.
  • Recurrencedaily or weekly; each occurrence gets its own send ledger, so the same audience is safely re-targeted.
  • Subscriber timezone (deliverInSubscriberTz) — everyone receives the campaign at the scheduled wall-clock time in their timezone, swept over a 26-hour world-clock window.
  • Optimal time (optimalTime) — each subscriber receives it at their personally best engagement hour, learned from 90 days of opens/clicks in their timezone (circularly smoothed hourly histogram, minimum-sample gated, refreshed hourly). Subscribers without enough history send at campaign start; the 25-hour window closes with a catch-up pass.
  • Throttled rollout (sendRatePerMinute) — cap devices enqueued per minute for gradual releases.

Timezone, optimal-time and throttling are mutually exclusive.

A/B testing

Attach up to four variant messages with percentage splits; subscribers are bucketed deterministically at fan-out. GET /campaigns/:id/variants returns the per-variant funnel and the winner by open rate.

Personalization

Liquid templating from subscriber tags in title and body:

{{ tags.first_name | default: "there" }}, your {{ tags.plan }} renews soon

Multi-language

Messages accept per-language overrides, chosen by the subscriber's language tag with region → base-language → default fallback:

json
{ "title": "Big Sale", "body": "50% off", "localizations": { "ms": { "title": "Jualan Besar", "body": "Diskaun 50%" } } }

Measuring

  • Funnel: GET /campaigns/:id/stats — sent, delivered, opened, clicked, bounced + rates.
  • Conversions: GET /campaigns/:id/outcomes?event=purchase&window=24 — attributed within a window after open/click.
  • Completion notifications: Slack / webhooks via integrations.

App inbox (message center)

Tick Also deliver to app inbox in the composer (or mirrorToInbox: true on POST /campaigns) and every recipient also gets a persistent inbox item — snapshotted at fan-out, so the A/B variant a subscriber received is exactly what their inbox shows, and later message edits never rewrite history. One item per subscriber regardless of device count; scheduler retries never duplicate.

For inbox-only messages (order updates, account notices) post directly: POST /v1/apps/:appId/subscribers/:id/inbox {title, body, imageUrl?, deepLink?}.

Clients read the feed with the SDK: WingBlaze.getInbox() returns {items, unread} newest-first and WingBlaze.markInboxRead(itemId) clears the badge. Journey sends don't mirror yet.

Data hosted in Southeast Asia (asia-southeast1).