Sailthru Specific Functionality
A breakdown of the Sailthru specific functionality in the EngageBySailthru class
If you are a Sailthru customer there are some additional methods you can use to set details about a device or profile. This functionality used to be provided in the main SailthruMobile
class but has now been moved out to the EngageBySailthru
class as it is only available for this product.
Creating an instance
When creating an instance of the EngageBySailthru
class you must ensure that you have already called startEngine
or an error will be thrown. If you are not a Sailthru customer this class will also return an error, as this functionality is only available on the Sailthru product.
NSError *error;
EngageBySailthru *engageBySailthru = [[EngageBySailthru alloc] initWithError:&error];
if (error) {
// Handle error
}
do {
let engageBySailthru = try EngageBySailthru()
...
} catch {
// Handle error
}
try {
EngageBySailthru engageBySailthru = new EngageBySailthru();
...
} catch(IllegalStateException e) {
// Called before start engine
} catch(IllegalArgumentException e) {
// Functionality not available
}
try {
val engageBySailthru = EngageBySailthru()
...
} catch (e: IllegalStateException) {
// Called before start engine
} catch (e: IllegalArgumentException) {
// Functionality not available
}
In the wrapper SDKs (React Native and Unity) these errors will be returned when attempting to call methods on the EngageBySailthru
class.
The strength of Marigold'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
- Marigold SDK version
- Location based on IP Address
- Notifications Allowed
- Platform
Attributes tracked automatically are refreshed at every app load.
Beyond the automatically tracked attributes, Marigold 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 Marigold. 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
[[EngageBySailthru new] setUserId:@"user_id_1234" withResponse:^(NSError *error) {
NSLog(@"Error setting user id – %@", error.localizedDescription);
}];
// clearing a User ID after logout
[[EngageBySailthru new] setUserId:nil withResponse:^(NSError *error) {
}];
// setting a User ID after login
EngageBySailthru().setUserId("user_id_1234") { error in
print("setUserID returned with possible error: \(error)")
}
// clearing a User ID after logout
EngageBySailthru().setUserId(nil) { error in
print("setUserID returned with possible error: \(error)")
}
// setting a User ID after login
new EngageBySailthru().setUserId("user_id_1234", new Marigold.MarigoldHandler<Void> (){
@Override
public void onSuccess (Void value){
//Do Something
}
@Override
public void onFailure (Error error){
//Do Something
}
});
// clearing a User ID after logout
new EngageBySailthru().setUserId(null, new Marigold.MarigoldHandler<Void> (){
@Override
public void onSuccess (Void value){
//Do Something
}
@Override
public void onFailure (Error error){
//Do Something
}
});
// setting a User ID after login
EngageBySailthru().setUserId("user_id_1234", object : MarigoldHandler<Void?> {
override fun onSuccess(value: Void?) {
//Do Something
}
override fun onFailure(error: Error?) {
//Do Something
}
})
// clearing a User ID after logout
EngageBySailthru().setUserId(null, object : MarigoldHandler<Void?> {
override fun onSuccess(value: Void?) {
//Do Something
}
override fun onFailure(error: Error?) {
//Do Something
}
})
// setting a User ID after login
EngageBySailthru.setUserId("user_id_1234");
// clearing a User ID after logout
EngageBySailthru.setUserId("");
// setting a User ID after login
new EngageBySailthru().SetUserId("user_id_1234");
// clearing a User ID after logout
new EngageBySailthru().SetUserId(null);
Setting a User ID is optional, but it is strongly recommended. It is a prerequisite to use:
- Sending notifications to specific users, including using the Notifications API with
user_id
audiences- Users API
- Users Events API
- Audience creation via CSV
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 Marigold, 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 Marigold user profile.
We recommend to collect both User ID and email.
// setting a User Email after login
[[EngageBySailthru new] setUserEmail:@"[email protected]" withResponse:^(NSError *error) {
NSLog(@"Error setting user email – %@", error.localizedDescription);
}];
// clearing a User Email after logout
[[EngageBySailthru new] setUserEmail:nil withResponse:^(NSError *error) {
NSLog(@"Error setting user email – %@", error.localizedDescription);
}];
// setting a User Email after login
EngageBySailthru().setUserEmail("[email protected]") { error in
print("setUserEmail returned with possible error: \(error)")
}
// clearing a User Email after logout
EngageBySailthru().setUserEmail(nil) { error in
print("setUserEmail returned with possible error: \(error)")
}
// setting a User Email after login
new EngageBySailthru().setUserEmail("[email protected]", new Marigold.MarigoldHandler<Void> (){
@Override
public void onSuccess (Void value){
//Do Something
}
@Override
public void onFailure (Error error){
//Do Something
}
});
// clearing a User Email after logout
new EngageBySailthru().setUserEmail(null, new Marigold.MarigoldHandler<Void> (){
@Override
public void onSuccess (Void value){
//Do Something
}
@Override
public void onFailure (Error error){
//Do Something
}
});
// setting a User Email after login
EngageBySailthru().setUserEmail("[email protected]", object : MarigoldHandler<Void?> {
override fun onSuccess(value: Void?) {
//Do Something
}
override fun onFailure(error: Error?) {
//Do Something
}
})
// clearing a User Email after logout
EngageBySailthru().setUserEmail(null, object : MarigoldHandler<Void?> {
override fun onSuccess(value: Void?) {
//Do Something
}
override fun onFailure(error: Error?) {
//Do Something
}
})
// setting a User Email after login
EngageBySailthru.setUserEmail("[email protected]");
// clearing a User Email after logout
EngageBySailthru.setUserEmail("");
// setting a User Email after login
new EngageBySailthru().SetUserEmail("[email protected]");
// clearing a User Email after logout
new EngageBySailthru().SetUserEmail(null);
User Attributes (deprecated - use Profile Vars instead)
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 Marigold 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
MARAttributes *attributes = [[MARAttributes 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:MARAttributesMergeRuleUpdate];
EngageBySailthru *engageBySailthru = [EngageBySailthru new];
// Send to Sailthru Mobile
[engageBySailthru setAttributes:attributes withResponse:^(NSError * _Nullable error) {
if (error) {
NSLog(@"Error - %@", [error debugDescription]);
}
}];
// Remove an attribute
[engageBySailthru removeAttributeWithKey:@"products_in_cart" withResponse:nil];
// Remove all attributes
[engageBySailthru clearAttributesWithResponse:^(NSError * _Nullable error) {
if (error) {
NSLog(@"Error - %@", [error debugDescription]);
}
}];
// Construct the object
let attributes = MARAttributes()
// 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)
let engageBySailthru = EngageBySailthru()
// Send to Sailthru Mobile
engageBySailthru.setAttributes(attributes) { error in
print("setAttributes returned with possible error: \(error)")
}
// Remove an attribute
engageBySailthru.removeAttributeWithKey("products_in_cart", nil)
// Remove all attributes
engageBySailthru.clearAttributes() { error in
print("clearAttributes returned with possible error: \(error)")
}
// 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);
EngageBySailthru engageBySailthru = new EngageBySailthru();
// Send to Marigold
engageBySailthru.setAttributes(attributes, new EngageBySailthru.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
engageBySailthru.removeAttribute("products_in_cart");
// Remove all attributes
engageBySailthru.clearAttributes(new Marigold.MarigoldHandler<>() {
@Override
public void onSuccess(Void value) {
// Handle success here
}
@Override
public void onFailure(@NonNull Error error) {
// Handle failure here
Log.d("YOUR_LOG_TAG", "clearAttributes returned with possible error: " + error.getLocalizedMessage());
}
});
// Construct the object
val attributes = AttributeMap()
// Set one or more attributes
attributes.putString("last_visited_category", "Handbags")
val subscriptions: ArrayList<String> = ArrayList(Arrays.asList("world", "sports"))
attributes.putStringArray("subscriptions", subscriptions)
attributes.putFloat("customer_ltv", 104.87f)
val cartItemsUnitPrice: ArrayList<Float> = ArrayList(Arrays.asList(36.99f, 42.3f))
attributes.putFloatArray("cart_items_unit_price", cartItemsUnitPrice)
attributes.putInt("products_in_cart", 3)
val cartItemQuantities: ArrayList<Int> = ArrayList(Arrays.asList(2, 1))
attributes.putIntArray("cart_item_quantities", cartItemQuantities)
attributes.putBoolean("user_did_user_facebook_login", true)
attributes.putDate("checkout_started", 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)
val engageBySailthru = EngageBySailthru()
// Send to Marigold
engageBySailthru.setAttributes(attributes, object : EngageBySailthru.AttributesHandler {
override fun onSuccess() {
// Handle success here
}
override fun onFailure(error: Error?) {
// Handle failure here
Log.d("YOUR_LOG_TAG", "setAttributes returned with possible error: " + error!!.localizedMessage)
}
})
// Remove an attribute
engageBySailthru.removeAttribute("products_in_cart")
// Remove all attributes
engageBySailthru.clearAttributes(object : Marigold.MarigoldHandler<Void?> {
override fun onSuccess(value: Void?) {
// Handle success here
}
override fun onFailure(error: Error) {
// Handle failure here
Log.d("YOUR_LOG_TAG", "clearAttributes returned with possible error: " + error.localizedMessage)
}
})
// Construct the object
var attrMap = new EngageBySailthru.AttributeMap();
// Set one or more attributes
attrMap.setString("string_key", "This is the string value");
attrMap.setStringArray("strings_key", ["This is first value", "This is the second value"]);
attrMap.setDate("date_key", new Date());
attrMap.setDateArray("dates_key", [new Date(), new Date(), new Date()]);
attrMap.setFloat("float_key", 3.141);
attrMap.setFloatArray("floats_key", [1.1, 2.2, 3.3, 4.4]);
attrMap.setInteger("integer_key", 3);
attrMap.setIntegerArray("integers_key", [1, 2, 3, 4]);
attrMap.setBoolean("boolean_key", true);
// 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.
attrMap.setMergeRule(attrMap.MergeRules.Update);
// Send to Sailthru Mobile
EngageBySailthru.setAttributes(attrMap).catch(e => {
// Handle error
});
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.
Profile Vars
Profile Vars can be set and retrieved through the SDK. This makes maintaining state between your email/on-site campaigns and your mobile marketing easy. Note that the use of this feature requires that your app be linked to Sailthru Email/On-Site.
Vars should be set in a JSON object format:
// Setup profile vars object
NSDictionary *profileVars = @{
@"string_key" : @"string_value",
@"object_key" : @{},
@"boolean_key" : @YES
};
// Set profile vars
[[EngageBySailthru new] setProfileVars:profileVars withResponse:^(NSError * _Nullable error) {
// Check if error is non-nil for result
}];
// Setup profile vars object
let profileVars : [String : Any] = [
"string_key" : "string_value",
"object_key" : [],
"boolean_key" : true
]
// Set profile vars
EngageBySailthru().setProfileVars(profileVars) { (error : Error?) in
// Check if error is non-nil for result
}
// Setup profile vars object
JSONObject profileVars = new JSONObject();
profileVars.put("string_key", "string_value");
profileVars.put("object_key", new JSONObject());
profileVars.put("boolean_key", true);
// Set profile vars
new EngageBySailthru().setProfileVars(profileVars, new Marigold.MarigoldHandler<Void>() {
@Override
public void onSuccess(Void value) {
// Handle success
}
@Override
public void onFailure(Error error) {
// Handle error
}
});
// Setup profile vars object
val profileVars = JSONObject()
profileVars.put("string_key", "string_value")
profileVars.put("object_key", JSONObject())
profileVars.put("boolean_key", true)
// Set profile vars
EngageBySailthru().setProfileVars(profileVars, object : MarigoldHandler<Void?> {
override fun onSuccess(value: Void?) {
// Handle success
}
override fun onFailure(error: Error?) {
// Handle error
}
})
// Setup profile vars object
var profileVars = {
"string_key" : "string_value",
"object_key" : {},
"boolean_key": true
};
// Set profile vars
EngageBySailthru.setProfileVars(profileVars).then(result => {
// Handle success
}).catch(e => {
// Handle error
});
JSONClass jsonObject = new JSONClass();
jsonObject["string_key"] = "string_value";
jsonObject["object_key"] = new JSONClass();
jsonObject["array_key"] = new JSONArray();
jsonObject["int_key"].AsInt = 1234;
jsonObject["bool_key"].AsBool = false;
new EngageBySailthru().SetProfileVars(jsonObject);
When retrieving vars they will be returned in the same JSON object format:
[[EngageBySailthru new] getProfileVarsWithResponse:^(NSDictionary<NSString *,id> * _Nullable profileVars, NSError * _Nullable error) {
// Handle profileVars object or error
}];
EngageBySailthru().getProfileVars { (profileVars : [String : Any]?, error : Error?) in
// Handle profileVars object or error
}
new EngageBySailthru.getProfileVars(new Marigold.MarigoldHandler<JSONObject>() {
@Override
public void onSuccess(JSONObject profileVars) {
// Handle profileVars object
}
@Override
public void onFailure(Error error) {
// Handle error
}
});
EngageBySailthru().getProfileVars(object : MarigoldHandler<JSONObject?> {
override fun onSuccess(profileVars: JSONObject?) {
// Handle profileVars object
}
override fun onFailure(error: Error?) {
// Handle error
}
})
EngageBySailthru.getProfileVars().then(profileVars => {
// Handle profileVars object
}).catch(e => {
// Handle error
});
EngageBySailthru.OnProfileVarsReceivedEvent += (object sender, ProfileVarsReceivedEventArgs args) => {
// Handle profileVars object
};
new EngageBySailthru().GetProfileVars();
Variable names
- Are case sensitive. For example, if you create a variable named “Survey_Score” it will not be accessible using all lowercase letters.
- May not start with a number or be only numbers. For example, don’t upload phone numbers using var name “#”.
- May not contain special characters (such as %, -, or $). These characters will be scrubbed from variable names upon import.
- May not contain spaces. If the variable name is submitted with a space, it will be converted to an underscore.
Date and Time Formats
- In order to maintain strict JSON compatibility, Marigold vars do not support a native date or time type. Instead, if you use specific naming and formatting conventions, Marigold will treat these values as dates and times throughout our system, for example, when performing queries in Audience Builder.
For more information on Vars, see the User Variables docs
Custom Events
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 Marigold dashboard.
A record is kept 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
These events are also passed through to the Lifecycle Optimiser where they can be used to build powerful workflows for both mobile and email.
To log a Custom Event
EngageBySailthru *engageBySailthru = [EngageBySailthru new];
[engageBySailthru logEvent:@"Checkout started"];
[engageBySailthru logEvent:@"Article shared"];
// with Vars
NSDictionary* eventVars = @{ @"itemCount" : @3 };
[engageBySailthru logEvent:@"Checkout started" withVars:eventVars];
let engageBySailthru = EngageBySailthru()
engageBySailthru.logEvent("Checkout started")
engageBySailthru.logEvent("Article shared")
// with Vars
let eventVars = [ "itemCount" : 3 ]
engageBySailthru.logEvent("Checkout started", withVars: eventVars)
EngageBySailthru engageBySailthru = new EngageBySailthru();
engageBySailthru.logEvent("Checkout started");
engageBySailthru.logEvent("Article shared");
// with Vars
JSONObject eventVars = new JSONObject().put("itemCount", 3);
engageBySailthru.logEvent("Checkout started", eventVars);
val engageBySailthru = EngageBySailthru()
engageBySailthru.logEvent("Checkout started")
engageBySailthru.logEvent("Article shared")
// with Vars
val eventVars = JSONObject().put("itemCount", 3)
engageBySailthru.logEvent("Checkout started", eventVars)
EngageBySailthru.logEvent("Checkout started");
EngageBySailthru.logEvent("Article shared");
// with Vars
var eventVars = {
"itemCount" : 3
};
EngageBySailthru.logEvent("Checkout started", eventVars);
EngageBySailthru engageBySailthru = new EngageBySailthru();
engageBySailthru.LogEvent("Checkout started");
engageBySailthru.LogEvent("Article shared");
// with Vars
JSONClass eventVars = new JSONClass()
eventVars["itemCount"].AsInt = 3;
engageBySailthru.LogEvent("Checkout started", eventVars);
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.
Auto-Analytics Tracking
On iOS
The Marigold 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 Marigold 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
[[EngageBySailthru new] enableAutoAnalytics:@[MARAutoAnalyticsSourceGoogleAnalytics, MARAutoAnalyticsSourceAdobe, MARAutoAnalyticsSourceMixpanel, MARAutoAnalyticsSourceLocalytics]];
//Call as early as possible, perhaps straight after startEngine
EngageBySailthru().enableAutoAnalytics([MARAutoAnalyticsSourceGoogleAnalytics, MARAutoAnalyticsSourceAdobe, MARAutoAnalyticsSourceMixpanel, MARAutoAnalyticsSourceLocalytics])
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 Marigold event with the action
as the name
.
Adobe Analytics
+ (void)trackAction:(NSString *)action data:(id)data
We discarddata
and log a Marigold event with theaction
as thename
.
Mixpanel
- (void)track:(NSString *)event properties:(NSString *)properties
We discardproperties
and log a Marigold event with theevent
as thename
.
Localytics
- (void)tagEvent:(NSString *)eventName attributes:(NSDictionary *)attributes customerValueIncrease:(NSNumber *)customerValueIncrease;
We discard attributes
and customerValueIncrease
, and log a Marigold 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 Marigold 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 Marigold event with the eventType
as the name
.
On Android
For Auto-Analytics Tracking on Android, the logEvent()
call now takes a source parameter for when forwarding events from other analytics frameworks to Marigold. This allows you to target based on events you already track.
new EngageBySailthru().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.
Updated 6 months ago