URL: /capabilities/keep-and-grow/referral-ask · Id: `page:/capabilities/keep-and-grow/referral-ask`

# Referral ask

> Each week, your five happiest customers asked for one introduction each, by a note naming the outcome they got from you.

Each week, your five happiest [customers](https://startupbuilder.dev/business/nouns/customer) asked for one introduction each, by a note naming the outcome they got from you.

```text title="Run it · Ask"
Ask my five happiest customers for an introduction, spend up to 25¢, and show me the plan first.
```

```ts title="Run it · SDK"
import { $ } from 'startup-builder'

const result = await $.try.Referral.ask({ goal: 'Ask my five happiest customers for an introduction, spend up to 25¢, and show me the plan first.', budget: { amount: 0.25, currency: 'USD' } })
```

```json title="Run it · Response"
{
  "mode": "try",
  "stored": false,
  "estimate": "About 2.4¢ per 5 asks",
  "sample": {
    "customer": "Northgate Rentals",
    "outcome": "31 renewals tracked, 0 missed",
    "note": "You have not missed a renewal since March. Know another manager still on a spreadsheet?",
    "referral": "one, day 6"
  }
}
```

- **Kind:** Agent task
- **Price:** 2.4¢ per 5 asks
- **Your time:** 10 minutes
- **Approval:** Runs on its own
- **Deepens:** [`process:retention`](https://startupbuilder.dev/workflows/processes/retention)

## What you get
- **5 asks** · Each week, your five happiest customers asked for one introduction each, by a note naming the outcome they got from you. · [`noun:referral`](https://startupbuilder.dev/business/nouns/referral)
- `asked[]` · Each customer asked, the outcome the note names, and the note.
- `referrals[]` · Introductions that came back, each a Referral with its Acquisition.
- `skipped` · customers passed over and why, such as an open support issue.

**How long:** Seconds to send; replies over the week.

## Trigger
It runs weekly, and when a customer publishes a review.

```ts
every.week(($) => $.Referral.ask({ take: 5 }))
```

## Steps
Your agent does these steps, one row per step; each step’s check is part of its row and its price.

| Step | Verb | Returns | Price | Approval |
| --- | --- | --- | --- | --- |
| 1. Rank customers by outcomes and replies | [`Metric.query`](/code/apps/analytics-and-experiments/query) | Customers past their first outcome, by outcomes delivered and the tone of recent replies | Free | Runs on its own |
| 2. Choose whom to ask | `Collection.query` | Five customers, each with the reason they were picked | About 0.2¢ | Runs on its own |
| 3. Write each ask | `Content.draft` | A two-sentence note naming the outcome they got and asking for one introduction | About 2¢ | Runs on its own |
| 4. Send them | [`Message.schedule`](/code/apps/mailboxes/schedule) | Each ask sent; a reply with a name becomes a Referral | About 0.25¢ | Runs on its own |

Calls: `Metric.query`, then `Collection.query`, then `Content.draft`, then `Message.schedule`.

About 2.4¢ per 5 asks, each time it runs, capped at 10¢.

## Example: give it a goal
> Ask my five happiest customers for an introduction, spend up to 25¢, and show me the plan first.

The plan it shows runs 4 steps, priced before it spends anything; Run it above shows the result it returns.

## When something fails

| When | Then |
| --- | --- |
| A customer has an open issue | They are never asked that week; the next customer by outcomes takes the slot. |
| An ask fails the grade | It is rewritten once; the redo is free. |
| You call it twice in one quarter for one customer | One ask per customer per quarter: it is idempotent. |

Each step retries three times, then falls through to the next route; one item's failure lands in `skipped` with its reason, never failing the call. Before a step would cross `maxCost` it stops and returns what it has, with a `resume` link. Each step's idempotency key is the call's key plus the step and the item: `leaseline-referral-ask-1-<step>-<item>`.

## How it is graded
A second model checks each ask against the customer’s record and your referral Policy. It passes when the outcome named is theirs and true, the reward matches the Policy, and the note asks for exactly one introduction. A miss is redone at no charge.

## Change it
| Ask | Compiles to |
| --- | --- |
| Offer a month free for each introduction that becomes a customer. | [`Referral.ask`](/capabilities/keep-and-grow/referral-ask) |
| Ask after each review they publish. | [`Referral.ask`](/capabilities/keep-and-grow/referral-ask) |
| Write to each person they introduce within an hour. | [`Prospect.reach`](/capabilities/reach-out/personal-first-touch) |

```ts
import { $, workflow } from 'startup-builder'

export default workflow({
  name: 'referral-ask',
  verb: 'Referral.ask',
  input: { take: 'integer', offer: 'string?' },
  trigger: { schedule: 'weekly' },
  maxCost: 0.1,
  grade: { evaluator: 'the Customer’s record; the referral Policy', passWhen: 'their true outcome; the Policy’s reward; one introduction asked', remedy: 'redo' },
  execute: async (ctx, { take = 5, offer }) => {
    const ranked = await ctx.step('rank', () => $.Metric.query({ metrics: ['outcomes-delivered', 'reply-sentiment'], by: 'customer', where: 'firstOutcome = true and openIssues = 0' }))
    const notes = await ctx.step('write', () => $.Content.draft({ kind: 'referral-ask', customers: ranked.slice(0, take), offer, maxCost: 0.03 }))
    for (const n of notes) await ctx.step(`send:${n.customer}`, () => $.Message.schedule({ to: n.customer, body: n.body }))
    return { asked: notes }
  },
})
```

Extend it with `extends: 'Referral.ask'` and `base()`, or [compose your own](/code/compose) from these steps.

## Works with
**Plays that order it:** [Commission partners](/plays/commission-partners), [customer referrals](/plays/customer-referrals), [Made-with line](/plays/made-with).

It serves [Keep and grow customers](/capabilities/keep-and-grow), and runs in [Hands-on](/strategy/hands-on), [Keep and grow](/strategy/keep-and-grow). It deepens [`process:retention`](https://startupbuilder.dev/workflows/processes/retention).

## For agents

| Fact | Value |
| --- | --- |
| Guide | `Referral.ask` |
| Guide approval | `none` |
| Goal | Ask my five happiest customers for an introduction, spend up to 25¢, and show me the plan first. It may change counts, the order of steps, soft filters and the route a read takes, and it stops early when the goal is met. It never relaxes your words, raises its own budget, answers its own approval, calls a Play or acts outside your standing approval. |
| Trigger | `every.week(($) => $.Referral.ask({ take: 5 }))` |
| Retry | Three times with backoff, then the next route; the same idempotency key never charges, sends or posts twice |
| Idempotency key | `leaseline-referral-ask-1` |
| Lookups | A `find:` input asks unless exactly one record fits |

| Step | Action class | Seat rule that would hold it |
| --- | --- | --- |
| 1. Rank customers by outcomes and replies | None | None. |
| 2. Choose whom to ask | None | None. |
| 3. Write each ask | None | None. |
| 4. Send them | None | None. |

The amounts live once, on the hub ([approvals](https://startupbuilder.dev/agents/approvals)).

| Step | Function | Doer | Checker | Assurance | Inputs |
| --- | --- | --- | --- | --- | --- |
| 1. Rank customers by outcomes and replies | `code` | Code | Code checks each result against its declared type | `measured` | `$0.minOutcome` |
| 2. Choose whom to ask | `agentic` | The [CSM](/capabilities/agents/csm) agent | A second model grades the output | `graded` | `$1, $0.take` |
| 3. Write each ask | `agentic` | The [CSM](/capabilities/agents/csm) agent | Adversarial review by a second model: Pass, or held: the outcome is theirs and true, and the reward matches the Policy | `graded` | `$2, $0.offer` |
| 4. Send them | `code` | Code | Code reads the result back from where it landed | `measured` | `$3.passed` |

The doer is never the checker, and the weakest assurance on the path is the whole call’s.

## Under the hood

| Kind | What it touches |
| --- | --- |
| Systems | [Analytics and experiments](/code/apps/analytics-and-experiments), [Mailboxes](/code/apps/mailboxes), [sites and content](/code/apps/sites-and-content) |
| Nouns | [`noun:metric`](https://startupbuilder.dev/business/nouns/metric), [`noun:collection`](https://startupbuilder.dev/business/nouns/collection), [`noun:content`](https://startupbuilder.dev/business/nouns/content), [`noun:action`](https://startupbuilder.dev/business/nouns/action), [`noun:message`](https://startupbuilder.dev/business/nouns/message) |
| Events | `Metric.queried`, `Collection.queried`, `Content.drafted`, `Action.evaluated`, `Message.scheduled` |

## Reference

| Name | Type | Default | Meaning |
| --- | --- | --- | --- |
| `take` | `integer` | `5` | Customers asked a week. |
| `minOutcome` | `string` | `their first outcome delivered` | Only customers past it are asked. |
| `offer` | `string` | Required | What the referrer gets, from your referral Policy, if anything. |
| `maxCost` | `number` | `0.10` | The most this call spends each time it runs, in USD. It stops before a step would cross it and returns what it has, with a `resume` link. |

Emits `Referral.asked`. Refuses with [`max-cost`](/code/refusals#max-cost) before it spends, and with the Data calls and Accounts groups ([refusals](/code/refusals)).