Skip to content

Personal first touch

Your verified list in, a first email to each person whose opening line quotes their own company, then two follow-ups that each add something new.

Kind
Agent task
Price
$1.33 per 100 people
Your time
15 minutes
Approval
Goes out on its own
Prospect.reach
You, to your agent

Send personal first touches to these 50 people, up to $3.

curl -X POST "https://api.sb/leaseline.app/prospects/reach?mode=try" \
  -H "Authorization: Bearer $SB_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"goal":"Send personal first touches to these 50 people, up to $3.","budget":{"amount":3,"currency":"USD"}}'
npx startup-builder do Prospect.reach --try --data '{"goal":"Send personal first touches to these 50 people, up to $3.","budget":{"amount":3,"currency":"USD"}}'
{
  "name": "do",
  "arguments": {
    "mode": "try",
    "code": "return await $.Prospect.reach({ goal: 'Send personal first touches to these 50 people, up to $3.', budget: { amount: 3, currency: 'USD' } })"
  }
}
import { $ } from 'startup-builder'

const result = await $.try.Prospect.reach({ goal: 'Send personal first touches to these 50 people, up to $3.', budget: { amount: 3, currency: 'USD' } })
Response
{
  "mode": "try",
  "stored": false,
  "estimate": "About $1.33 per 100 people",
  "sample": {
    "person": "Dana W., Director of Operations",
    "first line": "Saw you are hiring a leasing coordinator. Is the rent spreadsheet part of the job?",
    "evidence": "a job post, 2026-10-21",
    "review": "passed",
    "state": "sent"
  }
}
Try mode reads, stores nothing and spends nothingAPI referenceIts Nouns

What you get

  • 100 people · Your verified list in, a first email to each person whose opening line quotes their own company, then two follow-ups that each add something new. · Prospect
  • sent · First touches sent.
  • held[] · People not written to, with the reason, such as “the evidence quote is no longer on the page”.
  • sample[] · Opening lines with the evidence each one quotes, so you can read what went out.
  • review · The adversarial Review’s tally: passed, and cut with the reason.
  • replies · Replies, each handed to Answer every inbound; unsubscribes, each suppressed.

Connect once: Gmail, Outlook. How long: Drafting takes about 5 minutes; sends pace over 3 to 5 days at the mailbox caps, and the sequence closes by day 10 to 14.

Trigger

It runs when you call it on an Audience, and as each Buying-trigger feed hit lands.

TypeScript
on.Audience.built((audience, $) => $.Prospect.reach({ audience: audience.$id, offer: 'leaseline-pro', ask: 'a 15-minute call' }))

Steps

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

StepVerbReturnsPriceApproval
1. Read each row’s evidence and buying triggerCollection.queryThe quote, its page and the trigger saved on each rowFreeRuns on its own
2. Re-read each evidence pagePage.auditThe quote still on the page, or the row held as staleAbout 2¢Runs on its own
3. Write each first touchContent.draftAn opening line that quotes the evidence or the trigger, then three sentences toward the askAbout 40¢Runs on its own
4. Enroll each person who passesContact.enrollEach person enrolled in the sequence from the warmed Mailbox, after the Consent and Suppression checksAbout 14¢Goes out on its own
5. Write each follow-upContent.draftA follow-up that adds a new fact or a new angle, never a bump, to each person who has not repliedAbout 76¢Runs on its own
6. Hand each reply onProspect.engageEach reply becomes a lead for Answer every inbound; each unsubscribe is suppressedFreeRuns on its own

Calls: Collection.query, then Page.audit, then Content.draft, then Contact.enroll, then Content.draft, then Prospect.engage.

About $1.33 per 100 people, each time it runs, capped at $3.00.

Example: give it a goal

Send personal first touches to these 50 people, up to $3.

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

When something fails

Each failure, and what happens next
WhenThen
A Mailbox nears its bounce thresholdIt rests itself (Mailbox.rest) and the call waits on it; the other Mailboxes carry the day’s sends.
An evidence quote is gone from its pageThe person is held as stale-evidence, or re-read when the agent can find a fresh quote for under 1¢.
A message fails Review twiceThe person is held with the findings and that message is not billed.
You call it twice on the same AudienceNo person gets the same touch twice: it is idempotent per Audience, offer, touch and person.

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-prospect-reach-1-<step>-<item>.

How it is graded

A second model checks every fact in each message against the row’s evidence and your offer, before it sends. It passes when every person on the list gets a first touch whose opening line quotes their own evidence, and no message states a fact the row lacks; anyone it cannot write to is held with the reason. A miss is redone at no charge.

Change it

AskCompiles to
Ask for a trial instead of a call.Prospect.reach
Lead with the renewals angle from my message test.Prospect.reach
Send a LinkedIn connection note instead of a second email.Contact.enroll
TypeScript
import { $, workflow } from 'startup-builder'

