---
title: "Build it"
url: https://services.dev/docs/examples/sales/build
captured: [sales-build.txt]
---
# Build it

URL: https://services.dev/docs/examples/sales/build

> The sales brand's folder, one file at a time. Its words, then the closers' Pool on an existing door, then the Deal Service, with check after each one.

*sales's words: deal is a job, account is a subject, gig is a task, closer is a member, seller is a client, seller is a buyer (examples/sales/brand.ts).*

The brand is a folder of three files. Write them in this order and run `check` after each. The count it prints is lines of code, comments left out.

## brand.ts

The brand's words, so every surface says deal, account, gig and closer, and names the seller where the core says client and buyer. `check` prints the plan each file needs, and the domain is the line that sets Launch. [Brand()](https://services.dev/docs/service/brand.md) covers each key.

```bash
cat brand.ts
```

```txt title="magic values: none"
import { Brand } from 'services.dev'

// A sales brand shaped like api.forsale, proving Money and Outcome. Its words render every surface its sellers,
// agents and closers read: /deals, deal.ordered, get_deal. A seller pays nothing on effort: 5% of the closed value
// at won, or 15% when a closer takes the deal, the closer's 10% passed through whole, proven by the payment.
export default Brand({
  domain: 'api.forsale',
  words: { job: 'deal', subject: 'account', task: 'gig', member: 'closer', client: 'seller', buyer: 'seller' },
})
```

```bash
npx services.dev check   # after brand.ts
```

```txt title="magic values: none"
brand.ts · 2 lines · sales · api.forsale · words job → deal, subject → account, task → gig, member → closer, client and buyer → seller
  plan    Launch (services.dev) · Brand.domain at :7
No Services here.
  next    npx services.dev init
```

## pools/closers.ts

