---
title: "Define a Decision"
description: "Update the contact form's destination criteria to distinguish stopping a future renewal from returning an existing payment. Preserve the account-access rule and add a cancellation sample."
canonical_url: "https://vercel.com/academy/make-decisions-with-jev/customize-the-routes"
md_url: "https://vercel.com/academy/make-decisions-with-jev/customize-the-routes.md"
docset_id: "vercel-academy"
doc_version: "1.0"
last_updated: "2026-09-23T22:41:10.511Z"
content_type: "lesson"
course: "make-decisions-with-jev"
course_title: "Make Decisions with Jev"
prerequisites:  []
---

<agent-instructions>
Vercel Academy — structured learning, not reference docs.
Lessons are sequenced.
Adapt commands to the human's actual environment (OS, package manager, shell, editor) — detect from project context or ask, don't assume.
The lesson shows one path; if the human's project diverges, adapt concepts to their setup.
Preserve the learning goal over literal steps.
Quizzes are pedagogical — engage, don't spoil.
Quiz answers are included for your reference.
</agent-instructions>

# Define a Decision

# Who handles “please cancel”?

Alex wants to stop next month's renewal and keep the rest of the month they've paid for. Should billing handle that, or should technical support change an account setting? We need to tell Jev which team is responsible.

In the repository you created on Vercel, open `lib/examples.ts` and find `examples.contact.destinations`, the list of teams the contact form can choose. The criteria for **Billing / Invoices** currently say:

```ts title="lib/examples.ts: original billing_invoices criterion"
criteria:
  "Invoice explanations, billing details, tax information, charges, or corrections, without a request to return money.",
```

“Corrections” leaves plenty of room for interpretation. Before we change the wording, let's decide which requests billing should receive.

## Outcome

Write an explicit cancellation policy and test the same request before and after applying it.

## Hands-on exercise

### Open your copy locally

To edit the app, we'll **clone** our GitHub repository: download a local copy of its code. Install [Git](https://git-scm.com/downloads/), [Node.js 22 or later](https://nodejs.org/en/download), and [pnpm 10](https://pnpm.io/10.x/installation) if they're missing. Node runs JavaScript on your computer; pnpm installs the project's dependencies and runs its commands.

In your GitHub repository, open **Code** and copy the HTTPS URL. Open a terminal, such as Terminal on macOS or PowerShell on Windows, in the folder where you keep your projects. Run these commands, replacing `YOUR_REPOSITORY_URL` with the URL you copied:

```sh
git clone YOUR_REPOSITORY_URL jev-form-router
cd jev-form-router
```

The first command downloads the code into a folder called `jev-form-router`. The second moves the terminal into that folder. Open the same folder in your preferred code editor.

Run the following commands one at a time in that terminal, from the project folder containing `package.json`:

```sh
pnpm install --frozen-lockfile
pnpm dlx vercel link
pnpm dlx vercel env pull .env.local
```

`pnpm install` installs the dependencies using the versions in the lockfile. `pnpm dlx vercel` runs Vercel's command-line tool. Sign in when prompted, then link to the same team and **existing project** you deployed in Meet Jev.

The final command writes local settings to `.env.local`, including an OIDC token stored as `VERCEL_OIDC_TOKEN`. This identifies the project so the app can authenticate with AI Gateway while running on your computer.

Start the local app:

```sh
pnpm dev
```

Leave this command running; it serves the app while we work. Open `http://localhost:3000/contact`, using the terminal's port if it differs. Use this local page for the remaining exercises. Saving a file updates the local app; the deployed site still has the original code.

Move `routing-notes.md` into the project folder. When we run checks later, open a second terminal tab or window in this same folder so the app can keep running in the first one.

### Try the cancellation request

Before editing the rules, load **Overlapping needs** on your local `/contact` page to fill in Alex's name and email. Change **Subject** to `Cancel next renewal` and **Account context** to `Pro plan, workspace Daybreak`. Replace **Your message** with:

```text
Please cancel next month's renewal. Keep my access until the current paid month ends. I am not asking for money back.
```

We want **Billing / Invoices** to handle this request. Click **Route submission** and save the returned team and **Selected by** in `routing-notes.md`. This is our **baseline**, the result before changing the rules. We'll repeat the same request after the edit.

### Decide which team should handle each request

Pick a team for each request before reading the explanation below. Our choices are **Billing / Invoices**, **Billing / Refunds**, **Support / Account access**, and **Contact / Triage**. Triage handles requests that need clarification before we can assign them to a specialist.

| Request                                                                             | What needs to happen first?        |
| ----------------------------------------------------------------------------------- | ---------------------------------- |
| “Stop next month's renewal. This month's payment is fine.”                          | Prevent a future charge.           |
| “Cancel my renewal and return yesterday's payment. Please handle the refund first.” | Return a collected payment.        |
| “I want to cancel, but first help me sign in. I lost my authentication device.”     | Recover account access.            |
| “Please cancel it.”                                                                 | Establish what the customer means. |

Our team assigns future renewal changes to Billing / Invoices. Billing / Refunds handles requests to return money. An explicit request to recover access first goes to Support / Account access. When we cannot tell what the customer wants to cancel, Contact / Triage handles clarification.

Those are business rules. The model needs them because the phrase “please cancel” doesn't contain our team's org chart.

