Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.kaireonai.com/llms.txt

Use this file to discover all available pages before exploring further.

Tutorial: Onboarding & Activation

The first 30 days of a customer relationship are decisive. A new customer who hits their activation moment — the first action that proves the product works for them — converts to long-term retention. A new customer who doesn’t, churns silently. This tutorial builds a flow that nudges new customers through the activation funnel, escalates if they stall, and stops the program the moment activation fires (so the next message is the right one for an active customer, not another activation prompt). Business scenario: a SaaS product’s activation event is “first project created and shared with a teammate”. Roughly 40% of signups never get there. Marketing wants a 30-day activation sequence: day 1 welcome → day 3 tutorial nudge → day 7 use-case examples → day 14 success-story → day 21 personal-help offer → day 28 cancellation-prevention. The moment a customer hits the activation event, the sequence terminates. What you’ll build:
  • A Customer schema with signup, activation, and step-completion timestamps
  • 6 onboarding offers staged across the 30-day window
  • A flow that selects the correct step based on tenure-since-signup
  • An auto-suppression rule on activation event
  • A direction-aware contact policy (outbound emails only Mon–Fri 9–6 customer time)
Prerequisites: A running KaireonAI instance, an admin API key. Time: 25–30 minutes.

1. Define the schema

curl -X POST $BASE/api/v1/schemas -d '{
  "name": "Customer",
  "fields": [
    { "name": "signupDate",         "type": "datetime" },
    { "name": "activatedAt",        "type": "datetime", "nullable": true },
    { "name": "lastLoginAt",        "type": "datetime", "nullable": true },
    { "name": "projectsCreated",    "type": "integer", "default": 0 },
    { "name": "teammateInvitesSent","type": "integer", "default": 0 },
    { "name": "tutorialCompleted",  "type": "boolean", "default": false },
    { "name": "useCaseSelected",    "type": "string", "nullable": true },
    { "name": "timezone",           "type": "string" },
    { "name": "plan",               "type": "string" }
  ]
}'
activatedAt being non-null is the signal that the program should stop. The qualification rules below all gate on activatedAt == null.

2. Computed fields — tenure since signup

curl -X POST $BASE/api/v1/categories -d '{
  "name": "Onboarding",
  "computedFields": [
    {
      "name": "daysSinceSignup",
      "formula": "round((now() - customer.signupDate) / 86400)",
      "outputType": "integer"
    },
    {
      "name": "isActive",
      "formula": "lastLoginAt != null && (now() - lastLoginAt) < 604800",
      "outputType": "boolean"
    },
    {
      "name": "completionPct",
      "formula": "((tutorialCompleted ? 1 : 0) + (projectsCreated > 0 ? 1 : 0) + (teammateInvitesSent > 0 ? 1 : 0)) / 3 * 100",
      "outputType": "integer"
    }
  ]
}'
completionPct is a quick health score: 0 means “haven’t started”, 100 means “completed all activation precursors”. Useful for the in-app dashboard.

3. Define the 6 staged offers

Each offer has a tenure window that activates only during its slot. The qualification rule uses daysSinceSignup to pick the right one.
# Day 1 — Welcome + setup link
curl -X POST $BASE/api/v1/offers -d '{
  "name": "Day 1 — Welcome",
  "category": "Onboarding",
  "priority": 60,
  "qualificationRules": [
    { "field": "daysSinceSignup", "op": ">=", "value": 0 },
    { "field": "daysSinceSignup", "op": "<=", "value": 2 },
    { "field": "activatedAt",     "op": "==", "value": null }
  ]
}'

# Day 3 — Tutorial nudge
curl -X POST $BASE/api/v1/offers -d '{
  "name": "Day 3 — Try the tutorial",
  "category": "Onboarding",
  "priority": 65,
  "qualificationRules": [
    { "field": "daysSinceSignup",     "op": ">=", "value": 3 },
    { "field": "daysSinceSignup",     "op": "<=", "value": 5 },
    { "field": "tutorialCompleted",   "op": "==", "value": false },
    { "field": "activatedAt",         "op": "==", "value": null }
  ]
}'

# Day 7 — Use-case examples
curl -X POST $BASE/api/v1/offers -d '{
  "name": "Day 7 — See real-world use cases",
  "category": "Onboarding",
  "priority": 70,
  "qualificationRules": [
    { "field": "daysSinceSignup",   "op": ">=", "value": 7 },
    { "field": "daysSinceSignup",   "op": "<=", "value": 9 },
    { "field": "projectsCreated",   "op": "==", "value": 0 },
    { "field": "activatedAt",       "op": "==", "value": null }
  ]
}'

# Day 14 — Success story
curl -X POST $BASE/api/v1/offers -d '{
  "name": "Day 14 — How [SimilarCustomer] does it",
  "category": "Onboarding",
  "priority": 75,
  "qualificationRules": [
    { "field": "daysSinceSignup",   "op": ">=", "value": 14 },
    { "field": "daysSinceSignup",   "op": "<=", "value": 16 },
    { "field": "activatedAt",       "op": "==", "value": null }
  ]
}'

