The strength of Sailthru Mobile's targeting and segmentation relies on knowing the correct information about your users. The more information we know about a user, the better you can target them when you send messages.

When a user installs an app, we collect a few attributes automatically. These attributes are helpful for targeting and segmenting your users.

Automatically Tracked Attributes

  • App Version
  • Language
  • Time Zone
  • Badge Number (iOS only)
  • Device ID
  • Device Make & Model
  • Operating System & Version
  • Carnival SDK version
  • Location based on IP Address
  • Notifications Allowed
  • Platform

📘

Attributes tracked automatically are refreshed at every app load.

Beyond the automatically tracked attributes, Sailthru Mobile also lets you store custom information about a user for further segmenting and targeting. This feature is called User Attributes.

Setting a User ID

If your app has a login, you can set a unique User ID with Sailthru Mobile. This allows you to associate multiple devices to a single user when targeting via the API. You may also want to collect the user's email as well.

// setting a User ID after login
[Carnival setUserId:@"user_id_1234" withResponse:^(NSError *error) {
    NSLog(@"Error setting user id – %@", error.localizedDescription);
}];

// clearing a User ID after logout
[Carnival setUserId:nil withResponse:^(NSError *error) {
    
}];
// setting a User ID after login
Carnival.setUserId("user_id_1234") { error in
    println("setUserID returned with possible error: \(error)")
}

// clearing a User ID after logout
Carnival.setUserId(nil) { error in
    println("setUserID returned with possible error: \(error)")
}
// setting a User ID after login
Carnival.setUserId("user_id_1234", new CarnivalHandler<Void> (){

    @Override
    public void onSuccess (Void value){
        //Do Something
    }

    @Override
    public void onFailure (Error error){
        //Do Something
    }
});


