Skip to content
Sendora Cloud
Create account
Engage · Push

APNs + FCM + Web Push + Live Activities + Geofences + Critical Alerts + per-org frequency caps + quiet hours + sticky-by-user A/B — one SDK, three transports.

OneSignal and Braze send pushes. Sendora's Push module ships the same transport surface (APNs cert + token, FCM, Web Push VAPID), plus Live Activities (iOS ActivityKit + Android ongoing notifications) + Geofences + Critical Alerts + per-org frequency caps + TZ-aware quiet hours + sticky A/B variant assignment + automatic invalid-token pruning. Device-takeover on signin retires the anon `user_id` so a single device doesn't get duplicate pushes. Honest about scope: audience fan-out is via Automation workflows (not a single-call `sendToAudience()` API), and the A/B test ships variant assignment + manual winner declaration — not automatic statistical readout or auto-promote.

Features

  • APNs + FCM + Web Push (VAPID) — one SDK, one send API, three transports. Cert + token auth on APNs. Lazy-generated VAPID keypair per-org on first web subscription.
  • Live Activities (iOS ActivityKit) + Live Updates (Android ongoing notifications) — push-to-update lock-screen widgets. Cross-platform via pushLiveActivities.platform discriminator.
  • Geofences (iOS + Android) — server-defined circular regions; SDK reports enter / exit / dwell back; backend writes geofence.<event> rows that Automation workflows can subscribe to.
  • Critical Alerts (iOS)aps.sound={critical:1,name,volume} + interruption-level=critical bypasses DND / Focus / silent (with the Apple entitlement + user permission).
  • Device-takeover on signIn — anon → identified flip retires the anon user_id, reassigns push tokens, hard-deletes the anon row, emits auth.device_takeover. No duplicate pushes after login.
  • Per-org delivery policiesfreqCapPerUserPerDay / freqCapPerUserPerHour (counts non-suppressed rows in window). quietHoursStartLocal / quietHoursEndLocal (0-23) evaluated against the recipient's pushTokens.timezone. Wraps midnight. deferInQuietHours → schedule for window end; otherwise → suppress with reason.
  • Sticky-by-user A/B testassignAbVariant(test.id, userId) hashes to a uniform 0-99 bucket; same user always lands on the same variant. Honest: manual winner declaration via setPushAbTestStatus(winnerKey). No automatic statistical readout, no auto-promote.
  • Templates + localizationlocalized_body: { "en": ..., "fr": ... } resolved per-recipient via pushTokens.locale (exact match → 2-letter fallback → default).
  • Rich payloads + action buttonsactions: [{ id, title, url? }] (max 4, APNs limit). Body click → push.opened; action click → push.clicked w/ action id.
  • Open + click tracking — SDK fires POST /push/track-open on tap; backend stamps pushSends.openedAt + clickedAt + clickAction + emits analytics event.
  • Invalid-token pruning — dispatch errors matching UNREGISTERED|BadDeviceToken|... flip pushTokens.isActive=false + emit push.token_invalidated. Hourly GC cron deletes inactive tokens after 30-day grace.
  • Honest non-features: no single-call sendToAudience() (fan-out via Automation workflow); no automatic Deep-Link auto-wrap on push payloads for ROAS (customer provides URL); no silent push convenience param exposed (build via raw payload); A/B test has no statistical-readout engine.

Common use cases

Replace OneSignal ($139/mo at 10K mobile MAU) + per-platform geofence vendors + Live Activity gateways with one tenant.

Multi-platform mobile apps that need APNs + FCM + Live Activities + Live Updates without four separate vendors.

Lifecycle messaging that survives signin — device-takeover collapses anon + identified to one identity, so no duplicate-push storm post-login.

Start in minutes. Scale without switching tools.

The free tier covers most side projects. Every module is turn-key and every SDK is first-party.