RODEO

Customer Retention

Adaptive retention flows with engagement-driven escalation

Retain an insurance customer approaching renewal across multiple channels with slow-burn pacing and engagement-driven adjustment.

Intention

const retainIntention = await rodeo.intentions.create({
  type: "retain",
  description: "Retain policyholder approaching March 1 renewal",
  success_condition: { renewed: true },
  failure_conditions: [
    { cancelled: true },
    { noResponse: true, after: "14 days" },
  ],
  alignment: {
    audienceBenefit: "Rate lock, personalized coverage review, loyalty discount",
    mutuality: "Customer keeps coverage at preferred rate, insurer retains premium revenue",
  },
});

Audience

const customer = await rodeo.audiences.create({
  type: "individual",
  context: {
    name: "Pat",
    accountId: "POL-2024-4821",
    currentPremium: 1840,
    yearsAsCustomer: 3,
    claimsHistory: "none",
  },
  bounds: { maxTouchesPerDay: 1, maxTouchesPerWeek: 3, cooldownMinutes: 240 },
  rhythm: { preferredTimes: ["09:00-11:00", "14:00-16:00"] },
});

Scene

const scene = await rodeo.scenes
  .build()
  .intention(retainIntention.id)
  .audience(customer.id)
  .arc("slow_burn")
  .constraints({ maxTouches: 6, maxDuration: "14d", budget: 30 })
  .onDecline("terminate")
  .touch({
    sequence: 1,
    channel_id: email.id,
    content: {
      subject: "Your coverage summary for 2025",
      body: "Personalized coverage review with savings opportunities...",
    },
    tone: { warmth: "warm", urgency: "none", ask: "none" },
    timing: { mode: "absolute", at: "2025-02-01T09:00:00Z" },
  })
  .touch({
    sequence: 2,
    channel_id: sms.id,
    content: { body: "Quick heads up — your renewal is coming up March 1. We've locked in your current rate." },
    tone: { warmth: "warm", urgency: "gentle", ask: "implicit" },
    timing: { mode: "relative", after: "3d", anchor: "touch:1" },
  })
  .rule({
    condition: { type: "engagement", metric: "opened", count: 0 },
    action: "escalate",
    params: { to: "phone" },
  })
  .rule({
    condition: { type: "behavior", metric: "visited_competitors" },
    action: "accelerate",
    params: { factor: 2 },
  })
  .rule({
    condition: { type: "conversion", metric: "renewed" },
    action: "terminate",
  })
  .create();

Ledger

const retentionResults = await rodeo.ledger.query({
  intentionType: "retain",
  since: "2025-01-01T00:00:00Z",
});

const successRate = retentionResults.entries.filter(
  (e) => e.outcome === "succeeded"
).length / retentionResults.total;