> ## Documentation Index
> Fetch the complete documentation index at: https://docs.usefini.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrate from Zendesk bots to Fini

> Move from Zendesk's native bots and flows to a Fini agent running inside Zendesk: inventory your flows, map them to knowledge and intent rules, run in shadow mode, and cut over brand by brand.

export const StepExplorer = ({title, steps = [], hint = "Click a step, or use Next."}) => {
  const FV = {
    lime: "#C3EE5E",
    ink: "#131415",
    line: "rgba(127,127,127,0.28)",
    soft: "rgba(127,127,127,0.07)",
    softer: "rgba(127,127,127,0.04)",
    muted: "rgba(127,127,127,0.95)",
    pass: "#C3EE5E",
    warn: "#FFB020",
    fail: "#FF4D4D",
    radius: 14
  };
  const fvCard = {
    border: `1px solid ${FV.line}`,
    borderRadius: FV.radius,
    padding: 18,
    margin: "20px 0",
    background: FV.softer
  };
  const fvChip = active => ({
    border: `1px solid ${active ? FV.lime : FV.line}`,
    background: active ? FV.lime : "transparent",
    color: active ? FV.ink : "inherit",
    borderRadius: 999,
    padding: "6px 12px",
    fontSize: 13,
    fontWeight: 600,
    cursor: "pointer",
    lineHeight: 1.2
  });
  const fvBtn = primary => ({
    border: `1px solid ${primary ? FV.lime : FV.line}`,
    background: primary ? FV.lime : "transparent",
    color: primary ? FV.ink : "inherit",
    borderRadius: 10,
    padding: "7px 14px",
    fontSize: 13,
    fontWeight: 600,
    cursor: "pointer"
  });
  const fvLabel = {
    fontSize: 11,
    fontWeight: 700,
    letterSpacing: "0.08em",
    textTransform: "uppercase",
    opacity: 0.6,
    marginBottom: 8
  };
  const [i, setI] = useState(0);
  const s = steps[i] || ({});
  return <div style={fvCard}>
      {title && <div style={fvLabel}>{title}</div>}
      <div style={{
    display: "flex",
    alignItems: "center",
    gap: 0,
    overflowX: "auto",
    paddingBottom: 6
  }}>
        {steps.map((st, k) => <div key={k} style={{
    display: "flex",
    alignItems: "center",
    flex: k === steps.length - 1 ? "0 0 auto" : "1 0 auto"
  }}>
            <button onClick={() => setI(k)} title={st.title} style={{
    width: 34,
    height: 34,
    borderRadius: 999,
    flex: "0 0 auto",
    cursor: "pointer",
    fontWeight: 700,
    fontSize: 13,
    border: `2px solid ${k <= i ? FV.lime : FV.line}`,
    background: k === i ? FV.lime : k < i ? "rgba(195,238,94,0.25)" : "transparent",
    color: k === i ? FV.ink : "inherit"
  }}>{k + 1}</button>
            {k < steps.length - 1 && <div style={{
    height: 2,
    minWidth: 18,
    flex: 1,
    background: k < i ? FV.lime : FV.line,
    margin: "0 4px"
  }} />}
          </div>)}
      </div>
      <div style={{
    display: "flex",
    gap: 6,
    flexWrap: "wrap",
    margin: "6px 0 14px"
  }}>
        {steps.map((st, k) => <span key={k} onClick={() => setI(k)} style={{
    fontSize: 12,
    cursor: "pointer",
    opacity: k === i ? 1 : 0.55,
    fontWeight: k === i ? 700 : 500,
    marginRight: 8
  }}>{st.label}</span>)}
      </div>
      <div style={{
    border: `1px solid ${FV.line}`,
    borderRadius: 12,
    padding: 16,
    background: FV.soft,
    minHeight: 120
  }}>
        {s.meta && <div style={{
    ...fvLabel,
    opacity: 0.75
  }}>{s.meta}</div>}
        <div style={{
    fontSize: 17,
    fontWeight: 700,
    marginBottom: 6
  }}>{s.title}</div>
        {s.body && <div style={{
    fontSize: 14.5,
    lineHeight: 1.6,
    opacity: 0.9
  }}>{s.body}</div>}
        {s.points && <ul style={{
    margin: "10px 0 0",
    paddingLeft: 18,
    fontSize: 14,
    lineHeight: 1.6
  }}>
            {s.points.map((p, k) => <li key={k}>{p}</li>)}
          </ul>}
      </div>
      <div style={{
    display: "flex",
    justifyContent: "space-between",
    alignItems: "center",
    marginTop: 12,
    gap: 8
  }}>
        <span style={{
    fontSize: 12,
    opacity: 0.6
  }}>{hint}</span>
        <div style={{
    display: "flex",
    gap: 8
  }}>
          <button style={fvBtn(false)} disabled={i === 0} onClick={() => setI(v => Math.max(0, v - 1))}>Back</button>
          <button style={fvBtn(true)} onClick={() => setI(v => (v + 1) % Math.max(1, steps.length))}>{i === steps.length - 1 ? "Start over" : "Next"}</button>
        </div>
      </div>
    </div>;
};

