Skip to content

Personalization & recommendations

Interest profiles, the product catalog feed, {rec.*} product recommendation tags, smart send time and how the data is kept.

relaybell learns what each subscriber is interested in from what your website reports about them, and uses it in two ways: product recommendations in the copy of a notification ({rec.title}, {rec.url} …), and smart timing, which delivers at the hour each person usually clicks or visits.

Everything runs on your own (first-party) data: the actions your website sends (events) and the person's clicks on your notifications. Nothing comes from other websites, ad networks or Google Analytics.

Interest profiles

A subscriber gets a profile once they have done something in the last 90 days: viewed, carted or bought a product, joined a waitlist, or clicked a notification. It is worked out again a couple of minutes after each new action, and refreshed once a day.

PartWhat it is
Top categories, brands, productsScored from product.viewed (1), cart.updated / checkout.started (3), purchase (5) and waitlist.join (6). Scores halve every 14 days, so recent interest outweighs old.
Price rangeThe lowest, middle (median) and highest price of the products they viewed or carted.
Engagement0–100: their clicks compared with the notifications they received, how many days they were active in the last 30, and how recently.
Best hourThe hour of day, in their own time zone, at which they most often click or are active. Needs at least 3 such moments; until then it is empty.
Last activeTheir latest action or click.

Events only need the product id. When an event leaves out the category, brand or price (WooCommerce and Shopify events send only the id), they are taken from your product catalog by productId. Events that do carry them use these property names: productId, product (name), category (e.g. Shoes > Running), brand, price, currency, and on purchase / checkout.started an items list with the same fields.

See a subscriber's profile in Audience → Subscribers (open a subscriber: Interests), or with the API:

HTTP
GET /api/v1/subscribers/{externalId}/interests
Authorization: Bearer rb_live_…

It needs an API key with the subscribers:read scope and answers:

JSON
{
  "subscriber": { "id": "…", "externalId": "user_42", "timezone": "Asia/Kolkata" },
  "profile": {
    "categories": [{ "name": "Shoes > Running", "score": 4.7 }],
    "brands": [{ "name": "Acme", "score": 3.9 }],
    "products": [{ "id": "1042", "score": 3.8, "title": "Road Runner 2" }],
    "priceRange": { "min": 3499, "median": 4999, "max": 4999, "currency": "INR" },
    "engagement": 72,
    "bestHour": 19,
    "bestHourSignals": 6,
    "lastActiveAt": "2026-10-25T13:50:00Z",
    "computedAt": "2026-10-25T13:52:10Z"
  },
  "recommendations": [{ "productId": "1042", "title": "Road Runner 2", "url": "https://…", "image": "https://…", "price": 4999, "salePrice": null, "currency": "INR", "category": "Shoes > Running", "brand": "Acme", "availability": "in_stock" }],
  "recommendationSource": "profile"
}

profile is null for someone with no recent actions; recommendationSource is then popular. An unknown id answers 404.

Product catalog

Recommendations pick from your product catalog. Add it in Settings → Catalog (or PUT /api/v1/catalog) as a public web address in one of these formats:

FormatWhat to enter
Google Merchant CenterThe XML (RSS or Atom) feed your shop plugin makes for Google Shopping. The g: fields are read: g:id, g:title, g:link, g:image_link, g:price (e.g. 19.99 USD), g:sale_price, g:availability, g:brand, g:product_type (or g:google_product_category).
ShopifyYour store's address followed by /products.json, e.g. https://yourstore.com/products.json (all pages are read). Set the currency, since this file doesn't say.
JSON listA list of products, or { "products": [...] }, with id (or productId), title (or name), url, image, price, salePrice, currency, category (a text like Home > Kitchen, or a list), brand and inStock (or availability).
  • The product ids must be the ones your store sends as productId in its events, so actions can be matched to products.
  • Only http(s) links and images are kept. A product needs an id and a title.
  • Up to 10,000 products and 20 MB per feed (over 10,000, the first 10,000 are kept and the page says so); the download must finish within 60 seconds.
  • The feed is read every 6 hours, and right away when you save a new address or press Sync now (once a minute at most).
  • A product that disappears from the feed is marked unavailable and stops being recommended straight away; it is deleted after 30 days if it doesn't come back. Out-of-stock products are never recommended.
  • When a sync fails, the page says why in plain words (for example "Nothing was found at this address (HTTP 404)") and the products from the last good sync stay in use. It is tried again within the hour.

