URL: /plays/outbound-calling · Id: `play:outbound-calling`

# Outbound calling

> A disclosed voice agent calls back anyone who asks within 60 seconds and answers your number; a person dials every cold call.

A disclosed voice agent calls back anyone who asks within 60 seconds and answers your number; a person dials every cold call.

```text title="Set it up · Ask"
Be our SDR on the phone: when someone ticks “call me” on the demo form, call back within a minute in a disclosed AI voice, and answer our number at any hour. Over 120 units, book our AE. I dial every cold call myself from your prep. Show me what’s missing; don’t call.
```

```text title="Run one · Ask"
Hand our latest callback request to the SDR agent and show me what it chose and why. Don’t call.
```

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

const { events: [last] } = await $.Event.list({ where: 'name in ("Consent.granted", "Channel.called", "Channel.missed", "Conversation.labeled", "Lead.qualified", "Prospect.created")', limit: 1 })   // { play } compiled to this Play’s Trigger Events
const { subject } = await $.Trigger.fire({ like: last.$id }, { mode: 'try' })   // the same Event again, in try mode · Not live yet, gaps #7
const { decisions: [decision] } = await $.Decision.list({ where: `on = "${subject}"` })   // what it chose, and why · Not live yet, gaps #7
const { actions } = await $.Action.list({ where: `on = "${subject}"` })   // what it did, with its checks · Not live yet, gaps #7
return { decision, actions }
```

```json title="Run one · Response · sample"
{
  "decision": {
    "$id": "https://api.sb/leaseline.app/decisions/decision_547",
    "$type": "Decision",
    "choice": "call-back-now",
    "reason": "the yes names AI-voice calls from Leaseline to the number on the form; 14:06 Central is inside their calling window; 220 units is over the 120-unit line",
    "stakes": "low",
    "options": [
      "call-back-now",
      "a-person-calls",
      "answer-the-call",
      "cold-list",
      "answer-in-writing"
    ],
    "by": "worker_2",
    "on": "lead_47",
    "expected": "a Meeting held by day 14",
    "reopensWhen": "no Meeting.booked for lead_47 after the 4th dial, or within 14d"
  },
  "actions": [
    {
      "$id": "https://api.sb/leaseline.app/actions/action_1",
      "$type": "Action",
      "status": "done",
      "mode": "try",
      "by": "worker_2",
      "verb": "Contact.enrich",
      "checks": []
    },
    {
      "$id": "https://api.sb/leaseline.app/actions/action_2",
      "$type": "Action",
      "status": "done",
      "mode": "try",
      "by": "worker_2",
      "verb": "Lead.qualify",
      "checks": []
    },
    {
      "$id": "https://api.sb/leaseline.app/actions/action_3",
      "$type": "Action",
      "status": "pending",
      "reason": "awaitingTime",
      "mode": "try",
      "by": "worker_2",
      "verb": "Conversation.create",
      "checks": [
        {
          "check": "owned-by-a-person",
          "status": "pass"
        },
        {
          "check": "voice-consent",
          "status": "pass"
        },
        {
          "check": "do-not-call",
          "status": "pass"
        },
        {
          "check": "quiet-hours",
          "status": "pass"
        }
      ]
    },
    {
      "$id": "https://api.sb/leaseline.app/actions/action_4",
      "$type": "Action",
      "status": "pending",
      "reason": "awaitingReply",
      "mode": "try",
      "by": "worker_2",
      "verb": "Meeting.book",
      "checks": []
    }
  ]
}
```

`action_3` is the first dial, 41 seconds after the form, opening with the fixed clip. `action_4` books on the AE’s calendar only if the caller says yes on the call.

`+11.7 per 100 · ■□□□ Too early to tell · n 46 · path → arr`

| Try another: the callback | Choice | Reason |
| --- | --- | --- |
| Willow & Main: ticked “call me”, 220 units, 14:06 their time | `call-back-now` | a yes to AI-voice calls, inside their calling window |
| Birchway Properties: ticked “call me”, 600 units, on your top-25 List | `a-person-calls` | a top-tier company gets a person on every rung |
| A call to +1 512 555 0100 at 21:40: “Do you handle HOA renewals?” | `answer-the-call` | an inbound call, answered at any hour from your own pages |
| Ridgeline Rentals: a listed office number, and a leasing job reposted 64 days | `cold-list` | a local company with a live reason: on Dana’s list to dial |
| Kestrel Rentals: “Can someone call me?” by email, no box ticked | `answer-in-writing` | no yes to an AI-voice call: it answers by email and offers a person |

| Worth | Your time | Spend | First signal | Use it when | Instead |
| --- | --- | --- | --- | --- | --- |
| ≈ +$850 ARR a month at 47 | 2 h week 1, then 1 h 40 min a week | $1 per callback; $100 a month for up to 100 | Day 2: first Meeting booked on a callback | 20+ callback asks a month, or 30+ calls to your number | Under 20 a month: `instant-response` · no callback box: add one (<Op verb="Form.update" />) |

**Where it sits:** Stranger → **Lead** → Trialing → Activated → At risk → Churned → Won back · Callback Meeting rate → Deals and trials opened → New Customers ↑ → ARR added each week → ARR

Worth’s formula: about +7.5 Meetings per 100 callbacks (Other Startups’ results; your own read is too early) × 47 a month × 0.80 Meetings to opens × 0.30 opens to paid × $800 first order ARR ≈ +$846. Under First signal: With 1 callback in 10 held back at 47 a month, about 30 weeks to tell a +7.5 lift; 90 days can tell only +22 or more.

`sample · api.sb/leaseline.app · 41 Customers at 2026-10-19 · on Consent.granted · first ring 41s after the form · 10 keys Not live yet`

## Start it this week
```bash
npx startup-builder add play outbound-calling   # 1 · writes plays/outbound-calling.mdx (Not live yet, gaps #5)

