URL: /capabilities/convert-visitors/answer-every-inbound · Id: `page:/capabilities/convert-visitors/answer-every-inbound`

# Answer every inbound

> Every inbound question answered within 5 minutes from your own pages, with the next step set: a call booked, a trial, or you.

Every inbound question answered within 5 minutes from your own pages, with the next step set: a call booked, a trial, or you.

```text title="Run it · Ask"
Answer this week’s unanswered inbound questions, oldest first, spend up to $1, and show me the plan first.
```

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

const result = await $.try.Lead.respond({ goal: 'Answer this week’s unanswered inbound questions, oldest first, spend up to $1, and show me the plan first.', budget: { amount: 1, currency: 'USD' } })
```

```json title="Run it · Response"
{
  "mode": "try",
  "stored": false,
  "estimate": "About 1.1¢ an inbound",
  "sample": {
    "lead": "Dana W., Harbor Point Residential",
    "decision": "book-with-ae",
    "reason": "340 units; asked about portfolio reporting",
    "answered": "2m 51s",
    "next": "call booked Thu 10:30"
  }
}
```

- **Kind:** Agent task
- **Price:** 1.1¢ per inbound
- **Your time:** 20 minutes
- **Approval:** Runs on its own
- **Deepens:** [`workflow:answer-inbound`](https://startupbuilder.dev/workflows/processes/inbound-sales/answer-inbound)

## What you get
- **1 inbound** · Every inbound question answered within 5 minutes from your own pages, with the next step set: a call booked, a trial, or you. · [`noun:lead`](https://startupbuilder.dev/business/nouns/lead)
- `decision` · The choice: `book-with-ae`, `start-a-trial`, `nurture`, `not-a-fit`, `to-a-person` or `already-a-customer`, its reason and when it reopens.
- `answer` · The channel, when it went out, and the minutes it took.
- `next` · What it set in motion: a meeting booked, a trial started, a nurture enrolled, or a task for you.

**Connect once:** [Gmail](/code/integrations/gmail), [Google Calendar](/code/integrations/google-calendar). **How long:** The first answer in under 5 minutes; the measured median is 2 minutes 51 seconds.

## Trigger
It runs on every routed lead.

```ts
on.Lead.routed((lead, $) => $.Lead.respond({ lead: lead.$id }))
```

## 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. Read who sent it | [`Contact.enrich`](/code/data/people/enrich) | Name, title and employer | About 0.2¢ | Runs on its own |
| 2. Read their company | [`Organization.enrich`](/code/data/organizations/enrich) | Headcount and location, for the fit rule | About 0.2¢ | Runs on its own |
| 3. Match the question to your pages and offer | `Collection.query` | The passages that answer it, or none | Free | Runs on its own |
| 4. Decide the next step and record why | [`Lead.qualify`](/code/apps/crm/qualify) | One of six choices, recorded as a Decision with its reason and when it reopens | About 0.2¢ | Runs on its own |
| 5. Write the answer | `Content.draft` | An answer from your pages, with the next step in it | About 0.4¢ | Runs on its own |
| 6. Send it and set the next step | `Conversation.answer` | The answer sent; a call booked, a trial started or a nurture enrolled | About 0.05¢ | Runs on its own |

Calls: `Contact.enrich`, then `Organization.enrich`, then `Collection.query`, then `Lead.qualify`, then `Content.draft`, then `Conversation.answer`.

About 1.1¢ an inbound, each time it runs, capped at 10¢.

## Example: give it a goal
> Answer this week’s unanswered inbound questions, oldest first, spend up to $1, and show me the plan first.

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

## When something fails

| When | Then |
| --- | --- |
| Your pages cannot answer the question | The choice is `to-a-person`: a task for you, and a reply that says when a person will answer, still inside 5 minutes. |
| The sender is already a customer | It is routed to the customer’s owner and never answered as a new lead. |
| The calendar is not connected | It offers the booking link instead of three times, and tells you once. |
| One lead writes twice | It gets one first answer: it is idempotent per lead. |

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-lead-respond-1-<step>-<item>`.

## How it is graded
A second model checks each answer against your approved pages; code times it from arrival and reads back its Decision. It passes when every inbound is answered within 5 minutes from your approved pages, with its Decision recorded. A miss is free: you pay nothing for it.

## Change it
| Ask | Compiles to |
| --- | --- |
| Send questions about pricing to me instead. | [`Lead.respond`](/capabilities/convert-visitors/answer-every-inbound) |
| Offer a trial to everyone under 100 units. | [`Lead.route`](/capabilities/convert-visitors/inbound-routing) |
| Brief me before every call it books. | [`Deal.brief`](/capabilities/reach-out/account-brief) |

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

