Skip to content

Context Push

This page explains how to use ContextSDK to increase the open-rate of your mobile push notifications.

Overview

  • Step 1: Add ContextSDK to your app
  • Step 2: Ship app into production in Calibration Mode
  • Step 3: Once the model is ready, you'll get a message and can choose the Over-The-Air rollout

How does it work?

  • Your existing push notification provider / engagement platform sends a background push notification
  • Your app briefly wakes up for a few seconds to detect the user's real-world context
  • During calibration phase, your notifications will always be delivered the moment it's received
  • Once your custom model is ready, we deploy a CoreML machine learning model to your app
  • From that moment on, your notifications will be shown at the optimal time for the highest open-rate
  • You have control over the time span in which the notification is shown (e.g. best moment within 12 hours)
  • Use Context Push for non-time-sensitive notifications, such as news, promotions, or reminders

Impact on your app

Less than 0.2% CPU Usage

0.6 MB Memory Usage

Adds less than 700kb to your app's binary size

No PII processed or stored

No app permissions required

Operates without ATT

Installation

Register here to get your license key, then add the SDK to your app:

Add https://github.com/context-sdk/context-sdk-releases as dependency.

Add the following dependency to your Podfile and run pod install

pod 'ContextSDK'

  1. Download the latest release: https://storage.googleapis.com/de73e410-context-sdk-releases/latest/ContextSDK.zip
  2. Drag & Drop the ContextSDK.xcframework folder into the Xcode file list
  3. Go to your project settings, scroll down to Frameworks, Libraries, and Embedded Content, add ContextSDK.xcframework, and select Embed & Sign

If you want to download a specific version, you can replace latest with the desired version number, e.g. https://storage.googleapis.com/de73e410-context-sdk-releases/3.1.0/ContextSDK.zip

Usage

In your AppDelegate.swift, the following changes are needed:

didFinishLaunchingWithOptions:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool 
{
  // Update the CustomerIO profile attribute to set if background processing is allowed
  // (replace this with your existing `identify` call, if you have this somewhere else, that is okay also)
  CustomerIO.shared.identify(userId: email, traits: ["ctx_background_push_allowed": UIApplication.shared.backgroundRefreshStatus == .available])

  // If you don't already have this line
  UNUserNotificationCenter.current().delegate = self

  // Your other code here

  return true
}
userNotificationCenter:
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) 
{
  let userInfo = response.notification.request.content.userInfo
  // You can call this for every notification, we automatically filter out the ones not relevant
  ContextManager.handleNotificationClicked(userInfo: userInfo)
  completionHandler()
}
didReceiveRemoteNotification:
func application(_ application: UIApplication, 
                     didReceiveRemoteNotification userInfo: [AnyHashable : Any],
                     fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void)
{
  // Update the CustomerIO profile attribute "ctx_last_processed" to be the current date
  CustomerIO.shared.identify(userId: email, traits: ["ctx_last_processed": round(Date().timeIntervalSince1970)])

  if application.applicationState == .background || application.applicationState == .inactive {
    if let title = userInfo["ctx_title"] as? String,
       let body = userInfo["ctx_body"] as? String,
       let collapseId = userInfo["ctx_collapse_id"] as? String,
       let flowName = userInfo["ctx_flow_name"] as? String,
       let notificationDeadlineH = userInfo["ctx_notification_deadline_h"] as? Int
    {
      let pushContent = UNMutableNotificationContent()
      pushContent.title = title
      pushContent.body = body
      ContextManager.deliverNotificationIfGoodMoment(
          flowName: flowName,
          collapseId: collapseId,
          notificationContent: pushContent,
          notificationDeadlineH: notificationDeadlineH,
          completionHandler: completionHandler
      )
    } else {
      print("Error parsing userInfo of notification: \(userInfo)")
      completionHandler(.failed)
    }
  } else {
    // Your app is already running, you can still decide to show the notification
  }
}

Enable Remote Notification entitlement

In your Xcode project, go to Signing & Capabilities and enable the Remote Notification capability in the Background Modes section.

Update your push notification payload

Update your push notification payload of your push notification provider to include the following details:

{
  "aps": {
    "alert": { 
    },
    "content-available": 1
  },
   "ctx_title": "Daily Reminder",
   "ctx_body": "Get back to learning, {{ customer.email }} 4",
   "ctx_flow_name": "daily_reminder",
   "ctx_collapse_id": "{{ 'now' | date: "%B %-d, %Y" }}",
   "ctx_notification_deadline_h": 12
}

Explanation of the fields:

  • ctx_title & ctx_body: The title and body of the notification. This isn't used by ContextSDK directly, but is used by you in the didReceiveRemoteNotification method to create the UNMutableNotificationContent. You can add any other fields you need to create your notification (e.g. attachments, sound, etc.)
  • ctx_flow_name: The name of the flow (e.g. daily_reminder) describing what type of notification this is. You will get a custom model for each ctx_flow_name you use in your app
  • ctx_collapse_id: The collapse ID of the notification, which is used to only show a single notification per ctx_collapse_id at the perfect time. For a daily reminder you'd change this every day, for re-engagement campaigns you may change this every time you change your content.
  • ctx_notification_deadline_h: The time span in which the notification should be shown in hours. The latest time the notification will be shown is ctx_notification_deadline_h hours after the first background push notification was received on the users device. Keep in mind that this still doesn't guarantee the user will see the notification.

For example, if you want to leverage context-aware push notifications for your daily reminders, you should use the current date as the ctx_collapse_id and the ctx_flow_name to daily_reminder. If your logic is to show the notification anytime between 9am to 9pm, you'd set ctx_notification_deadline_h to 12 and start sending the notifications hourly at 9am.

Things to consider

  • The message will be delayed by around 5 seconds, due to the app detecting the real-world context
  • Due to the nature of iOS push notifications, there is no guarantee that your app gets woken up for every notification. We recommend setting a ctx_last_processed attribute in your user profile to track the last time a notification was processed. This way, you can fallback to static notifications if the user hasn't opened the app in a while.

Go Live

Now all that's left is to ship your update to the App Store to start calibrating your model