Creating rules-based item recommendations
Learn how to create a rules-based recommendation engine from items in your catalog.
About rules-based item recommendations
A rules-based recommendation engine uses user data and product information to suggest relevant items to users within messages. It uses Liquid and either Braze catalogs or Connected Content to dynamically personalize content based on user behavior and attributes.
Rules-based recommendations are based on fixed logic that you must manually set. This means your recommendations won’t adjust to a user’s purchase history and tastes unless you update the logic.
To create personalized AI recommendations that automatically adjust to a user’s history, check out AI item recommendations.
Recommendation engine options
When deciding which recommendation engine suits your available resources and use cases, refer to this table of considerations:
Recommendation Engine | No data points consumed | No-code solution | No advanced Liquid | Automatically updates product feed | Generated with Braze UI | No data hosting or troubleshooting |
---|---|---|---|---|---|---|
Catalogs CSV | ✔ | Yes, if using pre-generated Liquid. | ✔ | Yes, if recommendations are not updated frequently. | ✔ | ✔ |
Catalogs API | ✔ | ✔ | Yes, if recommendations are updated hourly. | ✔ | ✔ | |
Connected Content | ✔ | ✔ (Recommendations updated in real-time) |
Yes, if generated outside of Braze. | |||
Liquid | ✔ | ✔ |
Creating a recommendation engine
Create your recommendation engine using either a catalog or Connected Content:
To create your recommendation engine using a catalog:
- Create a catalog of products.
- For each product, add a list of recommended products as a string separated by a delimiter (like a pipe
|
) in a column named “product_recommendations”. - Pass the product ID that you want to find recommendations for to the catalog.
- Get the
product_recommendations
value for that catalog item and split it by the delimiter with a Liquid split filter. - Pass one or more of those IDs back to the catalog to collect the other product details.
Example
Let’s say you have a health food app and want to create a Content Card campaign that sends different recipes based on how long a user has been signed up for your app. First, create and upload a catalog through an CSV file that includes the following information:
Field | Description |
---|---|
id | A unique number that correlates to the number of days since the user signed up with your app. For example, 3 correlates to three days. |
type | The recipe category, such as comfort , fresh , and others. |
title | The title of the content card that will be sent for each ID, such as “Make ahead for lunch this week” or “Let’s taco about it”. |
link | The link to the recipe article. |
image_url | The image that corresponds to the recipe. |
After the catalog is uploaded to Braze, check the preview of a select number of catalog items to confirm the information imported accurately. The items may be randomized in the preview, but this won’t affect the output of the recommendation engine.
Create a Content Card campaign. In the composer, enter Liquid logic to determine which users should receive the campaign, and which recipe and image should display. In this use case, Braze will pull the user’s start_date
(or sign-up date) and compare it to the current date. The difference in days will determine which Content Card is sent.
1
2
3
4
5
6
{% assign start_date = {{custom_attribute.${start_date}}} | date: "%s" %}
{% assign current_date = "now" | date: "%s" %}
{% assign diff = {{current_date}} | minus: {{start_date}} | divided_by: 86400 %}
{% assign days = {{diff}} | round %}
{% catalog_items Healthy_Recipe_Catalog_SMB {{days}} %}
{{ items[0].title }}
1
2
3
4
5
6
{% assign start_date = {{custom_attribute.${start_date}}} | date: "%s" %}
{% assign current_date = "now" | date: "%s" %}
{% assign diff = {{current_date}} | minus: {{start_date}} | divided_by: 86400 %}
{% assign days = {{diff}} | round %}
{% catalog_items Healthy_Recipe_Catalog_SMB {{days}} %}
{{ items[0].image_url }}
For example:
In the On click behavior section, enter Liquid logic for where users should be redirected when they click the Content Card on iOS, Android, and Web devices.
1
2
3
4
5
6
{% assign start_date = {{custom_attribute.${start_date}}} | date: "%s" %}
{% assign current_date = "now" | date: "%s" %}
{% assign diff = {{current_date}} | minus: {{start_date}} | divided_by: 86400 %}
{% assign days = {{diff}} | round %}
{% catalog_items Healthy_Recipe_Catalog_SMB {{days}} %}
{{ items[0].link }}
For example:
Go to the Test tab and select Custom user under Preview message as user. Enter a date in the Custom attribute field to preview the Content Card that would be sent to a user who signed up on that date.
To create your recommendation engine using Connected Content, first create a new endpoint using one of the following methods:
Option | Description |
---|---|
Convert a spreadsheet | Convert a spreadsheet into a JSON API endpoint by using a service like SheetDP, and take note of the API URL this generates. |
Create a custom endpoint | Build, host, and maintain a custom-built in-house endpoint. |
Use a third-party engine | Use a third-party recommendation engine, such as one of our Alloy partners, including Amazon Personalise, Certona, Dynamic Yield, and others. |
Next, use Liquid in your message that calls your endpoint to match a custom attribute value with a user’s profile and pull the corresponding recommendation.
1
2
3
4
5
6
7
8
{% connected_content YOUR_API_URL :save items %}
{% assign recommended_item_ids_from_user_profile = custom_attribute.${RECOMMENDED_ITEM_IDS} | split: ';' %}
{% for item_id in recommended_item_ids_from_user_profile %}
{% assign recommended_item = items | where: "ITEM_ID", ITEM_ID | first %}
recommended_item.item_name
{% endfor %}
Replace the following:
Attribute | Replacement |
---|---|
YOUR_API_URL |
Replace with the actual URL of your API. |
RECOMMENDED_ITEM_IDS |
Replace with the actual name of your custom attribute that contains the IDs of recommended items. This attribute is expected to be a string of IDs separated by semicolons. |
ITEM_ID |
Replace with the actual name of the attribute in your API response that corresponds to the item ID. |
This is a basic example and you might need to modify it further based on your specific needs and data structure. For more detailed guidance, refer to the Liquid documentation or consult with a developer.
Example
Let’s say you want to pull restaurant recommendations from the Zomato Restaurants database and save the result as a local variable called restaurants
. You can make the following Connected Content call:
1
2
3
4
{% connected_content https://developers.zomato.com/api/v2.1/search?entity_id={{city_id}}&entity_type=city&count=20&cuisines={{food_type}}&sort=rating:headers{“user-key”:“USER_KEY”} :save restaurants %}
{{city_food.restaurants[0]}}
Next, let’s say you want to pull restaurant recommendations based on a user’s city and food type. You can do so by dynamically inserting the custom attributes for the user’s city and food type to the beginning of the call, and then assigning the value of restaurants
to the variable city_food.restaurants
.
The Connected Content call would look like this:
1
2
3
4
5
6
7
8
{% assign city_id = {{custom_attribute.${city_id} | default: ‘306’}} %}
{% assign food_type = {{custom_attribute.${food_type} | default: ‘471’}} %}
{%- connected_content https://developers.zomato.com/api/v2.1/search?entity_id={{city_id}}&entity_type=city&count=20&cuisines={{food_type}}&sort=rating:headers{“user-key”:“USER_KEY”} :save restaurants %}
{% assign restaurants = city_food.restaurants %}
{{city_food.restaurants[0]}}
If you want to tailor the response to retrieve only the restaurant name and rating, you can add filters to the end of the call, like so:
1
2
3
4
5
6
7
8
{% assign city_id = {{custom_attribute.${city_id} | default: ‘306’}} %}
{% assign food_type = {{custom_attribute.${food_type} | default: ‘471’}} %}
{%- connected_content https://developers.zomato.com/api/v2.1/search?entity_id={{city_id}}&entity_type=city&count=20&cuisines={{food_type}}&sort=rating:headers{“user-key”:”USER_KEY”} :save restaurants %}
{% assign restaurants = city_food.restaurants %}
{{city_food.restaurants[0].restaurant.name}}
{{city_food.restaurants[0].restaurant.user_rating.rating_text}}
Finally, let’s say you want to group together the restaurant recommendations by rating. Do the following:
- Use
assign
to create blank arrays for rating categories of “excellent”, “very good”, and “good”. - Add a
for
loop that examines the rating of each restaurant in the list.- If a rating is “Excellent”, append the restaurant name to the
excellent_restaurants
string, then add a * character at the end to separate each restaurant name. - If a rating is “Very Good”, append the restaurant name to the
very_good_restaurants
string, then add a * character at the end. - If a rating is “Good”, append the restaurant name to the
good_restaurants
string, then add a * character at the end.
- If a rating is “Excellent”, append the restaurant name to the
- Limit the number of restaurant recommendations returned to four per category.
This is what the final call would look like:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
{% assign city_id = {{custom_attribute.${city_id} | default: ‘306’}} %}
{% assign food_type = {{custom_attribute.${food_type} | default: ‘471’}} %}
{%- connected_content https://developers.zomato.com/api/v2.1/search?entity_id={{city_id}}&entity_type=city&count=20&cuisines={{food_type}}&sort=rating:headers{“user-key”:”USER_KEY”} :save restaurants %}
{% assign restaurants = city_food.restaurants %}
{% assign excellent_restaurants = “” %}
{% assign very_good_resturants = “” %}
{% assign good_restaurants = “” %}
{% for list in restaurants %}
{% if {{list.restaurant.user_rating.rating_text}} == `Excellent` %}
{% assign excellent_restaurants = excellent_restaurants | append: list.restaurant.name | append: `*` %}
{% elseif {{list.restaurant.user_rating.rating_text}} == `Very Good` %}
{% assign very_good_restaurants = very_good_restaurants | append: list.restaurant.name | append: `*` %}
{% elseif {{list.restaurant.user_rating.rating_text}} == `Good` %}
{% assign good_restaurants = good_restaurants | append: list.restaurant.name | append: `*` %}
{% endif %}
{% endfor %}
{% assign excellent_array = excellent_restaurants | split: `*` %}
{% assign very_good_array = very_good_restaurants | split: `*` %}
{% assign good_array = good_restaurants | split: `*` %}
Excellent places
{% for list in excellent_array %}
{{list}}
{% assign total_count = total_count | plus:1 %}
{% if total_count >= 4 %}
{% break %}
{% endif %}
{% endfor %}
Very good places
{% for list in very_good_array %}
{{list}}
{% assign total_count = total_count | plus:1 %}
{% if total_count >= 4 %}
{% break %}
{% endif %}
{% endfor %}
Good places
{% for list in good_array %}
{{list}}
{% assign total_count = total_count | plus:1 %}
{% if total_count >= 4 %}
{% break %}
{% endif %}
{% endfor %}
See the below screenshot for an example of how the response displays on a user’s device.