Overview
Chaay uses Microsoft RulesEngine to evaluate dynamic business rules against customer data. Rules are written as lambda expressions that access a customer context object (ctx) containing aggregated customer attributes.
With rule workflows, you can define conditions for tier upgrades, segment membership, point multipliers, promotional eligibility, churn prevention, and more — all without writing code.
Every expression accesses customer data through the ctx object. For example, ctx.PointsBalance >= 1000 checks if a customer has at least 1,000 points.
How It Works
The rule evaluation pipeline follows these steps:
- Context is built — The system aggregates customer data (identity, tier, wallet, transactions, RFM scores, etc.) into a
CustomerContextDataobject. This is cached for 4 hours and refreshed automatically. - Workflow is loaded — The workflow JSON definition (containing one or more rules) is loaded and compiled into an engine instance. Compiled engines are cached for 5 minutes.
- Rules are evaluated — Each rule's expression is evaluated against the customer context. The engine returns which rules matched and which failed.
- Results are logged — Execution results, timing, and matched rules are recorded in the execution log for auditing.
Workflow JSON Structure
A workflow is a JSON object following the Microsoft RulesEngine format. It contains a name and an array of rules, each with an expression to evaluate.
{
"WorkflowName": "TierGoldProgression",
"Rules": [
{
"RuleName": "HighSpenderRule",
"Expression": "ctx.LifetimeEarned >= 50000 AND ctx.TransactionCountLifetime >= 25",
"SuccessEvent": "TierUpgrade:Gold",
"ErrorMessage": "Customer does not meet Gold tier requirements",
"RuleExpressionType": "LambdaExpression",
"Enabled": true,
"Actions": {
"OnSuccess": {
"Name": "TierUpgrade",
"Context": { "TierId": "gold-tier-uuid" }
}
}
}
]
}
Rule Fields
| Field | Type | Description |
|---|---|---|
RuleName |
string Required | Unique name for this rule within the workflow |
Expression |
string Required | The lambda expression to evaluate (see below) |
SuccessEvent |
string Optional | Event name emitted when the rule passes |
ErrorMessage |
string Optional | Message returned when the rule fails |
RuleExpressionType |
string Optional | Always "LambdaExpression" (default) |
Enabled |
boolean Optional | Enable/disable individual rules (default: true) |
Actions |
object Optional | Actions to trigger on success or failure |
Simple Expressions
Simple expressions compare a single customer attribute against a value. All attributes are accessed through the ctx prefix.
Comparing Numbers
// Customer has at least 5,000 points
ctx.PointsBalance >= 5000
// Customer made more than 10 transactions in the last 30 days
ctx.TransactionCount30Days > 10
// Average transaction value is at least $50
ctx.AverageTransactionValue >= 50
// Customer has been registered for more than 90 days
ctx.DaysSinceRegistration > 90
Comparing Strings
String values must be wrapped in double quotes.
// Customer is on Gold tier
ctx.TierName == "Gold"
// Customer status is Active
ctx.Status == "Active"
// RFM segment is "Champions"
ctx.RfmSegment == "Champions"
// Lifecycle stage is NOT "Churned"
ctx.LifecycleStage != "Churned"
Comparing Booleans
// Customer was referred by someone
ctx.WasReferred == true
// Customer has a referral code
ctx.HasReferralCode == true
Null Checks
Since all context fields are nullable, you can check for null values:
// Customer has an email address
ctx.Email != null
// Customer has a tier assigned
ctx.TierName != null
// Customer has no transactions yet
ctx.TransactionCountLifetime == null OR ctx.TransactionCountLifetime == 0
Operators
Comparison Operators
| Operator | Description | Example |
|---|---|---|
== |
Equals | ctx.TierName == "Gold" |
!= |
Not equals | ctx.Status != "Blocked" |
> |
Greater than | ctx.PointsBalance > 1000 |
>= |
Greater than or equal | ctx.Age >= 18 |
< |
Less than | ctx.ChurnProbability < 0.3 |
<= |
Less than or equal | ctx.DaysInCurrentTier <= 90 |
Logical Operators
| Operator | Description | Example |
|---|---|---|
AND |
Both conditions must be true | ctx.Age >= 18 AND ctx.Status == "Active" |
OR |
At least one condition must be true | ctx.TierName == "Gold" OR ctx.TierName == "Platinum" |
NOT |
Negates a condition | NOT (ctx.Status == "Blocked") |
Grouping with Parentheses
Use parentheses ( ) to control evaluation order and group conditions:
// Gold OR Platinum tier AND active status
(ctx.TierName == "Gold" OR ctx.TierName == "Platinum") AND ctx.Status == "Active"
AND has higher precedence than OR. Always use parentheses when mixing AND and OR to make the intent clear.
Complex Expressions
Combine multiple conditions with logical operators to build sophisticated rules.
Multi-Condition AND
All conditions must be true:
// High-value active customer on Gold tier
ctx.Status == "Active"
AND ctx.TierName == "Gold"
AND ctx.TotalSpendLifetime >= 100000
AND ctx.TransactionCount30Days >= 5
Mixed AND/OR with Grouping
// Eligible for VIP promotion: high spender OR frequent buyer, and active
ctx.Status == "Active"
AND (ctx.TotalSpend90Days >= 50000 OR ctx.TransactionCount90Days >= 30)
AND ctx.DaysSinceRegistration >= 30
Range Checks
// Customer age between 25 and 40
ctx.Age >= 25 AND ctx.Age <= 40
// Churn probability in medium-risk range (30% to 70%)
ctx.ChurnProbability >= 0.3 AND ctx.ChurnProbability < 0.7
// RFM score between 6 and 9 (good but not champion)
ctx.RfmTotalScore >= 6 AND ctx.RfmTotalScore <= 9
Negated Groups
// Active customer not in Bronze or Basic tier
ctx.Status == "Active"
AND NOT (ctx.TierName == "Bronze" OR ctx.TierName == "Basic")
Multiple Rules in a Workflow
You can define multiple rules in a single workflow. Each rule is evaluated independently. Use this to create tiered conditions:
{
"WorkflowName": "SpendingTierCheck",
"Rules": [
{
"RuleName": "PlatinumEligibility",
"Expression": "ctx.TotalSpendLifetime >= 200000 AND ctx.TransactionCountLifetime >= 100",
"SuccessEvent": "TierUpgrade:Platinum"
},
{
"RuleName": "GoldEligibility",
"Expression": "ctx.TotalSpendLifetime >= 50000 AND ctx.TransactionCountLifetime >= 25",
"SuccessEvent": "TierUpgrade:Gold"
},
{
"RuleName": "SilverEligibility",
"Expression": "ctx.TotalSpendLifetime >= 10000 AND ctx.TransactionCountLifetime >= 10",
"SuccessEvent": "TierUpgrade:Silver"
}
]
}
Custom Attributes
Beyond the built-in customer fields, you can use custom attributes stored as key-value pairs. Access them with the helper methods GetCustomAttribute() and HasCustomAttribute().
GetCustomAttribute(key)
Returns the value of a custom attribute as a string, or null if not found.
// Customer region is "Baghdad"
ctx.GetCustomAttribute("region") == "Baghdad"
// Customer's preferred language is Arabic
ctx.GetCustomAttribute("language") == "ar"
// Customer's company is a known enterprise
ctx.GetCustomAttribute("companyName") != null
HasCustomAttribute(key)
Returns true if the attribute exists, false otherwise. Useful for checking if a flag is set.
// Customer has a VIP flag
ctx.HasCustomAttribute("vip") == true
// Customer has completed onboarding
ctx.HasCustomAttribute("onboarding_complete") == true
Combining Custom and Built-in Attributes
// VIP customer in Baghdad with high spend
ctx.GetCustomAttribute("region") == "Baghdad"
AND ctx.HasCustomAttribute("vip") == true
AND ctx.TotalSpend90Days >= 25000
Custom attributes are flexible and extensible. Use them to store merchant-specific data like customer preferences, survey responses, or regional flags without modifying the core schema.
Customer Attributes: Identity
Basic customer identification and demographic fields.
| Expression Field | Type | Description |
|---|---|---|
ctx.Phone |
string | Customer phone number (with country code) |
ctx.Email |
string | Customer email address |
ctx.Name |
string | Customer display name |
ctx.Status |
string | Account status (e.g., "Active", "Blocked") |
ctx.DaysSinceRegistration |
number | Days since customer registered |
ctx.BirthdayMonth |
number | Birth month (1–12) |
ctx.BirthdayDay |
number | Birth day of month (1–31) |
ctx.Age |
number | Customer age in years |
Customer Attributes: Tier
Current loyalty tier membership information.
| Expression Field | Type | Description |
|---|---|---|
ctx.TierName |
string | Current tier name (e.g., "Bronze", "Gold") |
ctx.TierDisplayOrder |
number | Tier rank (lower = base tier, higher = premium) |
ctx.DaysInCurrentTier |
number | Days since tier was last assigned |
Example
// Customer has been in Silver for over 60 days and is eligible for upgrade
ctx.TierName == "Silver" AND ctx.DaysInCurrentTier >= 60
Customer Attributes: Wallet & Points
Points balance and lifetime points activity.
| Expression Field | Type | Description |
|---|---|---|
ctx.PointsBalance |
number | Current available points |
ctx.LifetimeEarned |
number | Total points ever earned |
ctx.LifetimeRedeemed |
number | Total points ever redeemed |
Customer Attributes: RFM Metrics
Recency, Frequency, and Monetary scoring for customer segmentation. Each score is typically 1–5, with the total score being the sum (3–15).
| Expression Field | Type | Description |
|---|---|---|
ctx.RecencyScore |
number | How recently the customer transacted (1–5) |
ctx.FrequencyScore |
number | How often the customer transacts (1–5) |
ctx.MonetaryScore |
number | How much the customer spends (1–5) |
ctx.RfmTotalScore |
number | Combined RFM score (3–15) |
ctx.RfmSegment |
string | Segment label (e.g., "Champions", "At Risk") |
ctx.DaysSinceLastTransaction |
number | Days since the customer's last transaction |
Example
// Champion customers: high across all RFM dimensions
ctx.RecencyScore >= 4 AND ctx.FrequencyScore >= 4 AND ctx.MonetaryScore >= 4
Customer Attributes: Transaction Metrics
Transaction counts and spend totals across different time windows.
| Expression Field | Type | Description |
|---|---|---|
ctx.TransactionCount7Days |
number | Transactions in the last 7 days |
ctx.TransactionCount30Days |
number | Transactions in the last 30 days |
ctx.TransactionCount90Days |
number | Transactions in the last 90 days |
ctx.TransactionCountLifetime |
number | All-time transaction count |
ctx.TotalSpend7Days |
number | Total spend in the last 7 days |
ctx.TotalSpend30Days |
number | Total spend in the last 30 days |
ctx.TotalSpend90Days |
number | Total spend in the last 90 days |
ctx.TotalSpendLifetime |
number | All-time total spend |
ctx.AverageTransactionValue |
number | Average value per transaction |
ctx.AveragePointsPerTransaction |
number | Average points earned per transaction |
Customer Attributes: Behavioral
Customer behavior patterns and preferences.
| Expression Field | Type | Description |
|---|---|---|
ctx.PreferredDayOfWeek |
number | Most active day (0=Sunday, 6=Saturday) |
ctx.PreferredHourOfDay |
number | Most active hour (0–23) |
ctx.UniqueMerchantsVisited |
number | Number of distinct merchants visited |
ctx.FavoriteMerchantName |
string | Name of the most-visited merchant |
Example
// Weekend shoppers (Friday = 5, Saturday = 6)
ctx.PreferredDayOfWeek >= 5
// Multi-merchant loyalty customers
ctx.UniqueMerchantsVisited >= 3
Customer Attributes: Engagement
Voucher, referral, and points activity metrics.
| Expression Field | Type | Description |
|---|---|---|
ctx.VouchersClaimedCount |
number | Total vouchers claimed |
ctx.VouchersRedeemedCount |
number | Total vouchers redeemed |
ctx.VoucherRedemptionRate |
number | Redeemed / Claimed ratio (0.0–1.0) |
ctx.ReferralsMade |
number | Total successful referrals |
ctx.PointsEarnedThisMonth |
number | Points earned in the current month |
ctx.PointsRedeemedThisMonth |
number | Points redeemed in the current month |
ctx.PointsEarnedLastMonth |
number | Points earned in the previous month |
ctx.PointsRedeemedLastMonth |
number | Points redeemed in the previous month |
Customer Attributes: Lifecycle
Customer lifecycle stage and churn risk indicators.
| Expression Field | Type | Description |
|---|---|---|
ctx.LifecycleStage |
string | Current stage (e.g., "New", "Active", "At Risk", "Churned") |
ctx.ChurnProbability |
number | Churn probability (0.0–1.0) |
ctx.ChurnRisk |
string | Risk category (e.g., "Low", "Medium", "High") |
ctx.CustomerLifetimeValue |
number | Predicted customer lifetime value |
Customer Attributes: Referral
Referral program participation data.
| Expression Field | Type | Description |
|---|---|---|
ctx.HasReferralCode |
boolean | Whether the customer has generated a referral code |
ctx.WasReferred |
boolean | Whether the customer was referred by another customer |
Customer Attributes: Coupon Usage
Coupon and promotion usage statistics.
| Expression Field | Type | Description |
|---|---|---|
ctx.CouponsUsedCount |
number | Total coupons used |
ctx.TotalCouponSavings |
number | Total monetary savings from coupons |
Workflow Categories
Each workflow belongs to a category that determines its purpose and when it is evaluated.
| Category | Description | Typical Use |
|---|---|---|
| Tier Progression | Tier upgrade eligibility | Define spending/transaction thresholds for each tier |
| Tier Maintenance | Tier status maintenance | Minimum activity to keep current tier |
| Customer Segment | Dynamic segment membership | Auto-group customers by behavior or attributes |
| Point Earning | Base point earning rules | Custom earning rates based on conditions |
| Point Multiplier | Bonus point multipliers | 2x weekends, birthday bonuses, etc. |
| Promotion | Promotional campaigns | Target campaigns to specific customer groups |
| Birthday Reward | Birthday eligibility | Trigger birthday bonus during birth month |
| Referral Reward | Referral eligibility | Bonus for customers who refer others |
| Voucher Eligibility | Voucher claim restrictions | Tier-gated or spend-gated vouchers |
| Coupon Eligibility | Coupon application rules | Restrict coupons to specific segments |
| Churn Prevention | Churn risk triggers | Identify at-risk customers for re-engagement |
| Custom | Merchant-specific rules | Any custom business logic |
Example: Tier Progression
A complete workflow to evaluate customer eligibility for tier upgrades based on lifetime spending and activity.
{
"WorkflowName": "TierProgressionCheck",
"Rules": [
{
"RuleName": "PlatinumUpgrade",
"Expression": "ctx.LifetimeEarned >= 100000 AND ctx.TransactionCountLifetime >= 50 AND ctx.DaysSinceRegistration >= 90",
"SuccessEvent": "TierUpgrade:Platinum",
"ErrorMessage": "Does not meet Platinum requirements"
},
{
"RuleName": "GoldUpgrade",
"Expression": "ctx.LifetimeEarned >= 50000 AND ctx.TransactionCountLifetime >= 25 AND ctx.DaysSinceRegistration >= 30",
"SuccessEvent": "TierUpgrade:Gold",
"ErrorMessage": "Does not meet Gold requirements"
},
{
"RuleName": "SilverUpgrade",
"Expression": "ctx.LifetimeEarned >= 10000 AND ctx.TransactionCountLifetime >= 10",
"SuccessEvent": "TierUpgrade:Silver",
"ErrorMessage": "Does not meet Silver requirements"
}
]
}
Example: Customer Segments
Dynamic segments that automatically group customers by behavior. Use these for targeted messaging and promotions.
High-Value Segment
{
"WorkflowName": "SegmentHighValue",
"Rules": [
{
"RuleName": "HighValueCustomer",
"Expression": "ctx.Status == \"Active\" AND ctx.RfmTotalScore >= 12 AND ctx.TotalSpend90Days >= 30000 AND ctx.TransactionCount30Days >= 5",
"SuccessEvent": "Segment:HighValue"
}
]
}
New Customer Segment
{
"WorkflowName": "SegmentNewCustomers",
"Rules": [
{
"RuleName": "NewActiveCustomer",
"Expression": "ctx.DaysSinceRegistration <= 30 AND ctx.TransactionCountLifetime >= 1",
"SuccessEvent": "Segment:NewActive"
}
]
}
Regional Segment with Custom Attributes
{
"WorkflowName": "SegmentBaghdadVIP",
"Rules": [
{
"RuleName": "BaghdadHighSpender",
"Expression": "ctx.GetCustomAttribute(\"region\") == \"Baghdad\" AND ctx.TotalSpendLifetime >= 75000 AND ctx.Status == \"Active\"",
"SuccessEvent": "Segment:BaghdadVIP"
}
]
}
Example: Point Multipliers
Award bonus points based on customer attributes or behavior patterns.
Loyal Customer Double Points
{
"WorkflowName": "DoublePointsLoyalCustomer",
"Rules": [
{
"RuleName": "LoyaltyMultiplier",
"Expression": "ctx.DaysSinceRegistration >= 365 AND ctx.TransactionCountLifetime >= 50 AND ctx.TierDisplayOrder >= 2",
"SuccessEvent": "Multiplier:2x",
"Actions": {
"OnSuccess": {
"Name": "ApplyMultiplier",
"Context": { "multiplier": 2.0 }
}
}
}
]
}
Birthday Month Bonus
{
"WorkflowName": "BirthdayMonthBonus",
"Rules": [
{
"RuleName": "BirthdayBonus",
"Expression": "ctx.BirthdayMonth != null AND ctx.Status == \"Active\" AND ctx.TransactionCountLifetime >= 3",
"SuccessEvent": "BirthdayReward:BonusPoints",
"Actions": {
"OnSuccess": {
"Name": "ApplyMultiplier",
"Context": { "multiplier": 3.0 }
}
}
}
]
}
The BirthdayMonth check in the expression ensures the customer has a DOB on file. The actual month matching is handled by the system when evaluating Birthday Reward category workflows.
Example: Churn Prevention
Identify at-risk customers early so you can trigger re-engagement campaigns.
{
"WorkflowName": "ChurnRiskDetection",
"Rules": [
{
"RuleName": "HighChurnRisk",
"Expression": "ctx.ChurnProbability >= 0.7 AND ctx.DaysSinceLastTransaction >= 30 AND ctx.TransactionCountLifetime >= 5",
"SuccessEvent": "ChurnAlert:High",
"ErrorMessage": "Customer is not high churn risk"
},
{
"RuleName": "MediumChurnRisk",
"Expression": "ctx.ChurnProbability >= 0.4 AND ctx.ChurnProbability < 0.7 AND ctx.DaysSinceLastTransaction >= 14",
"SuccessEvent": "ChurnAlert:Medium"
},
{
"RuleName": "InactiveCustomer",
"Expression": "ctx.DaysSinceLastTransaction >= 60 AND ctx.PointsBalance > 0",
"SuccessEvent": "ChurnAlert:Inactive"
},
{
"RuleName": "DecliningEngagement",
"Expression": "ctx.PointsEarnedThisMonth < ctx.PointsEarnedLastMonth AND ctx.TransactionCount30Days < ctx.TransactionCount90Days / 3",
"SuccessEvent": "ChurnAlert:Declining"
}
]
}
Example: Promotions
Target promotions to specific customer groups using attribute combinations.
Re-engagement Offer for Lapsed Customers
{
"WorkflowName": "ReEngagementOffer",
"Rules": [
{
"RuleName": "LapsedHighValue",
"Expression": "ctx.DaysSinceLastTransaction >= 30 AND ctx.DaysSinceLastTransaction <= 90 AND ctx.TotalSpendLifetime >= 20000 AND ctx.Status == \"Active\"",
"SuccessEvent": "Promo:ReEngageHighValue"
}
]
}
Voucher Eligibility for Gold+ Tier
{
"WorkflowName": "PremiumVoucherAccess",
"Rules": [
{
"RuleName": "GoldPlusTierAccess",
"Expression": "ctx.TierDisplayOrder >= 3 AND ctx.Status == \"Active\" AND ctx.VoucherRedemptionRate >= 0.5",
"SuccessEvent": "VoucherAccess:Premium"
}
]
}
Referral Campaign with Custom Attributes
{
"WorkflowName": "ReferralCampaignQ1",
"Rules": [
{
"RuleName": "EligibleReferrer",
"Expression": "ctx.ReferralsMade >= 1 AND ctx.Status == \"Active\" AND ctx.DaysSinceRegistration >= 60 AND ctx.GetCustomAttribute(\"region\") != null",
"SuccessEvent": "Promo:ReferralBonus",
"Actions": {
"OnSuccess": {
"Name": "AwardBonusPoints",
"Context": { "points": 500 }
}
}
}
]
}
Testing Rules
Always test your workflow before activating it. The admin dashboard provides two testing methods.
Test with a Real Customer
Select a customer ID to evaluate the workflow against their actual context data. This lets you verify rules work with real-world data.
// Test request with real customer
{
"workflowJson": "{ ... your workflow JSON ... }",
"customerId": "customer-uuid-here"
}
Test with Mock Context
Provide a custom context object with specific values to test edge cases and boundary conditions.
// Test request with mock data
{
"workflowJson": "{ ... your workflow JSON ... }",
"mockContext": {
"status": "Active",
"tierName": "Silver",
"pointsBalance": 15000,
"lifetimeEarned": 52000,
"transactionCountLifetime": 30,
"daysSinceRegistration": 120,
"totalSpend90Days": 35000,
"rfmTotalScore": 11
}
}
Preview Affected Customers
After saving a workflow, use the "Preview Affected Customers" feature to see which existing customers match the rules. This shows up to the first 100 matching customers with their tier and points data.
Testing uses preview mode and does not create execution logs or trigger any actions. You can test freely without side effects.
Best Practices
Expression Writing
- Always check for null when using optional fields that may not be populated for all customers (e.g.,
ctx.Age != null AND ctx.Age >= 18). - Use parentheses when mixing
ANDandORto make logic explicit and avoid precedence issues. - Keep expressions readable — break complex conditions across multiple rules rather than writing one giant expression.
- Use descriptive RuleName values so execution logs are easy to understand (e.g., "HighSpenderGoldEligible" not "Rule1").
Workflow Design
- One purpose per workflow — don't mix tier progression and coupon eligibility in the same workflow.
- Order rules by specificity — put the most specific rules first (Platinum before Gold before Silver).
- Set meaningful SuccessEvents — they appear in logs and drive downstream actions.
- Use ErrorMessage for debugging — describe why a rule might fail so logs are informative.
- Disable instead of delete — set
Enabled: falseon individual rules or deactivate the workflow to preserve history.
Performance
- Customer context is cached for 4 hours — changes to customer data may take up to 4 hours to reflect in rule evaluations (or use manual refresh).
- Compiled rule engines are cached for 5 minutes — workflow JSON changes take effect within 5 minutes.
- Use the Priority field to control evaluation order when multiple workflows in the same category exist.
- Use EffectiveFrom/EffectiveTo for time-limited promotions instead of manually toggling active/inactive.
Custom Attributes
- Use consistent key names — custom attributes are case-sensitive. Standardize on camelCase or snake_case.
- Document your custom attributes — keep a record of what keys your team uses and their expected values.
- GetCustomAttribute returns a string — if you store numeric values, the comparison will be string-based. Design your rules accordingly.