← Back to Chaay

Rule Workflow Expressions

Complete guide to writing simple and complex rule expressions for the Chaay loyalty platform

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.

Key Concept

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:

  1. Context is built — The system aggregates customer data (identity, tier, wallet, transactions, RFM scores, etc.) into a CustomerContextData object. This is cached for 4 hours and refreshed automatically.
  2. 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.
  3. Rules are evaluated — Each rule's expression is evaluated against the customer context. The engine returns which rules matched and which failed.
  4. 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"
Operator Precedence

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
Tip

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 } } } } ] }
Tip

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.

Important

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 AND and OR to 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: false on 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.