export const ChecklistMeter = ({title = "Checklist", items = [], results = {}, disclaimer}) => {
  const FV = {
    lime: "#C3EE5E",
    ink: "#131415",
    line: "rgba(127,127,127,0.28)",
    soft: "rgba(127,127,127,0.07)",
    softer: "rgba(127,127,127,0.04)",
    muted: "rgba(127,127,127,0.95)",
    pass: "#C3EE5E",
    warn: "#FFB020",
    fail: "#FF4D4D",
    radius: 14
  };
  const fvCard = {
    border: `1px solid ${FV.line}`,
    borderRadius: FV.radius,
    padding: 18,
    margin: "20px 0",
    background: FV.softer
  };
  const fvChip = active => ({
    border: `1px solid ${active ? FV.lime : FV.line}`,
    background: active ? FV.lime : "transparent",
    color: active ? FV.ink : "inherit",
    borderRadius: 999,
    padding: "6px 12px",
    fontSize: 13,
    fontWeight: 600,
    cursor: "pointer",
    lineHeight: 1.2
  });
  const fvBtn = primary => ({
    border: `1px solid ${primary ? FV.lime : FV.line}`,
    background: primary ? FV.lime : "transparent",
    color: primary ? FV.ink : "inherit",
    borderRadius: 10,
    padding: "7px 14px",
    fontSize: 13,
    fontWeight: 600,
    cursor: "pointer"
  });
  const fvLabel = {
    fontSize: 11,
    fontWeight: 700,
    letterSpacing: "0.08em",
    textTransform: "uppercase",
    opacity: 0.6,
    marginBottom: 8
  };
  const [on, setOn] = useState(() => items.map(() => false));
  const n = on.filter(Boolean).length;
  const reqMissing = items.some((it, i) => it.required && !on[i]);
  const pct = items.length ? Math.round(n / items.length * 100) : 0;
  const msg = n === items.length ? results.complete : reqMissing && results.missingRequired ? results.missingRequired : results.partial;
  return <div style={fvCard}>
      <div style={{
    display: "flex",
    justifyContent: "space-between",
    alignItems: "baseline"
  }}>
        <div style={fvLabel}>{title}</div>
        <div style={{
    fontSize: 13,
    fontWeight: 700
  }}>{n} / {items.length}</div>
      </div>
      <div style={{
    height: 8,
    borderRadius: 999,
    background: FV.soft,
    overflow: "hidden",
    marginBottom: 12
  }}>
        <div style={{
    width: `${pct}%`,
    height: "100%",
    background: FV.lime,
    transition: "width .3s"
  }} />
      </div>
      {items.map((it, i) => <label key={i} style={{
    display: "flex",
    gap: 10,
    alignItems: "flex-start",
    padding: "8px 4px",
    borderTop: i ? `1px solid ${FV.line}` : "none",
    cursor: "pointer"
  }}>
          <input type="checkbox" checked={on[i]} onChange={() => setOn(o => o.map((v, j) => j === i ? !v : v))} style={{
    marginTop: 3,
    accentColor: FV.lime
  }} />
          <span style={{
    fontSize: 14,
    lineHeight: 1.5
  }}>
            {it.label}{it.required && <span style={{
    fontSize: 11,
    fontWeight: 700,
    marginLeft: 6,
    opacity: 0.6
  }}>REQUIRED</span>}
            {it.detail && <span style={{
    display: "block",
    fontSize: 12.5,
    opacity: 0.65
  }}>{it.detail}</span>}
          </span>
        </label>)}
      {msg && <div style={{
    marginTop: 12,
    fontSize: 13.5,
    padding: "10px 12px",
    borderRadius: 10,
    background: n === items.length ? "rgba(195,238,94,0.16)" : FV.soft
  }}>{msg}</div>}
      {disclaimer && <div style={{
    fontSize: 12,
    opacity: 0.6,
    marginTop: 8
  }}>{disclaimer}</div>}
    </div>;
};

