Appearance
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.
- Recurrence —
dailyorweekly; 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 soonMulti-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.