How a Klaviyo limitation became an RFM asset.
I was halfway through building an RFM segmentation system in Klaviyo when I hit a wall.
The architecture made sense on paper. Recency, frequency, and monetary segments built independently — five tiers each, fifteen segments total. Then composite segments that read those base segments to classify customers into groups: Champions, Loyal Customers, At Risk, Can't Lose Them. Clean. Logical. Standard practice.
Then I tried to build it in Klaviyo and discovered the problem.
Klaviyo segments cannot reference other segments.
A segment condition can reference a list. It can reference event history, profile properties, or predictive analytics data. What it cannot do is say "is a member of segment X." If you want to build a composite segment that reads from base segments, you cannot. The platform simply does not support it.
This is the kind of limitation that stops a lot of builds cold. You either abandon the architecture or you start hacking around it in ways that make the system fragile and hard to maintain.
I did neither. I found a cleaner solution — and the limitation turned out to be a feature.
The Problem With Referencing Segments Directly
Before getting to the solution, it is worth understanding why the limitation matters.
An RFM system has two layers. The first layer classifies customers on each dimension individually — how recently they purchased (R), how often they purchase (F), and how much they spend (M). The second layer combines those scores to produce a composite classification: this customer is a Champion, that customer is At Risk.
In a traditional database, you would store the R, F, and M scores as fields on the customer record, then query those fields to produce the composite classification. Klaviyo's segment logic is not a database query. It evaluates conditions against profile data and event history. So the question becomes: where do you store the R, F, and M scores so the composite logic can read them?
The obvious answer — use segment membership as the store — does not work. The correct answer is profile properties.
The Solution: Flows Write Properties, Segments Read Properties
Here is how the system actually works.
Layer 1 — Base Segments
Fifteen segments are built: R1 through R5, F1 through F5, M1 through M5. Each segment detects where a customer currently falls on that dimension based on event history. R5 is a customer who purchased in the last 30 days. R1 is a customer who purchased 181 to 365 days ago. These segments do nothing except detect.
Layer 2 — Property-Writing Flows
Each base segment has a corresponding flow triggered by segment entry. When a customer enters R3, a flow fires and writes the integer 3 to a profile property called R. When a customer enters F5, a flow fires and writes 5 to a property called F. Fifteen flows, one action each. But writing the property is not the final action. The last step in every property-writing flow is to add the profile to a list called RFM Trigger.
That list is the key to everything that follows.
Layer 3 — The RFM Trigger List (Universal Relay)
Here is the problem the RFM Trigger list solves.
A customer's R, F, and M scores are independent. A customer can move from R3 to R2 without their F or M changing. When that happens, the R property updates to 2, and the customer's composite classification needs to be re-evaluated. But the classification flow needs a trigger — something that tells it a profile is ready to be re-evaluated.
The naive solution is to trigger the classification flow from each base segment. Fifteen base segments, fifteen classification triggers, fifteen copies of the same classification logic to maintain in sync. Every time you update the decision tree in one flow, you need to update it in fourteen others.
The RFM Trigger list eliminates that entirely.
Every property-writing flow — all fifteen of them — ends with the same final action: add the profile to RFM Trigger. The list becomes a universal relay. It does not matter whether R changed, F changed, or M changed. It does not matter which segment the profile entered or exited. Every qualifying event funnels through the same list, into the same flow.
Layer 4 — Classification
RFM Trigger is the master trigger to send the profile with the updated value to the RFM Update flow.
The RFM Update flow reads the R, F, and M properties and evaluates them through an ordered series of conditional splits. Champions are R4 or R5, F4 or F5, M4 or M5. At Risk customers are R2 with M4 or M5. Each classification path writes a Segment property with the correct label — "Champions," "At Risk," "Can't Lose Them" — as a text value.
The final action of every branch in the RFM Update flow is to remove the profile from RFM Trigger. The list self-cleans continuously. Profiles enter when a property changes, get classified, and exit. The list stays empty. The system stays current.
Layer 5 — Composite Segments
The nine composite segments have the simplest possible definitions: Segment equals Champions. Segment equals At Risk. One condition each. Zero overlap by design. Because they read a single stored property instead of trying to evaluate dimensional logic, they never conflict with each other and they never need to be updated when thresholds change.
Why This Architecture Is Better Than What I Originally Planned
The segment reference limitation forced a separation of concerns that makes the system fundamentally more sound.
In the original architecture, detection and classification were collapsed into a single layer. Composite segments would have contained the full dimensional logic — R4 or R5, AND F4 or F5, AND M4 or M5 — which means every composite segment would need to be updated individually whenever thresholds changed. Nine complex segment definitions, each with interdependent logic, each requiring manual updates in sync with the others.
In the improved architecture, detection and classification are separated cleanly. If the recency thresholds change — maybe R5 should now cover 0 to 45 days instead of 0 to 30 — you update a single base segment. The property-writing flow carries the new value forward automatically. The classification flow re-evaluates. The composite segments update without touching a single definition. The system is self-consistent because it reads from a single source of truth.
A note on list discipline: the RFM Trigger list is a permanent fixture in this architecture, but it is not a storage list. The rule is two storage lists — Email and SMS. Those are the lists that define your audience, hold your subscribers, and determine who receives your marketing. That number does not move.
Lists beyond those two are tools. A list can do things a segment cannot do — it can serve as a flow trigger, act as a relay, gate entry into a sequence, or serve as a temporary holding mechanism for a specific task. Klaviyo's flow architecture requires lists in ways that segments simply cannot satisfy. Using them as tools is not a violation of list discipline. It is how the platform is designed to be used.
The discipline is in how those tool lists are managed. A tool list must have a defined purpose, and it must clean itself when that purpose is fulfilled. RFM Trigger processes profiles and empties continuously. A temporary list built for a one-time import gets deleted when the import is resolved. No list exists without a reason. No list accumulates without intent. The goal is maximum order — and a Klaviyo account that looks clean is a Klaviyo account that runs clean.
The Unexpected Benefit: R, F, and M as Live DTL Variables
There is a second benefit that did not occur to me until the build was complete.
Because R, F, and M are stored as profile properties, they are available as Django Template Language variables in every email in the account.
{{ person.R }}
{{ person.F }}
{{ person.M }}
{{ person.Segment }}This means email content can be dynamically adjusted based on RFM values without building separate flows or creating segmented sends. An abandoned cart email can offer a discount to M1 customers while showing standard copy to M4 and M5 customers — not because they are in different flows, but because the email reads the property and renders the appropriate block.
The limitation that prevented me from using segment membership as a data store forced me to use profile properties instead. Profile Properties are a cleaner, more functional alternative to segments for this particular situation.
The Big Picture
Klaviyo is a highly structured environment by necessity. Without that structure, the system cannot function — but the structure creates challenges. Those challenges drive the need for a deeper understanding of how every component of the system interacts with every other component.
The segment limitation pushed me to a more elegant, more robust solution to the custom RFM system.
If Klaviyo had allowed segments to reference segments, I would have built a system that worked. It would not have been this system.
MJE Consult builds email and text marketing systems for ecommerce businesses. If you are evaluating your Klaviyo architecture or starting from scratch, the Client Discovery Survey is the place to start.