To move from Zendesk's native bots to Fini (usefini.com), you rebuild each existing flow as either Fini knowledge or a [Rulebook](/en/automations/rulebook) intent rule, connect Fini to Zendesk, run it in shadow mode (internal notes only) next to your current setup, then use **Bot Routing** to cut over one brand and channel at a time before turning the old bot off. Your customers stay in Zendesk the whole time, and your team keeps working in the same views.

This guide assumes Zendesk stays your helpdesk. Fini reads incoming tickets and chats, replies on the brands and channels you map, and escalates to your team inside Zendesk.

Zendesk bot flows aren't imported: you rebuild them in Fini, and **Generate with AI** speeds up drafting each rule.

## The migration at a glance

| Phase | What you do | Where in Fini |
| - | - | - |
| 1. Inventory | List every bot flow, what it does, and how much traffic it handles. | Outside Fini |
| 2. Map | Decide whether each flow becomes knowledge, an intent rule, a reply rule, or nothing. | Planning |
| 3. Build knowledge | Ingest your help center and turn it into reviewed Articles. | [Sources](/en/knowledge/sources), [Magic Articles](/en/knowledge/magic-articles), [Review](/en/knowledge/review) |
| 4. Rebuild workflows | Recreate multi-step flows as intent rules. | [Rulebook](/en/automations/rulebook) |
| 5. Connect Zendesk | Authorize the integration and map bots to brands. | [Deploy → Zendesk](/en/deploy/zendesk) |
| 6. Shadow and test | Fini posts internal notes only while you compare and fix. | [Reply Rules](/en/automations/reply-behavior), [Inbox](/en/testing/inbox), [Test Suite](/en/testing/test-suite) |
| 7. Cut over | Switch one brand or channel to direct replies, then turn off the old bot there. | Bot Routing, Reply Rules |
| 8. Measure | Compare resolution before and after. | [Analytics](/en/analytics) |

<StepExplorer
  title="Click through the migration"
  steps={[
{ label: "Inventory", meta: "Phase 1, outside Fini", title: "List every bot flow", body: "Capture what each flow does, the systems it touches, how it ends, and how much traffic it handles.", points: ["Sort by monthly volume and start with the top flows", "Include flows that hand off to a human immediately, since they often encode policy"] },
{ label: "Map", meta: "Phase 2, planning", title: "Give each flow a home in Fini", body: "Each flow becomes knowledge, an intent rule, a reply rule, or nothing.", points: ["Question-answer flows become Articles", "Flows that collect details and call a system become intent rules", "Flows that only work around old bot limits are dropped"] },
{ label: "Build knowledge", meta: "Phase 3, Sources, Magic Articles, Review", title: "Turn your help center into reviewed Articles", body: "Ingest the Zendesk help center, add answers that only live in bot replies, and resolve conflicts in Review before anything goes live.", points: ["Use Suggest for Review", "Scope knowledge per brand with one agent per brand"] },
{ label: "Rebuild workflows", meta: "Phase 4, Rulebook", title: "Recreate multi-step flows as intent rules", body: "Use Generate with AI from the flow steps or a diagram, then wire every Tool node to a real Action.", points: ["Check every Check predicate against your Tag Groups and User Attributes", "Test with Suggested scenarios and Test Run before publishing"] },
{ label: "Connect Zendesk", meta: "Phase 5, Deploy", title: "Authorize and map bots to brands", body: "Map the Fini agent to the one brand you want to pilot first in Bot Routing. An empty Select brands means every brand on that channel.", points: ["Consider a response delay in Reply Settings"] },
{ label: "Shadow and test", meta: "Phase 6, Reply Rules, Inbox, Test Suite", title: "Internal notes only, while you compare", body: "An Internal Comment rule makes Fini post the reply it would have sent as a customer-invisible note.", points: ["Add a No Reply rule for human-owned tickets", "Tools in published intent rules still run, so point write Actions at a sandbox", "Compare answers in Inbox and fix with Refine with AI", "Turn pilot conversations into Test Suite test cases"] },
{ label: "Cut over", meta: "Phase 7, Bot Routing, Reply Rules", title: "Switch one brand or channel at a time", body: "Turn off the old bot for the slice, switch Fini to direct replies there, send test tickets, and watch the first days closely.", points: ["Only one bot replies on any slice", "Move chat first, then email"] },
{ label: "Measure", meta: "Phase 8, Analytics", title: "Compare resolution before and after", body: "Use AI resolution rate rather than deflection on the same brand and channel, with equal windows on either side." }
]}
/>