One Pool for the three roles a deal escalates to. It lists on closers.deals, a door another account runs, so each gig it fills carries a placement line, never taken from a closer's pay. A claim lasts while the closer works the account, and staff rule on a contested outcome. [The closers' Pool](https://services.dev/docs/examples/sales/pool.md) reads each line.

```bash
cat pools/closers.ts
```

```txt title="captured/sales-build.txt · magic values: none"
import { Pool } from 'services.dev'

// One Pool for every role a deal escalates to: setters book the meeting, closers take it to won or lost, and
// nurturers revive a deal gone quiet. The Pool lists on closers.deals, an existing door for B2B sellers, whose
// consumer face is closers.sale, so the door's roster, /join and verifier are the Pool's, with a placement line on
// each gig its members fill. A claim lasts as long as the closer works the account. Tiers follow deal size, a
// firm enrolls its bench under its own name, and staff rule on a contested outcome within 14 days.
export const closers = Pool({
  name: 'closers',
  door: 'closers.deals',
  roles: { setter: {}, closer: {}, nurturer: {} },
  join: {
    segments: ['SMB', 'midmarket', 'enterprise'], industries: 'text[]',
    acv: 'money: your typical deal size', hours: 'number: spare hours a week',
  },
  sees: { teaser: ['industry', 'value band', 'stage', 'pay', 'signals'] },
  rank: ['fit', 'track'],
  tiers: [{ name: 'SMB' }, { name: 'midmarket', after: 10, on_time: '90%' }, { name: 'enterprise', after: 40, on_time: '95%' }],
  firms: true,
  hold: { claim: '48h', then: 'touch' },
  contest: { by: 'staff', within: '14d' },
  payouts: { schedule: 'weekly', hold_first: { tasks: 5, for: '7d' } },
})
```

```bash
npx services.dev check   # after pools/closers.ts
```

```txt title="magic values: none"
pools/closers.ts · 14 lines · closers · 3 roles: setter, closer, nurturer · joins in 3 steps on closers.deals · feed, ranked by fit, then track · hold 48h to a first touch, then each touch renews it
  door    closers.deals · operated by another account · closers.sale is its consumer face · placement $25.00 a filled gig
  plan    Team (services.dev) · Pool.rank at :17 · Pool.tiers at :18 · Pool.firms at :19
No Services here.
  next    npx services.dev init
```

## services/deal.ts

The deal, at 42 lines: three roles and their pay, the closer's span from the meeting and how she reaches the account, the seller's policy, four signals, seven steps and a routed one, two guards, two ends with the reason a deal is lost, the outcome with its proof, and the price with its referral line. [The Deal Service](https://services.dev/docs/examples/sales/deal.md) walks it step by step.

```bash
cat services/deal.ts
```

```txt title="captured/sales-build.txt · magic values: none"
import { Service } from 'services.dev'
import { closers } from '../pools/closers.ts'

// A deal, worked from first touch to the account's payment. AI sources, qualifies, books, meets and negotiates.
// A warm reply sends the booking to a setter, paid once the meeting is held. A deal of $50,000 and up, a buying
// committee or a seller who asks sends the meeting to a closer, who keeps the deal to the end. An account that goes
// quiet sends the deal to a nurturer, and a revived deal returns to the step it left. The gate refuses a close below
// the seller's floor, from AI or a person. Won is proven by the account paying the seller's own invoice.
const setter = job => job.policy.setter === 'person' || (job.policy.setter === 'on_warm' && job.signals.warm)
const person = job => job.input.value >= job.policy.humans_from || job.signals.committee || job.signals.person

export default Service({
  name: 'Deal',
  description: 'A deal, worked to close.',
  input: {
    account: 'party', value: 'money: list value', motion: ['B2B', 'B2C', 'B2D', 'B2A'], offer: 'url',
    referrer: 'party?', expansion: 'boolean?' },
  output: {
    held: 'meeting', closed_value: 'money: the first 12 months the contract commits, over what the account already pays', billing: ['annual', 'monthly'], term: 'months?',
    contract: 'file?', pay_link: 'url', notes: 'text', lost: ['no_fit', 'price', 'timing', 'competitor', 'dark'] },
  policy: {
    floor: { word: 'number', default: 0.8, min: 0.1, max: 1, binds: 'merits', note: 'the least share of list a close may take' },
    humans_from: { word: 'money', default: 50_000, note: 'a closer takes deals at or above this value' },
    setter: { word: ['ai', 'on_warm', 'person'], default: 'on_warm', note: 'who books the meeting' },
    stall_days: { word: 'number', default: job => (job.input.motion === 'B2C' ? 7 : 14), min: 3, max: 90 },
  },
  roles: {
    setter: { pool: closers, pay: { usd: 250, on: 'meet.held' } },
    closer: { pool: closers, span: ['meet', 'propose', 'negotiate', 'close'], revive: { first_call: '24h' },
      pay: { share: '10%', of: 'closed_value', on: 'won', from: 'buyer' },
      talk: { as: 'client', mail: 'client_domain', calls: 'bridged' } },
    nurturer: { pool: closers, pay: 150 },
  },
  signals: {
    warm: job => job.record.replies > 0, committee: { inbound: true }, person: { inbound: true },
    gone_dark: { silent: job => `${job.policy.stall_days}d`, of: 'account', goto: 'nurture' } },
  deliver: {
    source: 'ai',
    qualify: { by: 'ai', next: job => (job.output.fit ? 'book' : 'lost') },
    book: { by: 'ai', escalate: { to: 'setter', when: setter } },
    meet: { by: 'ai', escalate: { to: 'closer', when: person }, when: job => job.input.motion !== 'B2A',
      guard: { held: (m, job) => m.attended(job.input.account) >= 10 || 'no one from the account stayed 10 minutes' } },
    propose: { by: 'ai' }, negotiate: { by: 'ai' },
    close: { by: 'ai', next: job => (job.output.closed_value ? 'won' : 'lost'),
      guard: { closed_value: (v, job) => v >= job.input.value * job.policy.floor || 'below the floor you set' } },
    nurture: { by: 'nurturer', routed: true, next: job => (job.output.revived ? job.record.interrupted : 'lost') },
  },
  ends: { won: { charge: true }, lost: { charge: false, by: ['client', 'brand'] } },
  outcome: { amount: 'closed_value', proof: 'payment', match: { customer: 'account', metadata: 'deal' },
    window: { bank: '60d', card: '120d' }, tail: '90d', due: '90d', collect: job => (job.output.billing === 'monthly' ? 'each' : 'first') },
  price: { usd: 0, share: '5%', of: 'closed_value', on: 'won', ai: 40,
    lines: { referral: { share: '1%', of: 'closed_value', to: 'referrer', on: 'won' } } },
})
```

`check` now reads the whole folder. Before the first order it prints what a won deal leaves you, what a lost one costs, the win rate where the two break even on each track, and the plan each file needs. Every way a deal ends, and its ledger, is on [the money](https://services.dev/docs/examples/sales/money.md#before-you-sell):

```bash
npx services.dev check   # after services/deal.ts
```

```txt title="magic values: none"
  plan    Launch (services.dev) · Brand.domain at :7
  plan    Team (services.dev) · Pool.rank at :17 · Pool.tiers at :18 · Pool.firms at :19
services/deal.ts · 42 lines · nothing on effort; 5% of closed_value at won, proven by the account's payment, held 60 days by bank, 120 by card.
  roles   setter · from closers · books the meeting · $250 when it is held, at meet.held · paid by api.forsale, won or lost
          closer · from closers · meet, propose, negotiate and close · 10% of closed_value at won · charged to the seller · held with the 5%
  policy  floor · merits, the seller's alone · 0.8 of value · 0.1 to 1
  outcome proof payment, read through services.dev's Stripe app on the seller's account · matched on the account's customer and metadata.deal · collect first, or each invoice when billing is monthly · due 90d · window 60d by bank, 120d by card, from each collection · tail 90d
  ledger  +$11,100.00 at won on $74,000 · closer $7,400.00 (share, from the seller) · setter $250.00 · placement $50.00 · AI $11.34 (example) · you $3,388.66
  lost    +$0.00 · setter $250.00 when the meeting was held · placement $50.00, the setter's gig and the closer's · AI up to $40.00 · you −$340.00 when a closer loses it
  odds    each track at the deal it takes · AI alone, $30,000: won $1,490.88, lost −$40.00 · breaks even at 2.6% won
          a setter, then AI, $40,000: won $1,714.52, lost −$315.00 · breaks even at 15.5% won, the worst bet
          a closer, $74,000: won $3,388.66, lost −$340.00 · breaks even at 9.1% won (about 1 in 11)
          gone quiet, a closer's deal: won $3,213.66, lost −$515.00 with the nurturer's $150.00 and its placement · breaks even at 13.8% won
  plan    Team (services.dev) · pay: { share } at :30, the closer's 10% at won · the referrer's 1% at :52
✓ ready to sell · 1 Service · AI closes a deal under $50,000 alone · nothing charged before won
```

[Its plans](https://services.dev/docs/examples/sales.md#its-plans) shows which keys need which plan.

---

- Up: [The sales example](https://services.dev/docs/examples/sales.md)
- Previous: [The sales example](https://services.dev/docs/examples/sales.md)
- Next: [One deal, every side](https://services.dev/docs/examples/sales/walk.md)