export default workflow({
  name: 'lead-respond',
  verb: 'Lead.respond',
  input: { lead: 'string', within: 'string', pages: "'approved' | string[]" },
  trigger: { event: 'Lead.routed' },
  maxCost: 0.1,
  review: { pattern: 'adversarial', rubric: 'approved Pages only; no claim beyond the Offer; no clash with an open Deal' },
  grade: { evaluator: 'the answer against approved Pages; minutes from arrival', passWhen: 'answered in 5 minutes with a Decision', remedy: 'free' },
  execute: async (ctx, { lead, within = '5m', pages = 'approved' }) => {
    const l = await ctx.step('lead', () => $.Collection.query({ from: 'lead', id: lead, with: ['contact', 'organization'] }))
    const passages = await ctx.step('pages', () => $.Collection.query({ from: 'page', where: pages === 'approved' ? "approved = true" : `url in ${pages}`, match: l.question }))
    const decision = await ctx.step('decide', () => $.Lead.qualify({ lead, from: passages, options: ['book-with-ae', 'start-a-trial', 'nurture', 'not-a-fit', 'to-a-person', 'already-a-customer'] }))
    const answer = await ctx.step('write', () => $.Content.draft({ kind: 'reply', to: l, from: passages, next: decision.choice, maxCost: 0.01 }))
    await ctx.step('send', () => $.Conversation.answer({ lead, body: answer.body, within }))
    if (decision.choice === 'book-with-ae') await ctx.step('book', () => $.Meeting.book({ lead, offer: 3 }))
    return { decision, next: decision.choice }
  },
})
```

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

## Works with
**Plays that order it:** [Agent-to-agent selling](/plays/agent-to-agent), [Instant response](/plays/instant-response), [lead nurture](/plays/lead-nurture), [Outbound calling](/plays/outbound-calling), [Single sign-on upgrade](/plays/sso-rung).

It serves [Convert visitors](/capabilities/convert-visitors), and runs in [Hands-on](/strategy/hands-on), [Paid](/strategy/paid). It deepens [`workflow:answer-inbound`](https://startupbuilder.dev/workflows/processes/inbound-sales/answer-inbound).

## For agents

| Fact | Value |
| --- | --- |
| Guide | `Lead.respond` |
| Guide approval | `limits` |
| Goal | Answer this week’s unanswered inbound questions, oldest first, spend up to $1, 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 | `on.Lead.routed((lead, $) => $.Lead.respond({ lead: lead.$id }))` |
| Retry | Three times with backoff, then the next route; the same idempotency key never charges, sends or posts twice |
| Idempotency key | `leaseline-lead-respond-1` |
| Lookups | A `find:` input asks unless exactly one record fits |

| Step | Action class | Seat rule that would hold it |
| --- | --- | --- |
| 1. Read who sent it | None | None. |
| 2. Read their company | None | None. |
| 3. Match the question to your pages and offer | None | None. |
| 4. Decide the next step and record why | None | None. |
| 5. Write the answer | None | None. |
| 6. Send it and set the next step | None | None. |

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

| Step | Function | Doer | Checker | Assurance | Inputs |
| --- | --- | --- | --- | --- | --- |
| 1. Read who sent it | `code` | Code | Code checks each result against its declared type | `measured` | `$0.lead.email` |
| 2. Read their company | `code` | Code | Code checks each result against its declared type | `measured` | `$1.organization` |
| 3. Match the question to your pages and offer | `code` | Code | Code checks each result against its declared type | `measured` | `$0.lead.question, $0.pages` |
| 4. Decide the next step and record why | `agentic` | The [AE](/capabilities/agents/ae) agent | A second model grades the output | `graded` | `$1, $2, $3` |
| 5. Write the answer | `agentic` | The [AE](/capabilities/agents/ae) agent | Adversarial review by a second model: Pass, or held: approved pages only, no claim past the offer, no clash with an open deal | `graded` | `$3.passages, $4.choice` |
| 6. Send it and set the next step | `code` | Code | Code reads the result back from where it landed | `measured` | `$5.passed, $4.choice` |

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 | [CRM](/code/apps/crm), [sites and content](/code/apps/sites-and-content) |
| Nouns | [`noun:contact`](https://startupbuilder.dev/business/nouns/contact), [`noun:organization`](https://startupbuilder.dev/business/nouns/organization), [`noun:collection`](https://startupbuilder.dev/business/nouns/collection), [`noun:lead`](https://startupbuilder.dev/business/nouns/lead), [`noun:content`](https://startupbuilder.dev/business/nouns/content), [`noun:action`](https://startupbuilder.dev/business/nouns/action), [`noun:conversation`](https://startupbuilder.dev/business/nouns/conversation) |
| Events | `Contact.enriched`, `Collection.queried`, `Lead.qualified`, `Content.drafted`, `Action.evaluated`, `Conversation.answered` |

## Reference

| Name | Type | Default | Meaning |
| --- | --- | --- | --- |
| `lead` | `Lead` | Required | The inbound: a form, a signup question, a reply to an email or a comment. |
| `within` | `string` | `'5m'` | The promise: the first answer goes out inside this. |
| `pages` | `'approved' \| Url[]` | `'approved'` | The pages an answer may draw on. A claim from anywhere else is cut. |
| `calendar` | `Calendar` | `your connected calendar` | Where a call is booked: three open times offered, the first accepted is held. |
| `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 `Lead.responded`. Refuses with [`max-cost`](/code/refusals#max-cost) before it spends, and with the Data calls and Accounts groups ([refusals](/code/refusals)).