## Step 1: Inventory your existing flows

Before you build anything, write down what your current bot does. For each flow, capture:

| Field | Example |
| - | - |
| Flow name | "Where is my order" |
| Trigger | Customer selects "Order status" or mentions an order number |
| Channel and brand | Messaging, brand "Acme US" |
| What it does | Asks for order number, looks up status, replies with tracking link |
| Systems it touches | Order management API |
| How it ends | Answer given, or handoff to the "Orders" group |
| Monthly volume | 4,200 conversations |
| Known problems | Fails when the customer has two orders |

Sort the list by volume. The top handful of flows usually carry most of the traffic, and they are where you should start.

<Tip>
  Include the flows that hand off to a human immediately. Those handoffs often encode policy (for example, "always route chargebacks to the disputes team") that you need to preserve in Fini.
</Tip>

## Step 2: Map each flow to its home in Fini

Fini splits what a bot "flow" usually bundles together into separate layers. Map each row of your inventory to one of them.

| What the old flow does | Where it lives in Fini | Why |
| - | - | - |
| Answers a question from help center content | **Articles**, via [Sources](/en/knowledge/sources) and [Review](/en/knowledge/review) | The agent answers from the article directly. No flow needed, and customers can ask in their own words. |
| Shows a menu of topics or buttons | Usually nothing | The agent understands free-text intent, so a menu is rarely needed. Keep only the topics that need a workflow. |
| Collects details, then calls a system (order lookup, refund, address change) | An **intent rule** with `Read`, `Form`, and `Tool` nodes | Deterministic behavior trees run the same steps every time, and each step is recorded in the [AI Steps trace](/en/automations/rulebook#observability-the-ai-steps-trace). |
| Routes certain topics straight to a human | An intent rule that escalates, an article with **Escalation** set to **Yes**, or [Reply Rules](/en/automations/reply-behavior) | Pick the layer that matches the policy: always for a topic, only after checks, or never engage at all. |
| Stays out of tickets a human already owns | A **No Reply** rule on **Human Agent Assigned** | One rule covers every channel. See the [example rules](/en/automations/reply-behavior#example-rules). |
| Tags tickets for reporting or routing | [Tags](/en/configuration/tags) plus **Fini tags → Zendesk tags** | Fini's output tags can be mapped to Zendesk ticket tags so your existing views and triggers keep working. |
| Writes a fixed greeting or sign-off | [Prompts](/en/configuration/prompts) | Tone and formatting rules apply across every reply. |

If a flow exists only to work around a limitation of the old bot, drop it rather than rebuilding it.

```mermaid theme={null}
---
title: Where each old flow type goes in Fini
---
flowchart LR
    FLOW(("Old Zendesk<br/>bot flow"))
    Q1["Answers a question"]
    Q2["Menu of topics"]
    Q3["Collects details,<br/>calls a system"]
    Q4["Routes a topic<br/>to a human"]
    Q5["Stays out of<br/>human-owned tickets"]
    Q6["Tags tickets"]
    Q7["Greeting or sign-off"]
    KN["Knowledge<br/>Articles via Sources and Review"]
    NONE["Usually nothing"]
    IR["Rulebook<br/>intent rule"]
    ESCL["Escalating intent rule,<br/>article Escalation, or Reply Rules"]
    RR["Reply Rules<br/>No Reply on Human Agent Assigned"]
    TAG["Tags plus<br/>Fini tags to Zendesk tags"]
    PR["Prompts"]

    FLOW --> Q1 & Q2 & Q3 & Q4 & Q5 & Q6 & Q7
    Q1 --> KN
    Q2 --> NONE
    Q3 --> IR
    Q4 --> ESCL
    Q5 --> RR
    Q6 --> TAG
    Q7 --> PR

    classDef source fill:#F7F7F7,color:#131415,stroke:#E8E8E8
    classDef agent fill:#131415,color:#FFFFFF,stroke:#131415,stroke-width:3px
    classDef surface fill:#FFFFFF,color:#131415,stroke:#131415
    classDef human fill:#C3EE5E,color:#131415,stroke:#131415,stroke-width:2px

    class FLOW agent
    class Q1,Q2,Q3,Q4,Q5,Q6,Q7 source
    class KN,IR human
    class ESCL,RR,TAG,PR surface
    class NONE source
```

## Step 3: Build the knowledge layer

Most bot flows are really answers to questions. Move that content into Fini's knowledge model first, because every intent rule falls back to knowledge when it doesn't fire.

<Steps>
  <Step title="Ingest your Zendesk help center">
    In [Sources](/en/knowledge/sources), click **+ Add Sources** and pick **Zendesk help center**. Authorize, enter your subdomain, and select the articles to ingest. This connection is read-only and separate from the Deploy → Zendesk connection you set up later.
  </Step>

  <Step title="Add the content that lives in bot replies">
    Answers written directly into old bot steps often exist nowhere else. Paste them into [Magic Articles](/en/knowledge/magic-articles) with **Suggest for Review** selected and all three Generation Actions enabled, so Fini can create, update, or flag duplicates.
  </Step>

  <Step title="Generate and review articles">
    Run bulk generation from your Sources, then work through the drafts in [Review](/en/knowledge/review). Resolve conflicts between old bot answers and help center articles here, before anything goes live. See [How Fini handles messy or conflicting docs](/en/knowledge/messy-or-conflicting-docs).
  </Step>

  <Step title="Scope knowledge per brand">
    If different Zendesk brands need different answers, create one Fini agent per brand and assign folders to each in [Articles](/en/knowledge/articles#assigning-knowledge-to-agents). Don't duplicate articles to scope them.
  </Step>
</Steps>

## Step 4: Rebuild workflows as intent rules

For each flow you mapped to an intent rule, open **Rulebook → Intent Rules** and click **Generate with AI**. Paste the flow's steps from your inventory into **Behavior Instructions**, or upload a screenshot or export of the flow diagram (PNG, JPG, JPEG, or PDF up to 10MB) under **Upload Workflow Diagram**. Fini drafts a behavior tree you then refine.

After generation:

1. Wire each `Tool` node to a real [Action](/en/api-reference/actions) that calls your system.
2. Check every `Check` predicate against your actual [Tag Groups](/en/configuration/tags) and [User Attributes](/en/api-reference/attributes).
3. Write a tight **Description** with example customer phrasings. The planner uses it to select the rule.
4. Assign the rule to the right agents under **Assigned Bots**.
5. Test with **Suggested scenarios** and **▶ Test Run** before publishing. See [Testing a rule](/en/automations/rulebook#testing-a-rule).

The [cancellation walkthrough](/en/walkthroughs/cancellation-flow) shows a complete intent rule built end to end.

## Step 5: Connect Zendesk

Follow [Deploy → Zendesk](/en/deploy/zendesk) to authorize the integration with a Zendesk admin account. That page lists what Fini creates in your Zendesk account and covers **Bot Routing**, **Reply Settings**, and **Fini tags → Zendesk tags** in detail.

For a migration, two choices matter at this stage:

* **Bot Routing**: in **Email Bot and Brand Mapping** and **Chat Bot and Brand Mapping**, map your Fini agent to the one brand you want to pilot first. Leaving **Select brands** empty means the agent responds on every brand on that channel, which is not what you want during a staged rollout.
* **Reply Settings**: a response delay gives your team a window to pick up a ticket first while you build confidence.

## Step 6: Run in shadow mode and test

Before Fini replies to a single customer, run it in shadow mode: the agent posts an internal note with the reply it would have sent, and your team sees it alongside the old bot's behavior.

<Steps>
  <Step title="Turn on internal notes only">
    In **Rulebook → Reply Rules**, enable an **Internal Comment** rule that matches the brand or channel you are piloting (for example, `Integration Provider Equals zendesk`). Internal notes are customer-invisible. Reply Rules hold only the reply: Tools in published intent rules still run, so point write Actions at a sandbox or keep those rules unpublished until cutover. See [Reply Rules](/en/automations/reply-behavior).
  </Step>

  <Step title="Keep Fini out of human-owned tickets">
    Add a **No Reply** rule for `Human Agent Assigned Equals True` and, on Zendesk, `Escalated Conversation Equals True`. No Reply always wins over the other cards.
  </Step>

  <Step title="Compare answers in Inbox">
    Every Zendesk ticket Fini sees is mirrored in [Inbox](/en/testing/inbox), with the full **AI Steps** trace. Review Fini's drafted replies against what the old bot and your team actually did. Mark replies with thumbs up or down and leave **Feedback notes**.
  </Step>

  <Step title="Fix what's wrong">
    Use [Refine with AI](/en/testing/fix-with-ai) on wrong replies, then **Replay** the conversation to confirm the fix.
  </Step>

  <Step title="Build regression test cases">
    Turn your best and worst pilot conversations into [Test Suite](/en/testing/test-suite) test cases: use the flask icon in Inbox for one conversation, or a criteria group's conversation picker to add several at once. Put them in a collection for this slice and use **Run collection** after every change during the migration. You can select rule and article drafts in the run dialog to test them before publishing. Test Suite uses recorded or mock Action responses, so verify rebuilt write Actions end to end against a sandbox.
  </Step>
</Steps>

<Warning>
  Avoid having both the old bot and Fini post customer-facing replies on the same tickets. Customers get two answers, and your metrics stop meaning anything. Keep Fini on **Internal Comment** wherever the old bot is still active, or split traffic so each brand or channel has exactly one bot replying.
</Warning>

## Step 7: Cut over brand by brand

When the pilot slice looks right in Inbox and its Test Suite collection passes, cut it over.

<ChecklistMeter
  title="Cutover readiness for one slice"
  items={[
{ label: "Knowledge for this brand is reviewed and published", detail: "Conflicts between old bot answers and help center articles resolved in Review.", required: true },
{ label: "Intent rules for this slice are published and tested", detail: "Tool nodes wired to real Actions, Suggested scenarios and Test Run passed.", required: true },
{ label: "Bot Routing maps the agent to this brand only", detail: "Select brands is not empty during a staged rollout.", required: true },
{ label: "A No Reply rule covers human-owned tickets", detail: "Human Agent Assigned, and Escalated Conversation on Zendesk.", required: true },
{ label: "Shadow-mode replies look right in Inbox", detail: "Reviewed with thumbs up or down and Feedback notes.", required: true },
{ label: "The pilot collection passes in Test Suite", detail: "Every required criterion passes, with no execution errors.", required: true },
{ label: "The old bot can be turned off for this slice", detail: "So only one bot replies there.", required: true },
{ label: "A response delay is set in Reply Settings", detail: "Optional: gives your team a window to pick up tickets first." },
{ label: "Someone is assigned to watch Inbox for the first days", detail: "Fini Touched set to Yes, Feedback set to thumbs down." }
]}
  results={{
complete: "This slice is ready. Turn off the old bot there, switch Fini to direct replies, and send test tickets.",
partial: "The required items are done. The rest make the first days smoother.",
missingRequired: "Keep this slice in shadow mode until every required item is done."
}}
/>

1. **Turn off the old bot for that slice** in your Zendesk admin settings, so only one bot can reply there.
2. **Switch Fini to direct replies for that slice** by disabling or narrowing the **Internal Comment** rule. A **Direct Reply** fires when no higher-priority card matches.
3. **Send test tickets** to the brand and confirm Fini replies after the configured delay, as described in [Verify it's working](/en/deploy/zendesk#verify-it’s-working).
4. **Watch the first days closely** in Inbox with **Fini Touched** set to *Yes* and **Feedback** set to thumbs down.
5. **Expand** by adding the next brand under **Select brands**, or by mapping additional agents with **+ Add Bot**, and repeat.

Once every brand and channel is on Fini, turn off the old bot entirely and review any Zendesk triggers, views, or reports that depended on it. Where they relied on tags, map the equivalent Fini output tags with **Fini tags → Zendesk tags**.

<Tip>
  Cut over email and chat separately. **Bot Routing** maps agents to brands per channel, so you can move chat first, where feedback is fastest, and email after.
</Tip>

## Step 8: Measure the result

Compare your old bot's results against Fini on the same brand and channel using **AI resolution rate** rather than deflection, since deflection counts conversations still waiting on the customer. [Measure your resolution rate](/en/how-to/measure-resolution-rate) explains how to set up a fair before-and-after comparison.

## Related

<CardGroup cols={2}>
  <Card title="Zendesk" icon="plug" href="/en/deploy/zendesk">
    Connect Zendesk, map bots to brands, and configure reply settings.
  </Card>

  <Card title="Intent Rules" icon="diagram-project" href="/en/automations/rulebook">
    Build deterministic workflows as behavior trees.
  </Card>

  <Card title="Reply Rules" icon="turn-down-right" href="/en/automations/reply-behavior">
    Run in shadow mode and control when the agent replies.
  </Card>

  <Card title="Best practices for testing" icon="list-check" href="/en/testing/best-practices">
    A testing playbook for before and after cutover.
  </Card>
</CardGroup>


## Related topics

- [Migrating from a legacy chatbot or IVR to an AI agent](/en/playbooks/legacy-bot-migration.md)
- [Changelog](/en/changelog.md)
- [Zendesk](/en/deploy/zendesk.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.