API: GET /api/v1/catalog (the feed, its last sync and product counts), PUT /api/v1/catalog { url, format?, currency?, enabled? } (format: auto, google, shopify or json), DELETE /api/v1/catalog, POST /api/v1/catalog/sync and GET /api/v1/catalog/products?limit=50&cursor=&q=&availability=. Changes need the settings:write scope and appear in Account → Activity.

Recommendations

For every subscriber, each in-stock product in the catalog is scored by how well its category and brand match their interests (and whether they looked at it), times how well its price fits their price range. Leaving out:

  • products they bought in the last 30 days,
  • the product the previous recommendation push showed them (for 7 days), so the next push moves on.

The best five are kept with the profile and refreshed after each catalog sync. Someone with no profile, or too few matches, gets your popular products: the ones most people viewed, carted or bought in the last 7 days. With no recent actions at all, your newest products are used. Stock is checked again at the moment each push goes out.

Product tags

Use these merge tags in the title, message, link, image and buttons of a send, and in the title, message and link of automations and journeys:

TagValue
{rec.title}The recommended product's name.
{rec.url}Its page (your UTM tags are added).
{rec.image}Its picture.
{rec.price}Its price with the currency, e.g. ₹4,999 or $19.50 (the sale price when it is on sale).
{rec.category}The last part of its category, e.g. Running.
{rec.brand}Its brand.
{rec2.…}, {rec3.…}The same for the second and third pick.
JSON
{
  "title": "{rec.title|Our bestsellers} is waiting for you",
  "body": "Now {rec.price|on sale}. Tap to take a look.",
  "url": "{rec.url}",
  "image": "{rec.image}",
  "actions": [{ "title": "Or see {rec2.title|more}", "url": "{rec2.url}" }]
}

When a subscriber has nothing to recommend (no catalog yet, or nothing in stock), the text after | is used. A link that doesn't come out as a web address opens your Default link (Settings → Sending) instead, a button without one opens the notification's link, and an image that isn't a web address is left out. An empty title becomes your website's name. A tag such as {rec.colour} is refused with 400 invalid_merge_tag.

In the Send page and the automation editor, Recommend a product adds the tags for you and shows a preview for a subscriber (by user ID, or a random recent one). The preview is also available to the API, with the same rendering a send uses:

HTTP
POST /api/v1/recommendations/preview
{ "externalId": "user_42", "title": "{rec.title|Our picks}", "url": "{rec.url}" }

It answers the subscriber, their picks, the tag values and the rendered copy; leave externalId out for a random recently active subscriber. GET /api/v1/recommendations/preview?externalId= returns the picks without copy. Clicks on these pushes are counted like any other click; Reports shows the clicks on notifications that used product tags in the last 30 days (GET /api/v1/recommendations/stats?days=30).

Smart timing

A send with Smart timing (smartTime: true) goes to each subscriber at their best hour in their own time zone. Someone without one yet gets the local hour their browser was last active, or 10:00. Smart timing never lands inside your quiet hours: such a group waits until they end. Your daily frequency cap still applies.

Privacy

  • Profiles use only data your website collected about its own visitors and their clicks on your notifications. No personal details beyond what your events already contain are stored, and properties are never shared with other websites.
  • Erasing a subscriber (Audience, or DELETE /api/v1/subscribers/{externalId}) deletes their profile with everything else. Deleting a website deletes its catalog and profiles.
  • Profiles only look back 90 days, and your data retention setting removes older actions.
  • If your privacy notice lists what you do with visitor data, mention that you personalize notifications based on what people view and buy on your site.