# 2 · dry run; below, its 422 on the day-0 Startup (--env test, clock at 2026-08-24)
curl -X POST "https://api.sb/leaseline.app/campaigns?mode=try" \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"play":"outbound-calling"}'

# 3 · keep it: people make the first 50 calls from the brief
curl -X POST https://api.sb/leaseline.app/campaigns \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"play":"outbound-calling"}'

# on
curl -X POST https://api.sb/leaseline.app/processes/call-routing/enable \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"

# off: every call goes to the person line
curl -X POST https://api.sb/leaseline.app/processes/call-routing/pause \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"
```


```json title="The dry run’s 422 · 2026-08-24 · sample"
{
  "type": "https://startupbuilder.dev/errors/refused",
  "title": "Refused",
  "status": 422,
  "detail": "outbound-calling can’t start yet: 1 check fails and 2 warn.",
  "checks": [
    {
      "check": "use-when",
      "status": "pass",
      "observed": "135 forms a month; 0 offer a callback yet",
      "doc": "/plays/outbound-calling#check-use-when",
      "fix": "Add the callback box; about 1 in 3 forms tick it at Other Startups.",
      "then": "Metric.query"
    },
    {
      "check": "baseline",
      "status": "warn",
      "observed": "callbacks by a person: 9 Meetings from 41, n 41 in 90 days",
      "doc": "/plays/outbound-calling#check-baseline",
      "fix": "n 41: your half becomes the baseline as it runs.",
      "then": "Metric.query"
    },
    {
      "check": "inbound-source",
      "status": "fail",
      "doc": "/plays/outbound-calling#check-inbound-source",
      "fix": "Connect your number and add the callback box to the demo form.",
      "then": "Integration.connect"
    },
    {
      "check": "owned-by-a-person",
      "status": "pass",
      "observed": "31 open Leads have a person owner, you",
      "doc": "/plays/outbound-calling#check-owned-by-a-person",
      "fix": "Nothing to do: their callbacks come to your queue instead.",
      "then": "Task.assign"
    },
    {
      "check": "customer-collision",
      "status": "pass",
      "observed": "6 numbers belong to Customers",
      "doc": "/plays/outbound-calling#check-customer-collision",
      "fix": "Nothing to do: a Customer’s call goes to their CSM.",
      "then": "Lead.assign"
    },
    {
      "check": "claim",
      "status": "pass",
      "observed": "the claims source holds 24 approved answers",
      "doc": "/plays/outbound-calling#check-claim",
      "fix": "Nothing to do: the agent says only what an Offer term grants.",
      "then": "Conversation.answer"
    },
    {
      "check": "role-unstaffed",
      "status": "warn",
      "roles": [
        "sales-lead"
      ],
      "owned": 31,
      "doc": "/plays/outbound-calling#check-role-unstaffed",
      "fix": "Staff the AE, or leave it: booked calls and transfers come to you.",
      "then": "Role.staff"
    }
  ]
}
```

| Check | Fix (one call) | Your time | Wait |
| --- | --- | --- | --- |
| <a id="check-use-when"></a>`use-when` · <a id="check-baseline"></a>`baseline` · pass: 135 forms a month; warn: n 41 person callbacks (gaps #8) | <Op verb="Metric.query" /> on `callback-meeting-rate`, which fills Before in Your number | 1 min | none |
| <a id="check-inbound-source"></a>`inbound-source` · fail (gaps #8) | <Op verb="Integration.connect" /> the number, then <Op verb="Form.update" /> with the callback box | 15 min | the number’s registration, often 1 to 3 days |
| <a id="check-owned-by-a-person"></a>`owned-by-a-person` · <a id="check-customer-collision"></a>`customer-collision` · <a id="check-claim"></a>`claim` · pass: 31 owned by you; 6 Customer numbers; 24 answers (gaps #1, #8) | nothing; each runs again before every dial | 0 min | none |
| <a id="check-role-unstaffed"></a>`role-unstaffed` · warn (gaps #8): 31 of your open Leads have a person owner, you | <Op verb="Role.staff" />, or leave it: bookings and transfers come to you | 0 min | none |

`Your number · Before 22.0 · Now 31.7 per 100 · Target 30 by Dec 31 · ■□□□ Too early to tell · n 46 matured · Other Startups +7.5, ■■■□ Likely`

One local number and voice minutes for about 100 callbacks cost $100 a month; each callback averages 3 minutes, about $1. Contact data costs $0.06 a lookup (<Op verb="Contact.enrich" />). A cold call costs only your time. That comes to $100 a month up to 100 callbacks.

## When it starts
| When | Trigger id | Starts on | Window | How it gets in |
| --- | --- | --- | --- | --- |
| Someone ticks “call me” on your form, naming AI-voice calls | `voice-consent-form` | `event` on `Consent.granted` where purpose = 'ai-voice-call' | first ring within 60 s; ignored after 1d | your form posts to <Op verb="Consent.create" />, then <Op verb="Lead.create" /> |
| Someone calls your number | `inbound-call` | `event` on `Channel.called` where direction = 'inbound' | answered on the first ring | <Op verb="Integration.connect" /> with your number |
| A call to you ends unanswered, or they ask to be called back | `dropped-call` | `event` on `Channel.missed` where answered = false or callbackAsked = true | 1d | the same number |
| + 3 more: a written ask for a call, a strong-fit Lead with no yes to a call, and a cold-call reason (`?lanes=all`) | | | | |

```json title="One signed delivery · StartupBuilder-Signature"
{
  "$id": "https://api.sb/leaseline.app/events/event_1366",
  "$type": "Event",
  "name": "Consent.granted",
  "at": "2026-10-19T19:06:12Z",
  "seq": 1366,
  "data": {
    "consent": {
      "$id": "https://api.sb/leaseline.app/consents/consent_201",
      "purpose": "ai-voice-call",
      "startup": "leaseline.app",
      "number": "+1 512 555 0148"
    },
    "lead": {
      "$id": "https://api.sb/leaseline.app/leads/lead_47",
      "company": "Willow & Main",
      "units": 220
    }
  }
}
```

```bash title="Reproduce it in test"
# each delivery is signed with the Watch’s secret
curl -X POST https://api.sb/leaseline.app/watches \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"selector":"Consent.granted","url":"https://leaseline.app/hooks/sb"}'
npx startup-builder trigger Consent.granted --like event_1366   # replays the delivery in test; the dial is simulated
npx startup-builder clock advance --by 1h   # runs the third dial if the first two went unanswered
```

## Who runs it
**SDR · your agent** (`role:prospector`, `worker_2`) holds the Key Result and speaks in a designed voice that sounds like no real person. It hands booked calls and transfers to **AE · Priya Shah (person)** (`role:sales-lead`, `worker_3`, staffed on day 9), and top-tier callbacks and every cold call to **you** (Dana Okafor, person, `worker_1`): a person dials every cold number. Your number links to Did it work.

| Option | Rung today | Evidence (passed of n) | Moves up at | Drops back when | You can |
| --- | --- | --- | --- | --- | --- |
| Call back now | It does it, you can stop it | 52 calls; 50 person calls from its brief first | started on day 21, after the 50; spot-check after 500 calls with no Stop in 30 days | any voice Stop (`clip`, `ai-identity`, `claims`) → You hear each transcript first | <Op verb="Action.cancel" /> before the dial · <Op verb="Conversation.escalate" /> on the call |
| Answer the call · Answer in writing | It does it, you can stop it | 38 calls · 9 of 9 | the same | the same | the same |
| A person calls back · Put it on your cold list | A person, every time | 6 held back, 5 top tier · 34 dials from 12 briefs | never: a person dials | — | <Op verb="Task.claim" /> · <Op verb="Task.return" /> |
| Change the calling rule | You approve each one | 1 merged of 1 | never | — | <Op verb="Change.merge" /> · <Op verb="Change.revert" /> |
| Never: a call with no yes to AI voice, a cloned voice, a discount | Refused, every time | 3 strong-fit Leads with no yes: written answers | never | — | none: the Check holds it |

**What it reads · What it may call.** It reads the form, the yes and its scope, the claims source and the calendar. A pricing-page visit may order the cold list and is never spoken on a call; nothing a caller says picks a number to dial. Its tools are <Op verb="Conversation.create" />, <Op verb="Meeting.book" />, <Op verb="Task.assign" />, <Op verb="Conversation.answer" />, <Op verb="Message.schedule" />, and any other call becomes an Approval under the Role’s Mandate.

```md title="The brief (folded): compiled Agent-register Markdown · play:outbound-calling@1 · each line names its record key"
You hold the SDR Key Result: 30 Meetings held per 100 callbacks by 2026-12-31.   ← worker, metric
On each callback ask or call, choose one option and say why.                     ← triggers, chooses
Play the opening clip first and in full; say you are an agent if asked.          ← copyRules 1, 3
One ask per call; the list Price only as the Pricing Page shows it.              ← copyRules 6, 9
Over 120 units, book the AE.                                                     ← your file, line 5
“Person” at any point reaches a person, live or booked.                          ← copyRules 12
```

**The slow loop, as a Change.** `decision_366` (2026-09-28, weekly) read 11 callbacks that rang once and went to voicemail, with 1 Meeting, and 9 that rang twice in a row, with 4. `change_27` made the double ring the first attempt for every callback (your file, line 6), and Dana merged it with <Op verb="Change.merge" />. **Replayed on past callbacks**, <Op verb="Change.review" /> put the agent half at 34 per 100; read back at +14d it is 33.3, n 18, ■□□□ Too early to tell (Example, day 50).

**Bring your own agent** (folded) starts at It does it, you can stop it when it matches or beats the seated agent on the eval suite, and at You approve each one when it does not; <Op verb="Role.staff" /> seats it.

```bash
npx startup-builder eval --scenario outbound-calling-58 --agent "claude -p" --seat prospector   # seated: 31.7 per 100, 0 clip misses, $1.02 a call · yours: 28.1, 0 misses → You approve each one
```

## What it can do next
| Option | Picked when | Decided by | Runs | Hands to | If it’s just you |
| --- | --- | --- | --- | --- | --- |
| Call back now `call-back-now` | a yes to AI-voice calls, inside their calling window | Your rule (line 5) | `callback-attempts`: double ring at once, then 1 h, then 1 day → <Op verb="Conversation.create" /> → on yes <Op verb="Meeting.book" /> | over 120 units: <Op verb="Task.assign" /> → AE · Priya Shah, due 60 min, Brief attached · Take this one | you, with the Brief, within 60 min |
| A person calls back `a-person-calls` | a top-tier company, a strong fit with no yes to AI voice, or held back | Your rule (line 7) | `human-callback`: a person calls from the brief within 5 min in staffed hours, twice at most → <Op verb="Task.assign" /> | you · Take this one | you |
| Answer the call `answer-the-call` | someone calls your number | Agent’s call, with its reason | `route-call` → <Op verb="Conversation.answer" /> from the claims source | a transfer: AE · Priya Shah, live, or booked within 60 min | you |
| Put it on your cold list `cold-list` | a local company with a live reason and a listed number | Agent’s call, with its reason | `cold-call-touches`: the brief and talk track → <Op verb="Task.assign" />; a person dials 3 times in 7 days | you dial · Take this one | you |
| Answer in writing `answer-in-writing` | outside the calling window, or a written ask with no yes to a call | Agent’s call, with its reason | `answer-inbound` → <Op verb="Message.schedule" />, offering a person’s call | nobody | the same |

| When they write back | Fixture reply | What happens | Who answers | By when | Call |
| --- | --- | --- | --- | --- | --- |
| Interested | “Yes, Tuesday at 3 works.” (on the call) | books it on the AE’s calendar while they listen; the Brief goes to the AE (When it hands to you) | AE · Priya Shah (person) | 60 min | <Op verb="Meeting.book" /> |
| Send me pricing | “Just email me the pricing.” | the call ends; the Pricing Page’s list Price arrives by email with one next step | SDR · your agent | 5 min | <Op verb="Message.schedule" /> |
| Not now | “Call me after the holidays.” | no more dials; it moves to Nurture with January noted | SDR · your agent | at once | <Op verb="Lead.nurture" /> |
| Wrong person | “You want our ops manager, Sam.” | it asks for Sam’s email to send the summary; it never dials a number it heard on the call | SDR · your agent | 1 business day | <Op verb="Conversation.answer" /> |
| Out of office | no answer twice; voicemail on the second ring | one fixed voicemail clip, then the 1 h and 1-day attempts | nobody | 1 day | <Op verb="Message.schedule" /> |
| Question it can’t answer | “Can you migrate our 6 years of lease history?” | a live transfer to the AE, or a booked call with the question in the Brief | AE · Priya Shah (person) | 60 min | <Op verb="Conversation.escalate" /> |
| Stop | “Don’t call this number again.” | the number goes on your do-not-call list; nothing dials it on any lane | nobody | at once | <Op verb="Contact.suppress" /> |

## When it hands to you
Booked calls and transfers go to **AE · Priya Shah (person)**; top-tier callbacks and every cold call come to **you**, and before day 9 every hand-off did. One Brief per Role, as captured (`brief` is Not live yet, gaps #7).

```json title="What lands in each queue · sample"
[
  {
    "$id": "https://api.sb/leaseline.app/tasks/task_455",
    "$type": "Task",
    "assignee": "worker_3",
    "due": "2026-10-19T20:07:00Z",
    "on": "lead_47",
    "brief": {
      "who": "Jordan, Willow & Main (220 units)",
      "said": "Renewals across 3 buildings, and owners want monthly statements.",
      "chose": "call-back-now",
      "why": "a yes to AI voice; over the 120-unit line",
      "booked": "Tue 15:00 Central",
      "call": "transcript and recording, 3 min 40 s",
      "never": [
        "a price beyond the list",
        "a discount",
        "terms"
      ]
    }
  },
  {
    "$id": "https://api.sb/leaseline.app/tasks/task_418",
    "$type": "Task",
    "assignee": "worker_1",
    "due": "2026-10-06T14:00:00Z",
    "on": "prospect_44",
    "brief": {
      "who": "Ridgeline Rentals, office line +1 512 555 0172",
      "reason": "leasing coordinator job reposted after 64 days",
      "chose": "cold-list",
      "why": "a local company with a live reason",
      "track": "name, Startup, reason in the first 15 seconds; one ask: a 20-minute call"
    }
  }
]
```

Each receiver answers with <Op verb="Task.claim" /> (Take this one), <Op verb="Task.return" /> with a reason, or <Op verb="Action.redirect" />, each a one-call window. Once a person claims, the agent dials that Lead no more and every call or reply goes to the claimer (`claimedByPerson`, Not live yet, gaps #7). A Task nobody claims by its due time goes up a Role through <Op verb="Task.escalate" /> (AE to you), and `person-promise` fires.

**On time:** AE · Priya Shah, 9 of 9 booked calls claimed within 60 min, median 12 min, n 9, ■□□□ Too early to tell beside Other Startups’ 95 % on time; you, 12 cold briefs dialed within their day, n 12. **Your queue:** counted by kind: in weeks 1 to 3, 50 person callbacks from the brief; after day 21, about 2 transfers, 3 cold briefs and 5 transcripts to spot-check a week.

**The fixed line:** <Op verb="Deal.assign" /> sets `owner` to the receiving person, the agent is never a Deal’s owner, and Meetings land on the owner’s calendar under the owner’s name.

## When it won’t send
| Check (refuses one send) | Refuses | Fix | Then |
| --- | --- | --- | --- |
| `owned-by-a-person` (rule j; Not live yet, gaps #8) | a dial to a Lead or open Deal a person owns, or a Conversation a person claimed | Leave it to the person who owns it: the draft waits in their queue. | <Op verb="Task.assign" /> |
| <a id="check-voice-consent"></a>`voice-consent` (the record’s Consent gate) | any AI-voice dial with no recorded yes naming AI-voice calls, the Startup and the number | Call back in person, or answer in writing; no override exists. | <Op verb="Task.assign" /> |
| <a id="check-do-not-call"></a>`do-not-call` (the record’s Do-not-call gate) | a number on Suppression, your list, or a Market list older than 31 days | Leave the number off; refresh the Market list if it is over 31 days old. | <Op verb="Suppression.list" /> |
| <a id="check-quiet-hours"></a>`quiet-hours` (the record’s Quiet hours gate) | a dial outside the calling window in the called party’s local time | Wait for their window; the narrower of two time zones wins. | <Op verb="Message.schedule" /> |
| `customer-collision` (gaps #1) | a new-lead callback to a number on an active Subscription | Send it to their CSM instead: the Contact leaves the List. | <Op verb="Lead.assign" /> |
| `claim` (hub, on <Op verb="Conversation.answer" />) | a term with no accepted terms Version, or money back no Action keeps | Cut the line, or cite the Offer term that grants it. | <Op verb="Conversation.answer" /> |

| Stop (pauses the Play) | Pauses when | Then |
| --- | --- | --- |
| <a id="stop-clip"></a>`clip` | one call log lacks the opening clip’s hash | AI calls pause on every number until you review it |
| <a id="stop-ai-identity"></a>`ai-identity` | one transcript says or implies the agent is a person | every voice Tactic pauses; the Marketing Lead is told |
| <a id="stop-claims"></a>`claims` | one transcript sentence states a price, policy or promise outside the claims source | callbacks drop to You hear each transcript first |
| <a id="stop-complaint"></a>`complaint` | one “I never asked for this call” opens an audit of that yes; two in 200 calls | AI callbacks pause on that form |
| <a id="stop-spam-label"></a>`spam-label` | a carrier labels the number as spam | outbound calls stop; an Issue opens; calls resume on a clean read |
| <a id="stop-early-hang-up"></a>`early-hang-up` | over 40 % of answered callbacks end within 10 s of the clip, after 50 | the Tactic pauses for review |
| <a id="stop-meeting-floor"></a>`meeting-floor` | the agent half books fewer Meetings per 100 than people, after 100 calls each | callbacks return to people, with the agent’s brief |
| <a id="stop-kill-switch"></a>`kill-switch` | you press it | <Op verb="Process.pause" /> on `call-routing`; every call goes to the person line |
| <a id="stop-cold-refusal"></a>`cold-refusal` | do-not-call asks over 5 % of cold connects, after 100; one dial to a suppressed number | that cold list pauses; a suppressed dial trips `kill-switch` |
| <a id="stop-cold-floor"></a>`cold-floor` | zero Meetings after 100 cold connects | cold calls end for that List until the Offer or the reason changes |

The captured refusal, then the Stop that fired as a `Campaign.paused` reason (Not live yet, gaps #2); the rewrite that sent is the Harbor Row follow-up in Example.

```json title="The captured refusal · sample"
{
  "type": "https://startupbuilder.dev/errors/refused",
  "title": "Refused",
  "status": 422,
  "detail": "The follow-up email to Harbor Row did not send: it promises a free month no Offer term grants.",
  "checks": [
    {
      "check": "claim",
      "status": "fail",
      "doc": "/plays/outbound-calling#check-claim",
      "fix": "Cut the line, or cite the Offer term that grants it.",
      "then": "Conversation.answer"
    },
    {
      "check": "owned-by-a-person",
      "status": "pass"
    }
  ]
}
```


```json title="The Stops that fired, as Campaign.paused reasons · sample"
[
  {
    "reason": {
      "limit": "spam-label",
      "observed": "“Spam risk” on 1 carrier",
      "bound": "0",
      "window": "the daily number read, Oct 3"
    },
    "doc": "/plays/outbound-calling#stop-spam-label",
    "task": "task_431"
  }
]
```

## Did it work
One callback in 10, drawn when the form lands, is held back at random and gets a person’s callback in staffed hours; both halves are read from that assignment (rules m–o), counting from day 21, when the agent started dialing. Each figure’s call is <Op verb="Metric.query" /> by node and by arm; the parent field and every node above the leaf are Not live yet (gaps #4).

`+11.7 per 100 · ■□□□ Too early to tell · n 46 · path → arr`

| Node | Agent half | Your half (a person calls back) | Added | Confidence | Basis |
| --- | --- | --- | --- | --- | --- |
| `callback-meeting-rate` | 31.7 per 100 (13 of 41) | 20.0 (1 of 5) | +11.7 [−25.4, +48.8] | ■□□□ Too early to tell | held back at random, 1 in 10 · n 46 · 12 still maturing · Other Startups +7.5, ■■■□ Likely, n 6,900 |
| `opened-deals-and-trials` | 26.8 per 100 (11 of 41) | 20.0 (1 of 5) | +6.8 [−29.9, +43.5] · Not carrying up yet | ■□□□ Too early to tell | the same assignment |
| `new-arr` ↑ | $3,087, 3 Customers · Booked: credited, not all caused by this Play | $347, 1 Customer | +$2,740 [−$1,600, +$7,000] · Worth about +$850 a month | ■□□□ Too early to tell (3 vs 1 Customers) | the same · 5 trials still deciding |
| `net-new-arr` · `arr` | +$3,087 | +$347 | +$2,740, carried: it moves no other bridge term | ■□□□ Too early to tell | carried from `new-arr` |

The leaf counts Meetings held within 14 days per 100 callbacks placed, wrong numbers kept in the denominator (rule m), to 100 matured calls in each half; at 41 and 5 the Experiment runs on.

**How it moves it:** a median first ring 41 seconds after the form for the agent half, against 2 h 50 min for a person’s callback (`time-to-first-call` on callbacks).

**Guardrails:** treated against held back: Deals or trials opened per Meeting held, 0.85 against 1.00 (`meeting-to-open-rate`, Not live yet), and “I never asked for this call”, 0 of 52 against a Stop at 2 in 200.

```json title="The Campaign card · sample"
{
  "$id": "https://api.sb/leaseline.app/campaigns/outbound-calling",
  "$type": "Campaign",
  "play": "play:outbound-calling@1",
  "path": [
    "callback-meeting-rate",
    "opened-deals-and-trials",
    "new-arr",
    "net-new-arr",
    "arr"
  ],
  "experiment": {
    "metric": "callback-meeting-rate",
    "unit": "per-100",
    "heldBack": 6,
    "decision": null
  },
  "readout": {
    "value": 11.7,
    "n": 46,
    "maturing": 12,
    "interval": [
      -25.4,
      48.8
    ],
    "readability": "inadmissible",
    "basis": "held-back",
    "pooled": false
  },
  "ledger": {
    "spend": 186,
    "workerCost": 6,
    "hours": 9.4,
    "pipelineArr": 4800,
    "wonArr": 3087,
    "projectedArr": 846,
    "paybackDays": 22
  }
}
```

Outside benchmark: 4 % of business demo requests got a phone call at all, after a median 34.5 hours (a vendor-run speed-to-lead field experiment, 2026-09-22).

**Before it · After it.** Before it: `paid-search`, `paid-social`, `directory-listings`, `free-tools` and `lead-nurture` end on a form, a number or a call-me reply. After it: booked calls reach the AE as `instant-response` hand-offs do, partial fits enter `lead-nurture`, and every call outcome maps to a reply kind.

## Example
Leaseline ran it from 2026-08-24 to 2026-10-19 (56 days). 88 forms ticked “call me”: people made the first 50 callbacks from the brief, then from day 21, 58 were assigned at random, 52 to the agent and 6 held back; 46 have matured. The agent also answered 38 calls to the number. Read every captured thread at `?threads=all`; three follow the tables.

| What happened, by option | Calls | Meetings held | Won (credited, not all caused by this Play) |
| --- | --- | --- | --- |
| Call back now | 52 (148 dials) | 16, 13 of the 41 matured | 1 on the $2,400 plan (+$2,393 ARR), 1 on Pro (+$347 ARR) |
| Answer the call | 38 | 4 | 1 on Pro (+$347 ARR) |
| A person calls back · Put it on your cold list · Answer in writing | 11 · 34 dials from 12 briefs · 9 | 2 · 2 · 1 | 1 on Pro (+$347 ARR) · 0 · 0 |
| **Agent half** · held back | **52** · 6 | **16** · 1 | **$3,087** · $347 |
| At 20 callback asks a month (the Use it when volume) | about 37 in 56 days | held back 1 in 10: Too early to tell at 90 days | Other Startups’ results: +7.5 per 100, ■■■□ Likely, n 6,900 |

| Day | Event | What happened | Your time |
| --- | --- | --- | --- |
| 0 | `event_118` Campaign.created | the three steps; the dry run fails `inbound-source` | 25 min |
| 2 | `event_131` Integration.connected | the number registered; the callback box went live on the demo form | 15 min |
| 2 | `event_140` Meeting.booked | the first Meeting on a person’s callback from the brief | — |
| 9 | `event_301` Role.staffed | AE · Priya Shah (person) takes `role:sales-lead` | — |
| 0–20 | `event_133` Task.claimed, first of 50 | you and Priya made the first 50 callbacks from the agent’s brief | 4 h 10 min |
| 21 | `event_455` Role.updated | the voice agent starts dialing consented callbacks | — |
| 36 · 50 | `event_655` Change.merged · `event_861` Readout.created | double ring first: replayed at 34 per 100, read back at 33.3, n 18 | 5 min |
| 40 | `event_702` Campaign.paused | `spam-label` on 1 carrier; calls resumed on a clean read 19 h later | 20 min |
| 21–56 | `event_480` Conversation.created, first of 90 | you spot-checked transcripts and dialed 34 cold calls; weekly reviews, from the <Op verb="Task.next" /> log | 5 h · **total 9.4 h measured, 9 h asked** |

> **Willow & Main, form at 14:05:31 Central** · ticked “call me”, 220 units.
>
> **Leaseline (voice agent), 14:06:12** · *The fixed opening clip, naming Leaseline, the AI voice and the recording.*¹ “You asked about renewals across your buildings. Is now still a good time?”² Jordan named 3 buildings and monthly owner statements; the agent offered the AE’s times³ and booked Tue 15:00 with <Op verb="Meeting.book" /> at 14:09:52. The Brief reached Priya at 14:10.

```diff title="Harbor Row, 2026-09-30 · the follow-up claim refused, then the rewrite that sent"
- As promised on the call, your first month is free.
+ As we said on the call, Pro is $29 a month, and the trial is free for 14 days.
```

> **A call to +1 512 555 0100, 2026-10-08 10:12** · “Can you migrate our 6 years of lease history?” · **Leaseline (voice agent)** · *Priya, our account lead, calls you back by 11:12.*⁴ The Brief went to Priya (`task_437`); she claimed it at 10:20 and called back at 10:48.

¹ The fixed opening clip, compiled from the Role’s Settings and never typed. ² It quotes the form; nothing they did not give is spoken. ³ One ask, with times the calendar shows. ⁴ The routed line, compiled from the Role’s Settings.

## Your settings
```mdx title="plays/outbound-calling.mdx"
---
holdout: 0.1      # 1 callback in 10 gets a person instead
number: +1 512 555 0100
---
Over 120 units, book our AE.
Every callback starts with the double ring.
Our top 25 get a person on every rung, and I dial every cold call.
```

```text
$ npx startup-builder show plays/outbound-calling
holdout 0.1 · number +1 512 555 0100 · size line 120 units · double ring · top 25 to a person     your file  plays/outbound-calling.mdx:2,3,5,6,7 (Ask 2026-08-24; line 6 by change_27, 2026-09-28)
family outreach · process call-routing · worker role:prospector, owns never · chooses 5 (2 rule, 3 read) · sequences callback-attempts, human-callback, cold-call-touches · approval every-send · checks owned-by-a-person, voice-consent, do-not-call, quiet-hours, customer-collision, claim · stops 10    catalog  play:outbound-calling@1 (Not live yet, gaps #5)
```

### Every key from the catalog
| Key | Id | What it is |
| --- | --- | --- |
| Tactic | <a id="tactic-consented-callback"></a>`consented-callback` | Consented callback: A disclosed voice agent calls back a Form submitter whose Consent names AI-voice calls within 60 seconds, qualifies them and books or transfers. Starts at You approve each one. |
| Tactic | <a id="tactic-inbound-answer"></a>`inbound-answer` | Inbound answer: A disclosed voice agent answers every call to a Startup number at any hour, from the claims source, and routes, books or hands off. Starts at You approve each one. |
| Tactic | <a id="tactic-human-call-prep"></a>`human-call-prep` | Human call prep: The agent builds the cold call list, the briefs and the talk tracks, and coaches after each call; a Human dials every number by hand. Starts at You approve each one. |
| Tactic | <a id="tactic-call-gate"></a>`call-gate` | Call gate: The send gate for the phone medium: Suppression, do-not-call lists, Consent for AI voice, calling windows, Market stamp and number health, before every dial. Starts at Sends nothing. |
| Tactic | <a id="tactic-send-gate-check"></a>`send-gate-check` | Send gate check: Checks Suppression, a live reason, the Market, the basis stamp, the Person-month grant, the facts and the labels before any cold Message exists. Starts at Sends nothing. |
| Tactic | <a id="tactic-reply-matrix"></a>`reply-matrix` | Reply matrix: Labels every outreach reply with one of 11 labels, maps it to one of seven reply kinds, and runs that kind’s move by its deadline. Starts at You approve each one. |
| Trigger | <a id="trigger-voice-consent-form"></a>`voice-consent-form` | Voice consent form: `event` on `Consent.granted`, window 1d. |
| Trigger | <a id="trigger-inbound-call"></a>`inbound-call` | Inbound call: `event` on `Channel.called`, window 1h. |
| Trigger | <a id="trigger-dropped-call"></a>`dropped-call` | Dropped call: `event` on `Channel.missed`, window 1d. |
| Trigger | <a id="trigger-call-me-reply"></a>`call-me-reply` | Call me reply: `event` on `Conversation.labeled`, window 1d. |
| Trigger | <a id="trigger-strong-fit-lead"></a>`strong-fit-lead` | Strong fit lead: `event` on `Lead.qualified`, window 1d. |
| Trigger | <a id="trigger-cold-call-reason"></a>`cold-call-reason` | Cold call reason: `event` on `Prospect.created`, window 7d. |
| Sequence | <a id="sequence-callback-attempts"></a>`callback-attempts` | Callback attempts: 4 steps; it ends on A connected call; A booked Meeting; Any reply to the written answer; An opt-out or Suppression; Consent withdrawn. |
| Sequence | <a id="sequence-human-callback"></a>`human-callback` | Human callback: 2 steps; it ends on A connected call; A booked Meeting; Any reply to the written answer; An opt-out or Suppression. |
| Sequence | <a id="sequence-cold-call-touches"></a>`cold-call-touches` | Cold call touches: 3 steps; it ends on A connected call; A voicemail left; A do-not-call request; Any reply; A booked Meeting; The Reason expiring. |
| Benchmark | <a id="benchmark-demo-request-call-coverage"></a>`demo-request-call-coverage` | Share of B2B demo requests that got any phone call: 4 % (A vendor-run speed-to-lead field experiment, 2026-09-22). |
| Benchmark | <a id="benchmark-demo-request-hours-to-call"></a>`demo-request-hours-to-call` | Average hours from a demo request to the first call: 34.5 hours (A vendor-run speed-to-lead field experiment, 2026-09-22). |