Product recommendations
Joryio's recommendation engine picks per-customer items for your messages - email, SMS, WhatsApp, Viber, push, in-app and landing pages, in campaigns and journeys alike (see Where recommendations work). It is deliberately transparent - you decide what it learns from:
- Item source - your synced store products (Shopify/WooCommerce) or any Custom Entity that holds a catalog.
- Training signals - which events teach the engine, each with a weight (e.g.
purchase×5,add_to_cart×2,product_viewed×1 - any events, including your custom ones). The item id property tells the engine which event property carries the item (e.g.productId); when an event uses a different property (say anad_viewevent carryingad_id), set it per signal - each signal row has its own optional property that overrides the model default. - Selection (optional) - pick a selection to narrow what the model may recommend: a product selection for store products (managed on the Products page), or an entity selection for a Custom Entity catalog (e.g. only one brand or location). Static filters only - per-recipient filter values can't resolve at ranking time. It applies in campaigns and journeys alike. When too few of the model's picks match, the selection's own products fill the gap. If the selection cannot be read (it was deleted, or the lookup fails), the model shows only its pinned products - never items outside the selection.
- Training window and an option to never recommend items the customer already purchased (see Exclusions).
Multiple models
A workspace can hold several named models, each with its own type, source and signals - create them under Settings → Recommendations and pick one per product block in the email editor (Source: Recommended → Model), or address one by its slot in Liquid. One model is marked as the default and is used when a block doesn't pick one.
Recommendation types
Each model has one type, chosen under Settings → Recommendations → Model type:
| Type | What each recipient gets | Training |
|---|---|---|
| Personalized | Items matched to their own recent activity (trained co-occurrence) | Nightly + Train now |
| Most popular | The same bestseller list for everyone, weighted by your signals | Nightly + Train now |
| Trending now | Weighted activity over the last 7 days | None - computed live |
| Recently viewed | Their own latest items. Falls back to Trending for new customers; never topped up with other items | None - computed live |
| Similar items | Content-based: products that share categories, brand, attributes (e.g. Color, Material) and tags with a product. Categories weigh most, then brand and attributes; tags break ties | None - computed live |
| Bought together | Products that appeared in the same order as a product, over the model's training window (from your synced orders). If the product has no co-purchases yet, similar items fill in | None - computed live |
| Viewed together | Products viewed by the same people as a product. Trains on product views (Product Viewed) only. A product with no co-view history falls back to similar items | Nightly + Train now |
| New arrivals | The newest products in the catalog (by when they were added), optionally limited to one category | None - computed live |
| Popular in category | Best sellers (units in orders) or most viewed products (Product Viewed events) - choose sales or views - in a category over the model's window. Category: the one set on the model, else the anchor product's first category, else the whole catalog | None - computed live |
| Top discounted | The biggest discount of compare-at price vs price, optionally in one category | None - computed live |
The last six types (Similar items to Top discounted) work with store products only, not custom-entity catalogs.
Which product?
Similar items, Bought together and Viewed together recommend around one product. It is picked in this order:
- A fixed product id set on the model.
- The product id set on the email product block (the optional Product id field).
- In a journey, the product of the event that triggered the journey - its
productId/product_idproperty, or the first item of aproducts/itemslist. - Otherwise, the recipient's most recently viewed product.
With none of these, the model's popular fallback is shown.
Pinned products
Every type accepts a list of pinned product ids on the model. Pinned products are shown first, in that order, and the type's algorithm fills the remaining places. Pinned products are subject to the same exclusions - a sold-out or already-bought pinned product is skipped.
Exclusions
By default:
- Out-of-stock products - products your store marks out of stock are not recommended. Switch Hide out-of-stock products off on a model to allow them.
- Already purchased - products the recipient already bought (from their orders) are not recommended. Don't recommend items the customer already purchased is on by default and applies to every type.
- The anchor product itself is never recommended.
- The model's Selection (allow-list) still applies; pinned products keep their place.
- The block list beats everything, pinned products included.
Block list
Products that must never be recommended - gift cards, samples, a discontinued line. There are two lists, and both always apply:
- Per model - Never recommend on the model: product ids, and for store products also categories and tags (not case-sensitive).
- Workspace-wide - Settings → Recommendations, below the models: the same three lists, applied to every model.
A blocked product is never shown by any type - not as a pick, not when pinned, not from the popular fallback, and not from the fallback used when a slot runs out of time. A product id may be the store's id or Joryio's. Each list holds up to 500 product ids and 100 categories and 100 tags. Changes reach messages within 5 minutes.
Boost and bury
Merchandising rules on top of relevance, for store products. On a model, open Boost and bury, click Add rule and pick:
- What - a product field (numeric or yes/no - the list shows the fields your products actually have: price, compare-at price, discount percent, units in stock, in stock, and attributes such as a rating or a margin), a category or a tag.
- Direction - Boost lifts matching products, Bury pushes them down.
- Strength - Light, Medium or Strong (0.2, 0.5 and 1.0).
How the rules score: the model first finds and orders its candidates (relevance, including the learned ranker); the block list, the Selection and the stock filter remove what may not be shown; then the rules re-order what is left, before the list is cut to the number of items the message shows. A candidate's relevance comes from its place in that order - the first of 10 scores 1, the last 0.1. A numeric field is scaled across the candidates from 0 (lowest) to 1 (highest); a yes/no field, a category or a tag counts 1 when it matches and 0 when it does not; a product without the field counts 0. Boost multiplies relevance by (1 + strength x value), Bury divides by it, and several rules multiply.
Rules never add a product the model did not find, never show a blocked product and never move a pinned product - pinned products keep their places. They apply the same way in every channel and in journeys. Up to 10 rules per model.
Examples:
- Push the sale - Boost, category "Sale", Medium: a sale item just below a full-price item moves ahead of it; one far down the list does not jump to the top.
- Margin first - Boost, attribute "Margin", Strong: among similarly relevant products, the highest-margin ones lead.
- Do not lead with add-ons - Bury, tag "accessory", Light.
- Plenty in stock first - Boost, Units in stock, Light.
The card's Preview shows one contact's list Without rules and With rules side by side, using the rules on screen before you save (moved items are outlined). Pick a contact by email, phone or id; with none picked it shows the list an anonymous visitor gets. A preview never counts as a served recommendation.
Fallback and speed
Every list is topped up from the model's popular items (trained popularity, else 7-day trending, else the newest products), so a slot is never empty while the store has products.
Each recipient has a time budget (1.5 seconds by default). A slot that does not resolve in time gets the model's popular items, so a send never stalls. Lists that do not depend on the recipient - a product's related items, a category's best sellers, new arrivals - are computed once per campaign send and reused for every recipient.
How it learns
Training (nightly for enabled configs, or Train now) runs two passes over your event stream: weighted popularity per item, and item-to-item affinity - which items the same customers engage with together, normalized so niche pairs aren't drowned out by bestsellers. Viewed together models train the same affinity on product views only.
Learning from outcomes (v2 ranker)
Once a model has real serving outcomes (recommendations served → items bought), a second-stage ranker trains on them nightly and starts re-ordering each recipient's candidates by learned conversion probability - "when we show X to customers like this, they buy". It includes an exploration slot so new items keep getting a fair chance to earn data. The ranker activates per model only after enough outcomes exist; until then (and whenever it's unreachable) the engine's standard ordering is used, so nothing ever breaks. Sends carry a flag distinguishing ranked from standard ordering, so their performance stays comparable.
Besides past outcomes, the ranker weighs how each product fits the recipient: its price against what they usually spend (order value and item price), whether it is in their top categories or brands (from their orders), whether it is on sale for someone who tends to buy on sale, and whether it is selling faster this week than usual. These are taken as they were when the recommendation was served, so the ranker never learns from what happened afterwards; recommendations served before this existed count as neutral. A model starts using them at its next nightly training.
Where recommendations work
Recommendations work in every channel, in campaigns and journeys:
| Channel | How |
|---|---|
The product block: Source: Recommended, choose the model and a count, plus an optional Product id for the anchored types. Liquid loops over recommendations work too, in campaigns and journeys; a block and a loop that use the same model show the same items | |
| SMS, WhatsApp, Viber | The Liquid variable {{ recommendations.<slot> }} in the text (for WhatsApp: session / reply text and template variable values) |
| Push | {{ recommendations.<slot> }} in title, body, image and link |
| In-app | Product cards in HTML messages, or Liquid in title / body / image of native messages |
| Landing pages | Product cards or Liquid |
Liquid slots
The slot is the model name turned into a key: lower-case, with spaces and punctuation becoming _ ("For you" → for_you, "Bestsellers" → bestsellers). recommendations.default always means the workspace default model. A model whose name has no Latin letters (e.g. a Hebrew name) gets the slot model_<first 8 characters of its id> - the Settings page and the personalization picker show each model's slot. An unknown slot renders as an empty list.
Each item has:
| Field | Meaning |
|---|---|
name (also title) | Product name |
price | Formatted with currency, e.g. $19.90 |
compareAtPrice, discountPercent | When the product is discounted |
url | Product link |
image (also imageUrl) | Product image |
description | Product description |
id | Product id |
A slot holds up to 4 items by default (up to 12 via a card's data-jr-count).
Item links are click-tracked like any link in the message: UTM parameters and Joryio ids are added at the click redirect, and SMS / WhatsApp / Viber links are shortened (respecting the per-message Shorten & track links switch).
SMS / WhatsApp / Viber text:
{% for item in recommendations.for_you limit: 3 %}
{{ item.name }} - {{ item.price }} {{ item.url }}
{% endfor %}
Push:
Title: {{ recommendations.for_you.first.name }}
Body: Now {{ recommendations.for_you.first.price }}
Image: {{ recommendations.for_you.first.image }}
Link: {{ recommendations.for_you.first.url }}
WhatsApp messages carry text and a link only - catalog / product messages are a separate feature, not yet available.
In-app native messages: Liquid in title, body and image, e.g. image {{ recommendations.for_you.first.image }}.
Product cards
In in-app HTML messages and landing pages, this element becomes a row of product cards (image, name, price, struck-through compare-at price when discounted, link):
<div data-jr-recommendations="for_you" data-jr-count="4" data-jr-button-label="Shop now"></div>
Liquid loops also work there.
On landing pages, product cards work on every published page: anonymous visitors see the model's non-personal list (refreshed every few minutes), and an identified visitor gets their own. The Liquid form works on personalized pages.
Previews and test sends
Editor previews and test sends in every channel show real items: for the test contact when the test goes to one, otherwise the model's list for someone with no history. They use the same fallback, block list and Selection as a real send. A test send never counts as a served recommendation in the analytics below.
Not yet supported
- In journey in-app messages, Similar items, Bought together and Viewed together do not use the product that triggered the journey - they use the model's fixed product, else the recipient's most recently viewed product.
Performance analytics
Once a model's recommendations go out in real sends, its page shows the last 30 days: how many recommendations were served, to how many recipients, the share who clicked the carrying message, and the share who bought a recommended item within 7 days of receiving it (attributed via the recommendation.served platform event). recommendation.served is recorded for email, SMS, push, WhatsApp and Viber sends (not in-app), so the panel counts every one of those channels.
How it serves
At send time each recipient's recent activity votes for related items; customers without history get the model's popular items. Configure models under Settings → Recommendations, then use them through the email product block (Source: Recommended with a count) or the recommendations Liquid variable in any other channel.