export default workflow({
  name: 'prospect-reach',
  verb: 'Prospect.reach',
  input: { audience: 'string', offer: 'string', ask: 'string', touches: 'integer', mailbox: 'string?' },
  maxCost: 3,
  review: { pattern: 'adversarial', rubric: 'every fact matches the row’s evidence; no claim beyond the Offer' },
  grade: { evaluator: 'every fact against the row’s evidence; no claim beyond the Offer', passWhen: 'every person touched or held with a reason', remedy: 'redo' },
  execute: async (ctx, { audience, offer, ask = 'a 15-minute call', touches = 3, mailbox = 'warmed' }) => {
    const rows = await ctx.step('rows', () => $.Collection.query({ from: 'audience', id: audience, members: true }))
    const fresh = await ctx.step('evidence', () => $.Page.audit({ urls: rows.map((r) => r.evidence.url), fields: ['content'], maxCost: 0.05 }))
    for (const r of rows.filter((x) => fresh.has(x.evidence.quote))) {
      const first = await ctx.step(`write:${r.$id}`, () => $.Content.draft({ kind: 'first-touch', prospect: r, offer, ask, maxCost: 0.005 }))
      await ctx.step(`enroll:${r.$id}`, () => $.Contact.enroll({ contact: r.$id, sequence: 'three-touch', first: first.$id, mailbox }))
    }
    on.Message.replied((reply, $) => $.Prospect.engage({ from: reply.$id }))     // a reply ends the sequence and becomes a Lead
  },
})

Extend it with extends: 'Prospect.reach' and base(), or compose your own from these steps.

Works with

Plays that order it: Account visits, Benchmark rows, Buying committee, Champion follow, Cold email, Gifting, lead reactivation, LinkedIn outbound, Moment outreach, Objection watch, Product signals, Proof-of-work outreach, Warm intros.

Built fromKindIts own price
Build a verified listWorkflowAbout $1.38 per 100 checked emails

It serves Reach out, and runs in Hands-on, Borrowed. It deepens Outbound sequence.

For agents

For agents: the guide, its approvals, retries and lookups
FactValue
GuideProspect.reach
Guide approvallimits
GoalSend personal first touches to these 50 people, up to $3. 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.
Triggeron.Audience.built((audience, $) => $.Prospect.reach({ audience: audience.$id, offer: 'leaseline-pro', ask: 'a 15-minute call' }))
RetryThree times with backoff, then the next route; the same idempotency key never charges, sends or posts twice
Idempotency keyleaseline-prospect-reach-1
LookupsA find: input asks unless exactly one record fits
Step
1. Read each row’s evidence and buying trigger
2. Re-read each evidence page
3. Write each first touch
4. Enroll each person who passes Action class send
5. Write each follow-up
6. Hand each reply on

The amounts live once, on the hub (approvals).

Who does each step, and how it is checked
StepFunctionDoerCheckerAssuranceInputs
1. Read each row’s evidence and buying triggercodeCodeCode checks each result against its declared typemeasured$0.audience
2. Re-read each evidence pagecodeCodeCode checks each result against its declared typemeasured$1.pages
3. Write each first touchagenticThe SDR agentAdversarial review by a second model: Pass, or cut with the reason; a failure is redrafted once, then heldgraded$2.rows, $0.offer, $0.ask
4. Enroll each person who passescodeCodeCode reads the result back from where it landedmeasured$3.passed, $0.mailbox
5. Write each follow-upagenticThe SDR agentAdversarial review by a second model: Pass, or heldgraded$4.silent, $2.rows
6. Hand each reply oncodeCodeCode reads the result back from where it landedmeasuredreplies

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

Under the hood

Under the hood: the Systems, Nouns and Events it touches
KindWhat it touches
SystemsProspecting, Sequences, sites and content
NounsCollection, Page, Content, Action, Contact, Prospect
EventsCollection.queried, Content.drafted, Action.evaluated, Contact.enrolled, Prospect.engaged

Reference

Inputs, outputs, its Event and its errors
NameTypeDefaultMeaning
audienceAudienceRequiredThe list, from Build a verified list or your own.
offerOfferRequiredWhat you are offering; no message claims what it cannot do.
askstring'a 15-minute call'The one thing each message asks for.
touchesinteger3Messages per person: day 0, day 3 and day 8. Any reply ends the sequence.
mailboxMailbox[]your two warmed inboxesWhere cold first touches send from: 2 inboxes on a warmed second domain such as tryleaseline.app, 25 a day each after a 14-day ramp. Warm intros and replies stay on your main domain.
maxCostnumber3.00The 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 Prospect.reached. Refuses with max-cost before it spends, and with the Data calls and Accounts groups (refusals).

Contents