# Day 21 — Personal-help offer (high cost — only for active users)
curl -X POST $BASE/api/v1/offers -d '{
  "name": "Day 21 — Book a setup call",
  "category": "Onboarding",
  "priority": 85,
  "businessValue": 80,
  "margin": 25,
  "qualificationRules": [
    { "field": "daysSinceSignup",   "op": ">=", "value": 21 },
    { "field": "daysSinceSignup",   "op": "<=", "value": 23 },
    { "field": "isActive",          "op": "==", "value": true },
    { "field": "activatedAt",       "op": "==", "value": null }
  ]
}'

# Day 28 — Cancellation prevention (last chance)
curl -X POST $BASE/api/v1/offers -d '{
  "name": "Day 28 — Stay another month, on us",
  "category": "Onboarding",
  "priority": 95,
  "businessValue": 50,
  "margin": -100,
  "qualificationRules": [
    { "field": "daysSinceSignup",   "op": ">=", "value": 28 },
    { "field": "daysSinceSignup",   "op": "<=", "value": 30 },
    { "field": "completionPct",     "op": "<=",  "value": 33 },
    { "field": "activatedAt",       "op": "==", "value": null }
  ]
}'
The negative margin on the Day-28 offer is intentional — it’s a loss-leader to prevent cancellation, justified by retained LTV that the platform won’t see for months.

4. Onboarding contact policy — Mon-Fri business hours only

Onboarding messages over the weekend feel desperate. Office hours land better.
curl -X POST $BASE/api/v1/contact-policies -d '{
  "name": "Onboarding — weekdays only",
  "scope": "category:Onboarding",
  "ruleType": "time_window",
  "config": {
    "allowedDays":   ["mon","tue","wed","thu","fri"],
    "allowedHours":  ["09:00-18:00"],
    "timezoneSource":"customer.timezone"
  }
}'

curl -X POST $BASE/api/v1/contact-policies -d '{
  "name": "Onboarding — max 1 per day",
  "scope": "category:Onboarding",
  "ruleType": "frequency_cap",
  "windowDays": 1,
  "maxImpressions": 1
}'
The combination is: at most one onboarding contact per customer per day, only Mon–Fri 9am–6pm local. Saturday signups still get their day-1 welcome on the following Monday morning.

5. Wire the onboarding flow

curl -X POST $BASE/api/v1/decision-flows -d '{
  "name": "Onboarding & Activation",
  "config": {
    "nodes": [
      { "id": "inv",     "type": "inventory", "config": { "category": "Onboarding" } },
      { "id": "enr",     "type": "enrich",    "config": { "schema": "Customer" } },
      { "id": "compute", "type": "compute" },
      { "id": "qual",    "type": "qualify" },
      { "id": "cp",      "type": "contact_policy" },
      { "id": "score",   "type": "score",     "config": { "method": "priority_weighted" } },
      { "id": "rank",    "type": "rank" },
      { "id": "out",     "type": "response" }
    ]
  }
}'
Note method: priority_weighted — onboarding is deterministic by design. The right offer for day-7 is always the day-7 offer, not whatever the ML model thinks. Priority-weighted scoring honors the priority field directly.

6. The auto-stop event

The flow self-terminates the moment activation fires. The trigger is a record on /respond:
curl -X POST $BASE/api/v1/respond -d '{
  "customerId": "cust_new_1",
  "event": "activated",
  "context": { "trigger": "first_project_shared" }
}'
The platform writes customers.activatedAt = now(). The next /recommend call sees activatedAt != null in the enrich step, all onboarding offers fail qualification (each has activatedAt == null in their rules), and the flow returns no decisions — the program has stopped for this customer. This is the activation-detection pattern: instead of writing complex stop-logic in the flow, encode the stop condition as a qualification rule on every offer. Activation flips a bit; rules see it; offers stop firing.

7. What success looks like

Track three numbers on the Operations dashboard:
MetricSourceTarget
Activation rate (30d)customers.activatedAt IS NOT NULL ÷ signups> 60%
Avg days to activateavg(activatedAt - signupDate)< 14 days
Onboarding cost per activated customerdecisions.where(category=Onboarding).count × cost_per_message ÷ activations< $0.50
If activation rate drops below target, the most common cause is the schema isn’t capturing the right signal. Add a field, update the computed isActive definition, re-deploy — the flow picks up the new signal at next decision.

8. What’s next

  • Activation funnel A/B. Run a 10% holdout that gets no onboarding messages. If activation rate is similar, the program is just decorating outcomes that would have happened anyway — surface that to the team before adding more sequences.
  • Plan-specific tracks. Enterprise customers need a different onboarding (longer, more white-glove) than free-tier. Add plan to qualification rules to fork the flow per tier.
  • Inactivity-triggered side flows. If lastLoginAt > 5 days ago while still in onboarding, route to a re-engagement sub-flow instead of the next-step offer. See Journeys for the branching pattern.
  • In-product nudges. Some onboarding offers are not emails — they’re in-app banners. Use channel: in_app to surface the right step contextually during a session, not on a schedule.

See Churn Prevention, Cross-Sell, and Winback for the other three core lifecycle flows. Together they cover the four phases of the customer relationship: onboard, grow, retain, recover.