// clearing a User ID after logout
Carnival.setUserId(null, new CarnivalHandler<Void> (){

    @Override
    public void onSuccess (Void value){
        //Do Something
    }

    @Override
    public void onFailure (Error error){
        //Do Something
    }
});
Carnival.setUserId(
  function callback(data) {
      console.log('setUserId successfully returned');
  },
  function errorHandler(err) {
      console.log('setUserId returned error: ' + err);
  },
  'user_id_1234'
);
Carnival.SetUserId("user_id_1234);
// You can also attach the standard error hander too.

📘

Setting a User ID is optional, but it is strongly recommended. It is a prerequisite to use:

Without User ID you will be able to set attributes and events only at the device level.

Setting a User Email

Setting a user email allows to link a device to an identifier user profile in Sailthru, matching the email you provide with an existing Email ID. This is a required step to leverage omnichannel personalization using interests and other data points from the Sailthru user profile.

We recommend to collect both User ID and email.

// setting a User Email after login
[Carnival setUserEmail:@"[email protected]" withResponse:^(NSError *error) {
    NSLog(@"Error setting user email – %@", error.localizedDescription);
}];

// clearing a User Email after logout
[Carnival setUserEmail:nil withResponse:^(NSError *error) {
    NSLog(@"Error setting user email – %@", error.localizedDescription);
}];
// setting a User Email after login
Carnival.setUserEmail("[email protected]") { error in
    println("setUserEmail returned with possible error: \(error)")
}

// clearing a User Email after logout
Carnival.setUserEmail(nil) { error in
    println("setUserEmail returned with possible error: \(error)")
}
// setting a User Email after login
Carnival.setUserEmail("[email protected]", new CarnivalHandler<Void> (){

    @Override
    public void onSuccess (Void value){
        //Do Something
    }

    @Override
    public void onFailure (Error error){
        //Do Something
    }
});


// clearing a User Email after logout
Carnival.setUserEmail(null, new CarnivalHandler<Void> (){

    @Override
    public void onSuccess (Void value){
        //Do Something
    }

    @Override
    public void onFailure (Error error){
        //Do Something
    }
});
Carnival.setUserEmail(
  function callback(data) {
      console.log('setUserEmail successfully returned');
  },
  function errorHandler(err) {
      console.log('setUserEmail returned error: ' + err);
  },
  '[email protected]'
);

User Attributes

User Attributes provide a powerful & flexible way for developers to store extra metadata for the purposes of grouping, segmenting and targeting users at a later stage.

Each User/Device can have multiple attributes. User Attributes are unique to a device and app, and persist between app opens. They are set on the SDK side and saved back to the Sailthru Mobile platform with the appropriate SDK methods.

Each User Attribute has a name, a type and a value.

For example;

  • first_name (String)
  • lifetime_value (Float)
  • number_of_items_purchased (Integer)
  • is_premium_subscriber (Boolean)

The following attribute datatypes are supported;

  • Integer (32 bit)
  • Float
  • Date
  • String
  • Boolean
  • Array (of Integer, Floats, Dates and Strings)

To set some User attributes from the SDK

// Construct the object
CarnivalAttributes *attributes  = [[CarnivalAttributes alloc] init];

// Set one or more attributes
[attributes setString:@"Handbags" forKey:@"last_visited_category"];
[attributes setStrings:@[@"world", @"sports"] forKey:@"subscriptions"];
[attributes setFloat:104.87 forKey:@"customer_ltv"];
[attributes setInteger:3 forKey:@"products_in_cart"];
[attributes setFloats:@[@(36.99), @(42.3)] forKey:@"cart_items_unit_price"];
[attributes setIntegers:@[@(2), @(1)] forKey:@"cart_item_quantities"];
[attributes setBool:YES forKey:@"user_did_use_facebook_login"];
[attributes setDates:@[[NSDate date]] forKey:@"checkout_started"];

// Optional: choose if you want to add new attributes to the existing (update), or if you want to overwrite the current attributes with the new payload (replace). By default, we update.
[attributes setAttributesMergeRule:CarnivalAttributesMergeRuleUpdate];

// Send to Carnival
[Carnival setAttributes:attributes withResponse:^(NSError * _Nullable error) {
  if (error) {
    NSLog(@"Error - %@", [error debugDescription]);
  }
}];

// Remove an attribute
[Carnival removeAttributeWithKey:@"products_in_cart" withResponse:nil];
// Construct the object
let attributes = CarnivalAttributes()

// Set one or more attributes
attributes.setString("Handbags", forKey: "last_visited_category")
attributes.setStrings(["world", "sports"], forKey: "subscriptions")
attributes.setFloat(104.87, forKey: "customer_ltv")
attributes.setInteger(3, forKey: "products_in_cart")
attributes.setFloats([36.99, 42.3], forKey: "cart_items_unit_price")
attributes.setIntegers([2, 1], forKey: "cart_item_quantities")
attributes.setBool(true, forKey: "user_did_use_facebook_login")
attributes.setDates(NSDate(), forKey: "checkout_started")

// Optional: choose if you want to add new attributes to the existing (update), or if you want to overwrite the current attributes with the new payload (replace). By default, we update.
attributes.setAttributesMergeRule(.Update)

// Send to Carnival
Carnival.setAttributes(attributes) { error in
  print("setAttributes returned with possible error: \(error)")
}

// Remove an attribute
Carnival.removeAttributeWithKey("products_in_cart", nil)
// Construct the object
AttributeMap attributes = new AttributeMap();

// Set one or more attributes
attributes.putString("last_visited_category", "Handbags");

ArrayList<String> subscriptions = new ArrayList<>(Arrays.asList("world", "sports"));
attributes.putStringArray("subscriptions", subscriptions);

attributes.putFloat("customer_ltv", 104.87f);

ArrayList<Float> cartItemsUnitPrice = new ArrayList<>(Arrays.asList(36.99f, 42.3f));
attributes.putFloatArray("cart_items_unit_price", cartItemsUnitPrice);

attributes.putInt("products_in_cart", 3);

ArrayList<Integer> cartItemQuantities = new ArrayList<>(Arrays.asList(2, 1));
attributes.putIntArray("cart_item_quantities", cartItemQuantities);

attributes.putBoolean("user_did_user_facebook_login", true);
attributes.putDate("checkout_started", new Date());

// Optional: choose if you want to add new attributes to the existing (update), or if you want to overwrite the current attributes with the new payload (replace). By default, we update.
attributes.setMergeRules(AttributeMap.RULE_UPDATE);

// Send to Carnival
Carnival.setAttributes(attributes, new Carnival.AttributesHandler() {
  @Override
  public void onSuccess() {
    // Handle success here
  }

  @Override
  public void onFailure(Error error) {
    // Handle failure here
    Log.d("YOUR_LOG_TAG", "setAttributes returned with possible error: " + error.getLocalizedMessage());
  }
});

// Remove an attribute
Carnival.removeAttribute("products_in_cart");
var attributeMap = new Carnival.AttributeMap();

// Optional: choose if you want to add new attributes to the existing (update), or if you want to overwrite the current attributes with the new payload (replace). By default, we update.
attributeMap.setMergeRule(Carnival.AttributeMap.MergeRule.Update);

attributeMap.setString('last_visited_category', 'Handbags');
attributeMap.setStringArray('subscriptions', ['world', 'sports']);
attributeMap.setFloat('customer_ltv', 104.87);
attributeMap.setFloatArray('cart_items_unit_price', [36.99, 42.3]);
attributeMap.setInteger('products_in_cart', 3);
attributeMap.setIntegerArray('cart_items_quantities', [2, 1]);
attributeMap.setDate('checkout_started', new Date());
attributeMap.setDateArray('stories_last_shared', [new Date(), new Date()]);
attributeMap.setBoolean('user_did_use_facebook_login', true);

Carnival.setAttributes(
  function callback() {
    console.log('setAttributes successfully returned');
  },
  function errorHandler(err) {
    console.log('setAttributes returned error: ' + err);
  },
  attributeMap
);

// Remove an attribute
Carnival.removeAttribute(function callback(data) {
  console.log('removeAtribute successfully returned');
    },
    function errorHandler(err) { 
    console.log('removeAttribute error: ' + err);
    }, 'products_in_cart');
});
// You can use a response handler to handle any errors returned by the platform.
Carnival.OnErrorEvent += (object sender, CarnivalErrorEventArgs e) => {
    Debug.Log (e.ErrorDescription);
};

