Basics
Introduction
You can personalize your push notifications from the campaign editor using the data you have collected on your users. Batch provides a system of dynamic contents and a templating engine, allowing you to make dynamic messages for your campaigns.
Using the templating engine, you can reference and use user data inside your message. You can also add conditions to a message so that the content changes if some condition is fullfilled.
Here's a quick example to see how a dynamic content with a user attribute looks.
Dynamic Content
Before diving deep let's review the dynamic contents.
We have implemented a dedicated interface to allow you to use user attributes in dynamic content and display custom data easily. When editing your message, just click on the {...} button, choose the custom attribute you want to personalize your message with and the formatting.
That's it!
As some of the targeted users might not possess the attribute you are using, you can add a default value. The preview will let you see how the notification will look for 10 random installs.
Referencing attributes in your messages
If you are using APIs to send your notifications, here is the syntax to use dynamic contents in a message. All attributes usable in a query are available in dynamic contents.
They are:
attribute
orc.attribute
will refer to an installation attribute, collected from the SDK.u.attribute
will refer to a user attribute, collected via the Custom Data API.
You're only on level {{ c.current_level }}, come back and play !
If the user's c.current_level
attribute is 6
this evaluates to:
You're only on level 6, come back and play !
Referencing properties in object attributes
If you have an object attribute, you can reference its properties using the dot notation.
Suppose you have an attribute address
that is an object with the following properties:
{
"city": "Paris",
"zip_code": "75001",
"street": "1 rue de Rivoli"
}
You can reference the properties like this:
You live in {{ address.city }}, {{ address.zip_code }} {{ address.street }}
Please note that, for now, object attributes are only accessible through event attributes.
Default values
Note that with the previous dynamic content, if the user doesn't have the attribute the resulting message will be:
You're only on level, come back and play !
This is probably not what you want, so you can add a default value. The default value is used when the attribute doesn't exist.
Here is how to use them:
Special offer: Get {{ u.special_offer|default('-5%') }} by subscribing today !
If the user's u.special_offer
attribute is -15%
then it evaluates to:
Special offer: Get -15% by subscribing today !
Otherwise it evaluates to:
Special offer: Get -5% by subscribing today !
Referencing tag collections
All tag collections usable in a query are available in a dynamic content.
They are:
t.tag
will refer to an installation tag collection.ut.tag
will refer to a user tag collection.
However, there's a catch: since a tag is a collection of values and we can only output a single value using a dynamic content, we have to introduce the concept of filters. A filter is a function you can apply to a value to transform it.
In the case of a tag collection you can use the filter join
to concatenate all values into a single string.
For example:
You've already beaten those levels: {{ t.levels_done|join(',') }}
We will see more examples later, but you can check out the reference on filters to learn all about them.
Note that using a filter on a tag collection that doesn't exist always produces an empty string. Using the previous example, if the tag collection t.levels_done
doesn't exist the dynamic content evaluates to:
You've already beaten those levels:
Loops are also available for tag collections, you can iterate over the values of a tag collection using the for
loop, as explained in the loop section.
Formatting
Using raw data like this is great but you might want to format the attributes yourself.
Formatting is done by using the following filters:
- formatDate to format a date attribute
- formatNumber to format a number attribute
The two filters are explained in details in the reference but here is a small example:
You have accumulated {{ u.points|formatNumber(decimals=2) }} points, make sure to use them before {{ u.points_expiration_date|formatDate('yyyy-MM-dd') }}
This evaluates to:
You have accumulated 20.68 points, make sure to use them before 2017-02-30
Be sure to check out the reference documentation to learn about all options !
Templating
Dynamic contents are already really powerful but they're not always enough.
For example, what if you want to display a completely different message based on which level a user is on, or how much fidelity points he has ?
This is a job for templates.
Templates allow you to do conditional statements, define variables and even do some light arithmetic.
Conditions
Suppose you want to congratulate a user based on how far he is into your game.
You could write something like this:
{% if current_level > 3 %}
Good job on beating level 3 ! You're now halfway through the game, keep pushing !
{% else if current_level > 5 %}
Almost there ! One more level and you beat the game !
{% else if current_level == 6 %}
Amazing ! You've beat the game ! Go take a look at the amazing perks you unlocked !
{% endif %}
Now depending on the value of the user's current_level
attribute, the message will be one of the 3 possibilities.
Loops
You might want to exploit a list of values, for example to display a list of products a user has bought.
for
loops are here for you, you can iterate over a list of values (like a tag collection or array attribute) and display them.
{% for $product in product_list %}
You've bought {{ $product.name }} for {{ $product.price }}$ !
{% endfor %}
Variables and arithmetic
Sometimes it might be handy to compute some value and keep a reference to it so you can reuse it multiple times inside your template.
It is mainly a quality of life improvement but still useful.
For example, suppose you want to compute an expiration date based on multiple user attributes and remind the user when their subscription will expire.
Given the following rules:
- premium users have 90-days subscription
- users subscribed to the newsletter have a 75-days subscription
- users younger than 25 and not premium have a 60-days subscription
- all other have a 50-days subscription
You could write something like this:
{% if premium %}
{% set $expiration_date = c.subscription_date + 90d %}
{% else if c.has_newsletter_subscription %}
{% set $expiration_date = c.subscription_date + 75d %}
{% else if c.age < 25 %}
{% set $expiration_date = c.subscription_date + 60d %}
{% else %}
{% set $expiration_date = c.subscription_date + 50d %}
{% endif %}
Hey {{ c.first_name ~ c.last_name }}, friendly reminder that your subscription will expire on {{ $expiration_date|formatDate('yyyy-MM-dd') }}
This evaluates to:
Hey John Smith, friendly reminder that your subscription will expire on 2018-01-03
Obviously the date will change based on what kind of subscription the user has.
There are a couple of new features here:
- Defining a variable with
set $variableName = <expression>
. A valid expression has to return a single value of any type. More about here - Arithmetic. You can do simple math inside a template. Conveniently, you can add also do arithmetic on a date by using a duration
- Concatenation. You can concatenate multiple values into a single one using the
~
operator.
Referencing custom app data
Dynamic contents and templating also allow you to reference custom application data to use in your messages.
These are tables of key/value pairs that you can upload using our dashboard.
For example, given a table with the name population_by_city
with the following content:
2988507,2244000
2996944,484344
2995469,850726
2973783,271782
3031582,239157
These are the population of Paris, Lyon, Marseille, Strasbourg and Bordeaux respectively.
You can now use them like this.
You live in a city of {{ lookup('population_by_city', b.city_code) }}
For someone in Paris, this evaluates to:
You live in a city of 2244000
You can use any attribute or literal value as the lookup key. This works too:
You live in a city of {{ lookup('population_by_city', 2988507) }}
However by doing this you lose the benefit of the custom app data table. This also works:
You live in a city of {{ lookup('population_by_city', c.my_custom_city_code) }}
Language matching
The speaks
function allows to customize contents according to the user language. It's recommended to use this function instead of the b.language
native attribute as its value is not guaranteed to be stable.
It accepts one or more languages as parameters, and returns true if the user language matches one of them according to the language matching rules.
{% if speaks('fr') %}
Bonjour !
{% else %}
Hello !
{% endif %}
Referencing trigger event data
In the context of a trigger campaign, using data from the trigger event is possible by using the attribute named trigger_event
.
It is an object attribute that contains the attributes of the trigger event that triggered the campaign.
Here is how you can use it:
Your {{ trigger_event.product_name }} is waiting for you !
You can still access the trigger event label, tags and attributes using 3 specific functions:
triggerEventLabel()
: returns the label of the trigger eventtriggerEventTags()
: returns the tag collection attached to the trigger event. It can be used the same way as the tag collections.triggerEventAttr(attribute_key)
: returns the attribute value corresponding to the given key, it is the equivalent oftrigger_event.attribute_key
Here are a few examples of how you can use them.
Using the product name the user added to its cart:
Your {{ triggerEventAttr('product_name') }} is waiting for you !
Or if the products are listed in the tag collection:
{{ triggerEventTags() | count }} items are still waiting for you in your cart !
Use cases
After looking into how it works, let's look at some use cases for dynamic contents and templates that would otherwise be hard or even impossible to do.
Electoral results
Suppose you want to report on electoral results for every city in France. There are currently 35416 cities in France and you want each user to have the result from their city when receiving the notification.
Without dynamic contents you could only do this by creating one campaign per city with a query matching the city. In that case you'd need more than 35k campaigns, this is obviously not good.
Instead you can do the following:
- create a table named
electoral_results_201710
for example. - each row in the table should be
$city_code => $result
.
Example:
2988507,Yes 20.3% - No 79.7%
2996944,Yes 48.5% - No 51.5%
2995469,Yes 74% - No 26%
2973783,Yes 38% - No 62%
3031582,Yes 93.2% - No 6.8%
These are the results of Paris, Lyon, Marseille, Strasbourg and Bordeaux respectively.
- Then, create a message containing a lookup on the table and city code.
Example:
Election day result: {{ lookup('electoral_results_201710', b.city_code) }}
For someone in Paris this will evaluate to:
Election day result: Yes 20.3% - No 79.7%
Each user will have a customised message based on where he is.
Loyalty program goals
Suppose you have a loyalty program for your application where the user can gain points and at some threshold you gain a loyalty level. Here is an example of a points scale:
- 100 points - regular
- 500 points - silver
- 1000 points - gold
- 5000 points - platinum
Suppose also that you attach special one time discounts every time the user gains a level.
Finally, suppose you want to remind a user that they're about to reach the next level with the following message:
Gain 55 more points to reach Gold and get a one time discount of 15%!
Let's break down what we need:
- the number of points to reach to get to a level
- the number of points that a user has to gain
- the discount for a level
Prerequisites
For this to work you need to feed us the data we'll be working with; you can do so using custom attributes or the Custom Data API.
We imagine the following user attributes:
u.loyalty_points
an integer
value representing the current number of points a user has
We also need two custom app data tables:
loyalty_thresholds
containing this:
regular,100
silver,500
gold,1000
platinum,5000
The query
Before diving into the template let's look at what query we should use:
{
"$or": [
"$and": [
"u.loyalty_points": {
"$gte": 85
},
"u.loyalty_points": {
"$lt": 100
}
],
"$and": [
"u.loyalty_points": {
"$gte": 475
},
"u.loyalty_points": {
"$lt": 500
}
],
"$and": [
"u.loyalty_points": {
"$gte": 850
},
"u.loyalty_points": {
"$lt": 1000
}
],
"$and": [
"u.loyalty_points": {
"$gte": 4950
},
"u.loyalty_points": {
"$lt": 5000
}
]
]
}
This will match any user that is just about to reach the next level.
The template
Here is how the template could look like:
{% if u.loyalty_points >= 85 and u.loyalty_points < 100 %}
{% set $nextLevel = 'regular' %}
{% set $discount = '5%' %}
{% else if u.loyalty_points >= 475 and u.loyalty_points < 500 %}
{% set $nextLevel = 'silver' %}
{% set $discount = '10%' %}
{% else if u.loyalty_points >= 850 and u.loyalty_points < 1000 %}
{% set $nextLevel = 'gold' %}
{% set $discount = '15%' %}
{% else if u.loyalty_points >= 4950 and u.loyalty_points < 5000 %}
{% set $nextLevel = 'platinum' %}
{% set $discount = '35%' %}
{% endif %}
{% set $remaining = lookup('loyalty_thresholds', $nextLevel)|int - u.loyalty_points %}
Gain {{ $remaining }} more points to reach {{ $nextLevel|upper }} and get a one time discount of {{ $discount }}
Special discounts after a purchase
Suppose you want to give a user a special discount if they didn't buy anything after a month, and at the same time you want to remind them what they bought.
Suppose also that the discount changes based on how much time has passed since the purchase:
- 20% after a month
- 40% after more than two months
For example you could have the following message:
Did you like your Nike Air Max ? Get a 20% discount on all purchase today!
Let's break down what we need:
- the product name of the last purchase
- the date of the last purchase
Prerequisites
Like before, for this to work you need to feed us the data we'll be working with; you can do so using custom attributes or the Custom Data API.
In this use case we'll use custom events so you need to have that working too.
We image the following user attributes:
u.last_purchase_product_name
a string
containing the product name of the last purchase. It should be a properly formatted string.
We also need one custom event:
e.purchase
which tracks every time a user has purchased something.
The query
The query needs to filter users that haven't purchased anything since at least a month:
{
"age(e.purchase)": {
"$gte": "30d"
}
}
The template
Here is how the template could look like:
{% set $timeSinceLastPurchase = age(e.purchase) %}
{% if $timeSinceLastPurchase > 30d and $timeSinceLastPurchase < 60d %}
{% set $discount = '20%' %}
{% else %}
{% set $discount = '40%' %}
{% endif %}
Did you like your {{ u.last_purchase_product_name }} ? Get a {{ $discount }} discount on all purchase today!