Personalized Offers

Personalized offers are offers assigned specifically to a particular user. Every time a personalized offer is assigned to an user, it creates a unique offer record that can only be used for that particular user.

The unique record is stored in the Loyalty Offer table in the Dynamo DB and is identified using an unique identifier aka Personalized Offer ID.

Personalized offers are constructed using the following:

  1. Config Offer - These are offers created in Sanity for the sole purpose of being used for personalized offer assignment. Personalized offers get most of the offer specific details from the config offer.

  2. Offer Template - This is a template that is used as a “shell” to create the personalized offer record. An offer template is also created in Sanity and requires a config offer to be linked. The main purpose of the template is to streamline which rules are required when creating personalized offers in bulk. If rules are defined in the template, then those rules MUST be configured during the assignment process and will override the rules configured as part of the config offer.This is a template that is used as a “shell” to create the personalized offer record. An offer template is also created in Sanity and requires a config offer to be linked. The main purpose of the template is to streamline which rules are required when creating personalized offers in bulk. If rules are defined in the template, then those rules MUST be configured during the assignment process and will override the rules configured as part of the config offer.

    1. https://prod.menu.plk.rbi.tools/desk/orderLevelContent;loyalty;incentives;offers;offerTemplates

    2. https://prod.menu.bk.rbi.tools/desk/orderLevelContent;loyalty;incentives;offers;offerTemplates

Both of the above are required in order to create a personalized offer record. Note that as part of the personalized offer record, both the sanity IDs of the Config Offer and Offer Template are stored and can be used to reference both documents.

How are personalized offers assigned?

There are three primary ways where personalized offers are used and assigned.

  1. Deepflame Bulk Assignment

    1. https://rbictg.atlassian.net/wiki/spaces/L/pages/3357114507/Incentive+Personalization?NO_SSR=1

  2. Campaign Webhook Assignment via Braze

    1. https://rbictg.atlassian.net/wiki/spaces/FEP/pages/3639083035

  3. Support Offers via Support Tool

    1. 5. Offers

Personalized Offers UI

Generally speaking, personalized offers will look and feel the same as regular systemwide (national) offers in the app. See example below (can you tell it’s a personalized offer? )

 

The one point to call out is that the URL used to directly access the personalized offer will be built using the unique personalized offer ID. This means that for every used, the path to get to the personalized offer using the URL will be different.

Example URL: {base URL}/rewards/offers/{personalized offer ID}

 

Offer Dispatcher

offer Dispatcher was created in October 2023. A new lambda has been created to migrate code from Tasks back to Lambdas. offerDispatcher takes streams of data from dynamoDB. Any time a row is added to the pendingPersonalizedOffers table in DynamoDB. Once that stream is triggered, a list of record(s) is sent to the offerDispatcher lambda.

Enhancements/ Updates

With the migration, there have been some changes to the original code.

1. CSV’s are no longer accepted, any new offers should be stored in the new table pendingPersonalizedOffers. Once an offer is stored it then triggers the lambda.

  1. Activated offers are no longer being utilized.

  2. Braze is no longer being utilized.

  3. Cloud watch is no longer being utilized to store metrics. Just logs

  4. Data dog is replacing cloud watch

  5. Once a pending offer has been stored in the personalized offers database, it is removed from the table.

Row Structure

No Challenge Example Data

{ "userId": string, "sortKey": string (campaignId+offerId), "offerId": string, "campaignId": string, "ttl": number, "startDate": ISO-TimeStamp "expiryDate": ISO-TimeStamp, "rank": number }

 

With Challenge Example Data

{ "userId": "us-east-1:192991f1-ede8-47bf-89e1-3942d4ab8aaa", "sortKey": "e2dd4cb3-9b0f-479c-8e1b-e42234f27314+d2dd9cb3-9b0f-479c-8e1b-e40046f57170", "challengeStatus": "{\"reward\": {\"value\": \"100\"},\"goals\": {\"visit1\": {\"condition\": {\"total_cost\": \"1\"}},\"visit2\": {\"condition\": {\"total_cost\": \"1\"}}}}", "challengeType": "frequency", "expiryDate": "2025-09-20T17:37:37+0000", "frequency": "290fb746-9850-4994-b7d7-5630046792e1", "offerId": "290fb746-9850-4994-b7d7-5630046792e1", "rank": 12, "startDate": "2023-10-20T17:37:37+0000", "taskId": "e2dd4cb3-9b0f-479c-8e1b-e42234f27314" }

Note: frequency must match offerId
To view challenge offers: GitHub/Loyalty/engine/src/lib/models/FormationGame.ts

Legacy Code

IssuePersonalizedOffers in engine and in tasks has remained unaffected.