Carnival.SetAttribute(True, "User_Has_Tweeted");
Carnival.SetAttribute("Sam", "User_Name");
Carnival.SetAttribute(54, "User_Level");
Carnival.SetAttribute(4.32, "User_Average_Score");

// Removing user attributes
Carnival.RemoveAttribute ("Latest_Open");

Custom Attribute Limits

There are limits in place on the maximum number of custom attributes allowed as well as the length (size) of strings and arrays.

  • A maximum of 50 custom attributes is allowed per device. If you exceed this amount any new attributes being set will be discarded.
  • String values that have more than 255 characters will be truncated.
  • Arrays with more than 50 elements will be truncated.

Key name restrictions

  • Only letters, numbers, underscore and dashes are valid characters.
  • Leading spaces will be removed.
  • Invalid characters other than spaces and dots will be removed.
  • Spaces and dots will be replaced for underscores.
  • Keys will be truncated to a maximum of 255 characters.
  • Invalid keys will simply be discarded and no error will be returned.

📘

Custom attributes are refreshed in real time.

Custom Events

Sailthru Mobile's Custom Events provide a simple event tracking solution for measuring actions in your app. Events can be logged to track actions that users take, such as screen views, or social network shares. Unlike user attributes, events occur over time and graphs of the events can be seen within the analytics section of the Sailthru Mobile dashboard.

Sailthru Mobile keeps record of the count and the last occurrence of an event for each user, so you can target and segment based on;

  • Whether a user has performed this event or not
  • When they last performed this event
  • How often they have performed this event

To log a Custom Event

[Carnival logEvent:@"Checkout started"];
[Carnival logEvent:@"Article shared"];
Carnival.logEvent("Checkout started")
Carnival.logEvent("Article shared")
Carnival.logEvent("Checkout started");
Carnival.logEvent("Article shared");
Carnival.logEvent('Checkout started');
Carnival.logEvent('Article shared');
Carnival.LogEvent ("Checkout started");
Carnival.LogEvent ("Article shared");

📘

NOTE

The maximum amount of unique events which can be registered per device is limited to 50. Events are updated in batch, close to real-time, every few seconds.

Sending Events together with Attributes

Sometimes you may need to send particular user properties along with an action. This is useful for example when you want to track product views and keep a list of the last browsed items, or in similar scenario when you need to keep track of an action and a list of recent items.

  1. You organize data into arrays using multi-value attributes.
  2. Values in the array are sorted from most recent to the oldest. This way, you can ensure that the first item (the one at index 0) is the most recent
  3. You track a relevant event (for example, Item browsed). This way you keep track of how many times this event occurred, and the date of last occurrence is also the date of when the most recent item was browsed.