### Put the policy in the right fields

Make these edits inside `examples.contact`:

1. Extend `billing_invoices.criteria` to include cancellation of future renewals and turning off auto-renew while keeping paid access. Retain the restriction on returning money.
2. Update `support_technical.criteria` so “turn off auto-renew” goes to billing. Otherwise its existing configuration responsibility overlaps with our new rule.
3. Update `instructions` to resolve mixed requests using the immediate blocker or explicitly requested resolution. Preserve the access-first rule and send unclear cancellations to triage.
4. Append **Cancel next renewal** to `samples`, using the form values above. The existing `sampleIdentity` object supplies Alex's name and email.

Write the criteria, then use them to assign the four requests again. Check that each request has a clear team. The **Solution** section below includes the complete replacements if you want to compare your edits.

Keep the existing destination IDs. The app uses those IDs to look up a team, label, and recipient. Changing a criterion expands an existing team's responsibility without adding another destination.

### Keep the answer out of the evidence

Store the expected team in `routing-notes.md`. The sample should contain only the customer's form fields, so Jev has to choose a team from the request and our criteria.

Find the mapping from `example.destinations` to `criteria` in `lib/router.ts`. Both Jev and the fallback receive that question. Editing the destination definitions updates the policy supplied to either model.

## Try It

Save `lib/examples.ts`, load **Cancel next renewal** on your local `/contact` page, and compare every field with your baseline. Submit it and record the result alongside the original, including **Selected by**.

If both runs choose invoices, we've removed an ambiguity without changing this particular answer. If the team changes, read the revised criterion beside the request and explain whether the change follows our rule. A different deciding model can also account for a different result.

In your second terminal, run the app's local checks:

```sh
pnpm validate
```

`validate` runs the checks defined in `package.json`: formatting and lint rules, TypeScript, unused code, and tests. The form-validation test checks every sample, including our new one.

The tests use **mocks**, replacements for the models that return fixed answers without making network calls. They can catch a malformed sample. Submitting the form checks how the models apply our new wording.

### If something goes wrong

**Routing worked on Vercel but fails locally:** the local authentication token expires after 12 hours. In the terminal running the app, press **Ctrl+C** to stop it. Run `pnpm dlx vercel env pull .env.local`, then `pnpm dev` to restart it with a fresh token.

**The new sample doesn't appear:** save `lib/examples.ts` and check that the browser URL starts with `localhost`. The Vercel deployment still runs the original version.

**Validation reports formatting errors:** run `pnpm fix` in the second terminal, then `pnpm validate` again. The formatter also checks Markdown files such as `routing-notes.md`.

**The sample appears on another page:** the file also defines issue and lead forms. Put the entry in `examples.contact.samples`, alongside **Limited context**.

**Refunds now go to invoices:** check whether your billing criterion still excludes returning money. “All cancellation requests” is too broad for the routing rule we chose.

## Checkpoint

Save `lib/examples.ts` and the before-and-after observations. Add a sentence explaining why cancellation and refund belong to different destinations.

## Done-When

- [ ] The criteria assign future renewals to billing without absorbing refunds.
- [ ] The instructions resolve access-first and unclear requests.
- [ ] The new sample reproduces the baseline's form values.
- [ ] Validation passes and your notes contain the revised live result.

## Solution

Replace the existing `billing_invoices` object with this complete entry:

```ts title="lib/examples.ts: billing_invoices"
{
  criteria:
    "Invoice explanations, billing details, tax information, charges, corrections, or cancellation of future renewals without a request to return money. This includes turning off auto-renew while keeping access for the current paid period.",
  id: "billing_invoices",
  specialty: "Invoices",
  team: "Billing",
},
```

The ID stays the same, so the app can still look up the team. Replace `support_technical` with:

```ts title="lib/examples.ts: support_technical"
{
  criteria:
    "A customer needs help with a malfunction, integration error, configuration, or product usage; account recovery is not the primary issue. Requests to cancel a future renewal or turn off auto-renew belong to billing_invoices.",
  id: "support_technical",
  specialty: "Technical help",
  team: "Support",
},
```

The contact-level instructions resolve mixed requests. Replace only `examples.contact.instructions`:

```ts title="lib/examples.ts: contact instructions"
instructions:
  "Choose the team that can resolve the main request. Distinguish a request to return money from a request to explain or correct an invoice. Route cancellation of future renewals without a refund request to billing_invoices. A request to return an already collected payment belongs to billing_refunds. For mixed topics, select the owner of the immediate blocker or explicitly requested resolution. If the customer explicitly asks to recover account access first, select support_access. A billing mention alone does not make a message a billing request. Use contact_triage if no primary need can be established, including cancellation requests that do not identify what should be canceled.",
```

Finally, append this entry to `examples.contact.samples`:

```ts title="lib/examples.ts: cancellation sample"
{
  label: "Cancel next renewal",
  values: {
    ...sampleIdentity,
    accountContext: "Pro plan, workspace Daybreak",
    message:
      "Please cancel next month's renewal. Keep my access until the current paid month ends. I am not asking for money back.",
    subject: "Cancel next renewal",
  },
},
```

Both models receive the updated criteria because `lib/router.ts` builds their shared routing question from these destination definitions.


---

[Full course index](/academy/llms.txt) · [Sitemap](/academy/sitemap.md)
