URL: /capabilities/find-customers/demand-generation · Id: `page:/capabilities/find-customers/demand-generation`

# Demand generation

> Your strategy’s Plays run for the month, billed per qualified lead, and every lead and dollar of revenue traced to the Play that sourced it.

Your strategy’s [Plays](https://startupbuilder.dev/business/nouns/play) run for the month, billed per qualified [lead](https://startupbuilder.dev/business/nouns/lead), and every lead and dollar of revenue traced to the Play that sourced it.

```text title="Run it · Ask"
Deliver 25 qualified leads this month from my Hands-on Plays, $40 each, and show me revenue per Play.
```

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

const result = await $.try.Lead.generate({ goal: 'Deliver 25 qualified leads this month from my Hands-on Plays, $40 each, and show me revenue per Play.', budget: { amount: 1000, currency: 'USD' } })
```

```json title="Run it · Response"
{
  "mode": "try",
  "stored": false,
  "estimate": "About $30.95 per 25 qualified Leads",
  "bills": "$40 a qualified Lead",
  "sample": {
    "play": "Warm intros",
    "qualified leads": "4",
    "first calls": "4",
    "customers": "2",
    "first-year revenue": "$3,576"
  }
}
```

- **Kind:** Service
- **Price:** $40 per qualified lead
- **Your time:** 75 minutes
- **Approval:** Goes out on its own
- **Deepens:** [`capability:demand-generation`](https://startupbuilder.dev/systems/integrations/upstreams#demand-generation)

## What you get
- **25 qualified leads** · Your strategy’s Plays run for the month, billed per qualified lead, and every lead and dollar of revenue traced to the Play that sourced it. · [`noun:lead`](https://startupbuilder.dev/business/nouns/lead)
- `leads[]` · Each qualified lead: the account, the person and title, what they asked for, and the Play that sourced it.
- `byPlay[]` · Per Play: leads, first calls, customers and first-year revenue read from Stripe.
- `decisions[]` · Each week’s change to the mix, with the readout row that caused it.
- `bill` · Accepted leads times the price, up to the target.

**Connect once:** [Gmail](/code/integrations/gmail), [Google Calendar](/code/integrations/google-calendar), [Stripe](/code/integrations/stripe). **How long:** The calendar month, read weekly.

## Trigger
It runs when you call it, or on any Trigger you set.

```ts
await $.Workflow.create({ verb: 'Lead.generate', trigger: { schedule: 'weekly' } })
```

## 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. Plan each week’s Play mix | `PlayMix.compile` | The week’s Plays, hours and sends, inside your budget and sending limits | About 20¢ | Runs on its own |
| 2. Run warm intros and personal first touches | [`Prospect.reach`](/capabilities/reach-out/personal-first-touch) | Intros asked from your own inbox, and cold first touches from the warmed second domain | About $7.95 | Goes out on its own |
| 3. Answer buyers’ threads | [`Post.answer`](/capabilities/publish-and-post/answer-threads) | Ten threads a week answered as you, every claim sourced | About $9.19 | Goes out on its own |
| 4. List on the directories your buyers read | [`Post.submit`](/capabilities/earn-first-links/directory-submissions) | Each listing submitted from your approved kit, then checked live | About $13.04 | Goes out on its own |
| 5. Answer every inbound within 5 minutes | [`Lead.respond`](/capabilities/convert-visitors/answer-every-inbound) | Each inbound answered from your pages, with a call or a trial offered | About 42¢ | Runs on its own |
| 6. Qualify each lead and name its Play | [`Lead.route`](/capabilities/convert-visitors/inbound-routing) | Fit, title and request checked, and the sourcing Play recorded on the lead | About 16¢ | Runs on its own |
| 7. Read each Play every week | [`Play.review`](/capabilities/measure/weekly-readout) | Each Play beside its benchmark: keep, fix or switch, naming the row that fired | Free | Runs on its own |
| 8. Read revenue back to each Play | [`Metric.query`](/code/apps/analytics-and-experiments/query) | First-year revenue per Play, from each paying customer’s first lead | Free | Runs on its own |

Calls: `PlayMix.compile`, then `Prospect.reach`, then `Post.answer`, then `Post.submit`, then `Lead.respond`, then `Lead.route`, then `Play.review`, then `Metric.query`.

About $30.95 per 25 qualified leads, each time it runs, capped at $40.00.

**Revenue read back to each Play by month end. Warm intros brought the fewest leads and the most revenue per lead, so next month’s mix moves two hours a week to them.**

| Play | Qualified leads | First calls | Customers | First-year revenue |
| --- | --- | --- | --- | --- |
| [Warm intros](/plays/warm-intros) | 4 | 4 | 2 | $3,576 |
| [Cold email](/plays/cold-email) | 11 | 9 | 1 | $1,788 |
| [Community answers](/plays/community-answers) | 7 | 5 | 0 | Free |
| [Directory listings](/plays/directory-listings) | 5 | 3 | 0 | Free |
| **In all** | **27** | **21** | **3** | **$5,364** |

[Planning estimate](/strategy/numbers#assumptions) for the per-Play rows.

## Example: give it a goal
> Deliver 25 qualified leads this month from my Hands-on Plays, $40 each, and show me revenue per Play.

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

## When something fails

| When | Then |
| --- | --- |
| A Play falls under its break row two weeks running | Its hours move to the Play the readout ranks next, and the Decision names the row that fired. |
| The month ends under the target | The work continues at no charge into the next month; only accepted leads are ever billed. |
| A paying customer has no sourcing Play | The revenue reads as unattributed in its own row, never spread across the Plays. |

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

## What it promises
| Term | This Service |
| --- | --- |
| Done | A qualified lead: a person at an account that fits, whose title matches, who asked for a call or a trial, with the Play that sourced it recorded. |
| Counted by | `Lead.qualified` |
| Billed | $40 a qualified lead, on acceptance |
| Target | 25 qualified leads a month, from the Plays your strategy names. |
| Accepted when | Each lead is accepted 7 days after it is qualified, unless you disqualify it with a reason. |
| Credited | Any lead you disqualify within 7 days with a reason. |
| Remedy | Continued work at no charge until the target is met, with hours moved to the Plays that delivered. |
| Evaluator | Code reads `Lead.qualified` with its fit, title and sourcing Play, and reads Stripe payments back to each lead. |
| Window | The calendar month. |
| Assurance | `measured`, the weakest check on the way to done |
| Oversight | `none`: agents do every step |

**December, Leaseline’s second month on Hands-on, ordered at a target of 25.**

| Count | What happened |
| --- | --- |
| 25 | Ordered |
| 27 | Delivered |
| 27 | Qualified from four Plays: cold email 11, community answers 7, directory listings 5, warm intros 4. |
| 2 | Disqualified by you within 7 days: one was a competitor, one a student. Credited. |
| 25 | Accepted and billed, the target met in week four. |
| **25** | **Accepted, billed 25 × $40 = $1,000** |

## Change it
| Ask | Compiles to |
| --- | --- |
| Run Launch and search instead, and count signups as leads. | [`Lead.generate`](/capabilities/find-customers/demand-generation) |
| Move every hour from directories to warm intros. | `PlayMix.compile` |
| Show me revenue per Play for the last 90 days. | [`Metric.query`](/code/apps/analytics-and-experiments/query) |

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

export default Service({
  name: 'lead-generate',
  verb: 'Lead.generate',
  input: { strategy: 'string', icp: 'string', target: 'integer', hours: 'number' },
  maxCost: 40,
  builtFrom: ['Prospect.reach', 'Post.answer', 'Post.submit', 'Lead.respond', 'Lead.route', 'Play.review'],
  oversight: { posture: 'none' },
  contract: {
    done: 'Lead.qualified', accept: { by: 'founder', within: '7d', silence: 'accept' }, assurance: 'measured',
    bills: { usd: 40, per: 'qualified Lead', upTo: 'target' }, target: 25, remedy: 'continue-until-target',
  },
  execute: async (ctx, { strategy, icp, target = 25, hours = 5 }) => {
    while ((await ctx.count('Lead.qualified', { accepted: true })) < target) {
      const mix = await ctx.step('mix', () => $.PlayMix.compile({ strategy, hours, icp }))
      for (const play of mix.plays) await ctx.step(`run:${play.id}`, () => $.Play.apply({ play: play.id, hours: play.hours }))
      await ctx.step('readout', () => $.Play.review({ plays: mix.plays.map((p) => p.id) }))
      await ctx.sleep('1w')
    }
    return ctx.step('revenue', () => $.Metric.query({ metric: 'first-year-revenue', by: 'play', since: 'month' }))
  },
})
```

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

## Works with
**Plays that order it:** [Cold email](/plays/cold-email), [Community answers](/plays/community-answers), [Directory listings](/plays/directory-listings), [Launch sites](/plays/launch-sites), [Warm intros](/plays/warm-intros).

| Built from | Kind | Its own price |
| --- | --- | --- |
| [Personal first touch](/capabilities/reach-out/personal-first-touch) | Agent task | About $1.33 per 100 people |
| [Answer threads](/capabilities/publish-and-post/answer-threads) | Agent task | About $2.30 per 10 answers |
| [Directory submissions](/capabilities/earn-first-links/directory-submissions) | Workflow | About $13.04 per 40 directories |
| [Answer every inbound](/capabilities/convert-visitors/answer-every-inbound) | Agent task | About 1.1¢ an inbound |
| [Inbound routing](/capabilities/convert-visitors/inbound-routing) | Workflow | About 0.4¢ an inbound |
| [Weekly readout](/capabilities/measure/weekly-readout) | Workflow | Free |

It serves [Find your first customers](/capabilities/find-customers). It deepens [`process:strategy`](https://startupbuilder.dev/workflows/processes/strategy) and [`capability:demand-generation`](https://startupbuilder.dev/systems/integrations/upstreams#demand-generation).

## For agents

| Fact | Value |
| --- | --- |
| Guide | `Lead.generate` |
| Guide approval | `limits` |
| Goal | Deliver 25 qualified leads this month from my Hands-on Plays, $40 each, and show me revenue per Play. 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. |
| Retry | Three times with backoff, then the next route; the same idempotency key never charges, sends or posts twice |
| Idempotency key | `leaseline-lead-generate-1` |
| Lookups | A `find:` input asks unless exactly one record fits |

| Step | Action class | Seat rule that would hold it |
| --- | --- | --- |
| 1. Plan each week’s Play mix | None | None. |
| 2. Run warm intros and personal first touches | None | None. |
| 3. Answer buyers’ threads | None | None. |
| 4. List on the directories your buyers read | None | None. |
| 5. Answer every inbound within 5 minutes | None | None. |
| 6. Qualify each lead and name its Play | None | None. |
| 7. Read each Play every week | None | None. |
| 8. Read revenue back to each Play | None | None. |

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

| Step | Function | Doer | Checker | Assurance | Inputs |
| --- | --- | --- | --- | --- | --- |
| 1. Plan each week’s Play mix | `agentic` | The [Marketing lead](/capabilities/agents/marketing-lead) agent | Code checks the mix against your hours, budget and the sending rule before anything runs | `measured` | `$0.strategy, $0.hours` |
| 2. Run warm intros and personal first touches | `agentic` | The [SDR](/capabilities/agents/sdr) agent | A second model checks every fact in each message against the row before it sends | `graded` | `$1.mix` |
| 3. Answer buyers’ threads | `agentic` | The [Content lead](/capabilities/agents/content-lead) agent | A second model checks every claim in each answer against your pages | `graded` | `$1.mix` |
| 4. List on the directories your buyers read | `code` | Code | Each listing read back from the directory until it shows, with its link type | `measured` | `$1.mix` |
| 5. Answer every inbound within 5 minutes | `agentic` | The [SDR](/capabilities/agents/sdr) agent | A `Lead.created` read-back for every routed reply, timed from arrival | `measured` | `inbound` |
| 6. Qualify each lead and name its Play | `code` | Code | A `Lead.qualified` read-back carrying the fit, the title and the sourcing Play | `measured` | `$5.leads, $0.icp` |
| 7. Read each Play every week | `code` | Code | Each Play’s numbers read against its break rows | `measured` | `$6.leads` |
| 8. Read revenue back to each Play | `code` | Code | Stripe payments read back to each lead and the Play that sourced it | `measured` | `$6.leads` |

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) |
| Nouns | [`noun:play-mix`](https://startupbuilder.dev/business/nouns/play-mix), [`noun:prospect`](https://startupbuilder.dev/business/nouns/prospect), [`noun:post`](https://startupbuilder.dev/business/nouns/post), [`noun:lead`](https://startupbuilder.dev/business/nouns/lead), [`noun:play`](https://startupbuilder.dev/business/nouns/play), [`noun:metric`](https://startupbuilder.dev/business/nouns/metric) |
| Events | `PlayMix.compiled`, `Metric.queried` |

## Reference

| Name | Type | Default | Meaning |
| --- | --- | --- | --- |
| `strategy` | `Strategy` | Required | The strategy whose Plays run: Hands-on, Paid, Launch and search, Borrowed, or Keep and grow. |
| `icp` | `string` | Required | Your customer sentence; a qualified lead is at an account that matches it. |
| `target` | `integer` | `25` | Qualified leads this month. Fixed when you order; the work continues at no charge until it is met. |
| `hours` | `number` | `5` | Your hours a week for the calls and the replies only you can write. |

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