Adopting this technique has some advantages:

  • You can keep a recent history of the user's activity
  • You can keep track of the date of last occurrence for both the latest item and the latest event
  • You don't pollute the attribute/event space by setting unnecessary attributes, which means you can get extra room to store more attributes in the future.

To do so, you can simply send an event, then take advantage of multi-value attributes to store the last item information.

// You are storing a list of recent items in a NSArray of any given type
// (strings in this example).

NSString *item = @"NEW_ITEM_ID";
NSArray *items = @[@[item] arrayByAddingObjectsFromArray:previousItems];


[Carnival logEvent:@"Item browsed"];

CarnivalAttributes *attributes  = [[CarnivalAttributes alloc] init];
[attributes setStrings:items forKey:@"item_id"];

// Send to Carnival
[Carnival setAttributes:attributes withResponse:^(NSError * _Nullable error) {
  if (error) {
    NSLog(@"Error - %@", [error debugDescription]);
  }
}];
// You are storing a list of recent items in an array of any given type
// (strings in this example).

let item = "NEW_ITEM_ID"
let items = previousItems.insert(item, at: 0);

Carnival.logEvent("Item browsed")
let attributes = CarnivalAttributes()
attributes.setStrings(items, forKey: "item_id")
Carnival.setAttributes(attributes) { error in
  print("setAttributes returned with possible error: \(error)")
}
// You are storing a list of recent items in an ArrayList of any given type
// (strings in this example).


String item = "NEW_ITEM_ID";
ArrayList<String> items = previousItems.add(0, item);

Carnival.logEvent("Item browsed");
AttributeMap attributes = new AttributeMap();
attributes.putStringArray("item_id", items);

// Send to Carnival
Carnival.setAttributes(attributes, new Carnival.AttributesHandler() {
  @Override
  public void onSuccess() {
    // Handle success here
  }

  @Override
  public void onFailure(Error error) {
    // Handle failure here
    Log.d("YOUR_LOG_TAG", "setAttributes returned with possible error: " + error.getLocalizedMessage());
  }
});
// You are storing a list of recent items in an array.

let item = 'NEW_ITEM_ID';
items.unshift(item);

Carnival.logEvent('Item browsed');
let attributeMap = new Carnival.AttributeMap();
attributeMap.setStringArray('item_id', items);

Carnival.setAttributes(
  function callback() {
    console.log('setAttributes successfully returned');
  },
  function errorHandler(err) {
    console.log('setAttributes returned error: ' + err);
  },
  attributeMap
);

Auto-Analytics Tracking

On iOS

The Sailthru Mobile iOS SDK automatically integrates with other analytics providers to capture some analytics. At the moment we capture only event data. This means that if you use the frameworks below to log events, they'll also be logged to Sailthru Mobile as an event also.

The providers we integrate with are:

  • Google Analytics
  • Mixpanel
  • Adobe Analytics (Formerly Omniture)
  • Localytics
  • Amplitutde
  • Flurry

Auto-Analytics is opt-in. To enable Auto-Analytics, use the enableAutoAnalytics method.

//Call as early as possible, perhaps straight after startEngine

[Carnival enableAutoAnalytics:@[CarnivalAutoAnalyticsSourceGoogleAnalytics, CarnivalAutoAnalyticsSourceAdobe, CarnivalAutoAnalyticsSourceMixpanel, CarnivalAutoAnalyticsSourceLocalytics]];
//Call as early as possible, perhaps straight after startEngine

Carnival.enableAutoAnalytics([CarnivalAutoAnalyticsSourceGoogleAnalytics, CarnivalAutoAnalyticsSourceAdobe, CarnivalAutoAnalyticsSourceMixpanel, CarnivalAutoAnalyticsSourceLocalytics])

Methods captured

Google Analytics
  • + (id)createEventWithCategory:(NSString *)category action:(NSString *)action label:(NSString *)label value:(NSString *)value

We discard category, label and value, and log a Sailthru Mobile event with the action as the name.

Adobe Analytics
  • + (void)trackAction:(NSString *)action data:(id)data
    We discard data and log a Sailthru Mobile event with the action as the name.
Mixpanel
  • - (void)track:(NSString *)event properties:(NSString *)properties
    We discard properties and log a Sailthru Mobile event with the event as the name.
