Tutorial: Winback
Winback campaigns target customers who’ve gone quiet — they had a relationship, they stopped, and the question is whether a well-timed offer can revive them. The flow has to know when each customer went silent, what they did before, and what offer might bring them back without over-messaging the lapsed-and-uninterested. Business scenario: an e-commerce platform’s data shows a long tail of customers who haven’t purchased in 90+ days. Instead of a blanket discount email, the platform routes lapsed-but-recoverable customers to a personalized offer matched to their prior purchase category — and routes the truly lost customers to no message at all. What you’ll build:- A Customer schema with recency and lifetime-value signals
- A category with a computed personalization field
- 3 winback offers (Apparel, Electronics, Home) with recency-aware eligibility
- Quiet-hour protection via a time-window contact policy
- A 3-touch cap and a flow-level holdout for measuring incremental winback
curl + jq.
Time: 25–30 minutes.
0. Set up your shell
1. Define the schema with lapse signals
The gate needs adaysSinceLastPurchase value it can compare against. The formula engine can’t compute a date difference (it has no epoch/date-diff function), so daysSinceLastPurchase is a real column that your data pipeline refreshes on a schedule — one add_field transform in a daily pipeline keeps it current. Store it alongside the lifetime-value signals the eligibility rules read.
consent* fields are non-negotiable. Winback hits dormant customers; the platform’s consent stage suppresses candidates on channels whose consent was revoked.
2. Create the category with a computed personalization field
Computed custom fields run in the Compute stage, near the end of the flow, and their results are merged into each decision’spersonalization object. They are output values — a personalized number or string you pass to the creative — not gates. (Gating happens earlier, in the Qualify stage, against enriched schema columns.)
A computed field’s formula uses the safe formula engine: arithmetic, comparison, ternary (cond ? a : b), and functions like round, min, max, coalesce, if, concat. There is no today(), no &&/||, and no date math — combine conditions with nested ternaries instead. outputType is number or text only.
loyaltyScore is a lightweight index the creative can key off (e.g. show a richer incentive to high-loyalty lapsers). The formula reads enriched customer fields via the customer. prefix and produces a single number surfaced in the response.
3. Define winback offers per category
Each offer belongs to theWinback category (via categoryId, so the computed field runs for it) and targets one prior-purchase category. Offers are active; eligibility is attached in step 4.
4. Attach recency-aware eligibility
Each offer’s rules gate on enriched columns: the right prior category, a lapse window of 90–365 days, and a “was a real customer” test (lifetimePurchases >= 3 and lifetimeRevenue >= 50, expressed as two AND-combined rules). The 365-day upper bound matters — a customer gone 18 months needs win-back-from-cold, a different program.
5. Quiet hours via a time-window contact policy
A winback email at 2am does worse than no email. Quiet hours are a contact policy, not a channel setting: atime_window rule blocks any candidate it’s scoped to when the current time falls outside the allowed window. startHour/endHour are 0–23; timezone is an IANA zone the rule evaluates against.
time_window rule uses a single zone per policy; for multi-region campaigns, create one policy per zone and scope it to the relevant channel or segment.) You can add "daysOfWeek": ["Mon","Tue","Wed","Thu","Fri"] to also skip weekends.
6. Cap the winback sequence at 3 touches
7. Wire the winback flow
The flow enriches the customer, qualifies against the recency rules, applies both contact policies, scores with an uplift-weighted formula, then runs the Compute node to attachloyaltyScore to each surviving decision. Node phases run 1 → 2 → 3 in order.
upliftWeight: 0.6 is intentionally high. Winback is where the uplift signal earns its keep — half the lapsed customers would return anyway (sure things) and the other half won’t return regardless (lost causes). The narrow band of persuadable customers is the ROI of the whole program. (The uplift term influences ranking once the 15% holdout has generated enough treated-vs-control outcomes — see Uplift Modeling.)
8. Recommend for a lapsed apparel customer
loyaltyScore to the surviving decision. The electronics and home offers were filtered at qualification because this customer’s primaryCategory is apparel.
Now try the same request when it’s 2am in the policy’s timezone. The time_window policy blocks every candidate, so decisions comes back empty. Add ?explain=true to see why — the response then includes a rejectedOffers array with the policy’s reason:
9. Measuring incremental winback
Winback measures poorly because returning customers would often have returned anyway. The flow’sflowConfig.experiment (set in step 7) already withholds offers from 15% of traffic — a real holdout. After the program has run long enough, the experiments z-test compares revenue per customer between the treated 85% and the 15% holdout and confidence-bounds the difference, so you can tell finance “winback drives X-low, $X-high]”.
This is the only honest way to claim winback ROI.
10. What’s next
- Winback-from-cold flow. Customers gone > 365 days need a different program (re-acquisition price, full re-onboarding). Create a sibling flow with a
daysSinceLastPurchase > 365gate. - Multi-step sequence. Chain initial email → SMS reminder → final-call via a Journey, with the 3-touch cap ensuring each customer gets at most 3 touches.
- Channel-preference learning. Feed each customer’s engaged channel back into the Score node’s Relevance term so later winback runs prefer their surface.
- Segment by value. Split the flow on
lifetimeRevenuethresholds so premium lapsers get an account-manager call, not a discount email.
See Cross-Sell for the existing-customer growth scenario, Churn Prevention for the retention scenario, or Computed Values for the formula-engine reference.