Localytics
  • - (void)tagEvent:(NSString *)eventName attributes:(NSDictionary *)attributes customerValueIncrease:(NSNumber *)customerValueIncrease;

We discard attributes and customerValueIncrease, and log a Sailthru Mobile event with the eventName as the name.

Flurry
  • + (NSInteger)logEvent:(NSString *)eventName withParameters:(NSDictionary *)parameters timed:(BOOL)timed
  • + (NSInteger)logEvent:(NSString *)eventName timed:(BOOL)timed

We discard eventName, parameters and timed, and log a Sailthru Mobile event with the eventName as the name.

Amplitude
  • - (void)logEvent:(NSString *)eventType withEventProperties:(NSDictionary *)eventProperties withApiProperties:(NSDictionary *)apiProperties withUserProperties:(NSDictionary *)userProperties withGroups:(NSDictionary *)groups withTimestamp:(NSNumber *)timestamp outOfSession:(BOOL)outOfSession (Other event methods call this method)

We discard most parameters, and log a Sailthru Mobile event with the eventType as the name.

On Android

For Auto-Analytics Tracking on Android, the Carnival.logEvent() call now takes a source parameter for when forwarding events from other analytics frameworks to Sailthru Mobile. This allows you to target based on events you already track.

Carnival.logEvent("source", "myEvent");

A selection of pre-written integrations have been provided, allowing you to just include one file, replace your event logging calls and then turn on or off the frameworks you want to use by commenting them out in the source file provided.

📘

User Attributes or Custom Events?

User Attributes don't record events that happen over time, and don't appear in graphs in the platform. They are simply metadata for a user. For recording events that happen over time, so you can target users by behavior, you should use our custom events feature.

Tracking Location

By default the Sailthru Mobile platform collects a last known IP location for each user. This can be used for coarse location segmentation and targeting with no extra development effort or permissions in your app.

Depending on local laws, you may need to obtain the express consent from your app users in order to track IP location.

[Carnival setGeoIPTrackingEnabled:NO];
Carnival.setGeoIPTrackingEnabled(false)
Carnival.setGeoIpTrackingEnabled(false);

In order to support more granular location, it is up to the app to decide and manage the accuracy of capturing location information, while considering the user's battery life. It's best practice to use only the accuracy you need for messaging (Block, City, State, Country).

Location updates can then be passed through to Sailthru Mobile for segmentation.

// On iOS, using Objective-C
[Carnival updateLocation:myLocation]; //Takes an instance of a CLLocation object as a argument.
// On iOS, using Swift
Carnival.updateLocation(myLocation) //Takes an instance of a CLLocation object as a argument.
// On iOS, using Java
Carnival.updateLocation(myLocation); //Takes an instance of a Location object as a argument.
// On Cordova, using JavaScript
Carnival.updateLocation(lat, lon); //Takes lattitude and longitude as arguments
// On Cordova, using C#
Carnival.UpdateLocation (lat, lon); //Takes lattitude and longitude as arguments

Below are some short tutorials for collecting a user's location on Both iOS and Android:

Clearing Device Data

At times, such as a logging out flow or a user settings screen, you might want to clear device data. You're able to clear data for Events, Custom Attributes and Message Stream.

Warning: By clearing events or custom attributes, the device may become eligible for an automated message triggered based on leaving an audience. Double check your set up before using this method.

[Carnival clearDeviceData:CarnivalDeviceDataTypeEvents | CarnivalDeviceDataTypeAttributes | CarnivalDeviceDataTypeMessageStream withResponse:^(NSError * _Nullable error) { 
  // Possible error here
}];
Carnival.clearDeviceData(.Attributes | .MessageStream | .Events) { (error) in
      // Possible error here
  }
//Carnival.ATTRIBUTES|Carnival.MESSAGE_STREAM|Carnival.EVENTS
Carnival.clearDevice(Carnival.CLEAR_ALL, new Carnival.CarnivalHandler<Void>() {
    @Override
    public void onSuccess(Void value) {

    }

    @Override
    public void onFailure(Error error) {

    }
});
// Clear one or more types. Specify one or more of these values.
Carnival.clearDevice(
  Carnival.DeviceValues.Attributes | 
  Carnival.DeviceValues.MessageStream |
  Carnival.DeviceValues.Events);

// Clear all device data
Carnival.clearDevice(Carnival.DeviceValues.ClearAll);