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
- Deepens
- Outbound sequence
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' } })
{
"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"
}
}
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.
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.
| Step | Verb | Returns | Price | Approval |
|---|---|---|---|---|
| 1. Read each row’s evidence and buying trigger | Collection.query | The quote, its page and the trigger saved on each row | Free | Runs on its own |
| 2. Re-read each evidence page | Page.audit | The quote still on the page, or the row held as stale | About 2¢ | Runs on its own |
| 3. Write each first touch | Content.draft | An opening line that quotes the evidence or the trigger, then three sentences toward the ask | About 40¢ | Runs on its own |
| 4. Enroll each person who passes | Contact.enroll | Each person enrolled in the sequence from the warmed Mailbox, after the Consent and Suppression checks | About 14¢ | Goes out on its own |
| 5. Write each follow-up | Content.draft | A follow-up that adds a new fact or a new angle, never a bump, to each person who has not replied | About 76¢ | Runs on its own |
| 6. Hand each reply on | Prospect.engage | Each reply becomes a lead for Answer every inbound; each unsubscribe is suppressed | Free | Runs 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
| When | Then |
|---|---|
| A Mailbox nears its bounce threshold | It 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 page | The person is held as stale-evidence, or re-read when the agent can find a fresh quote for under 1¢. |
| A message fails Review twice | The person is held with the findings and that message is not billed. |
| You call it twice on the same Audience | No 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
| Ask | Compiles 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 |
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: 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 from | Kind | Its own price |
|---|---|---|
| Build a verified list | Workflow | About $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
| Fact | Value |
|---|---|
| Guide | Prospect.reach |
| Guide approval | limits |
| Goal | Send 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. |
| Trigger | on.Audience.built((audience, |
| Retry | Three times with backoff, then the next route; the same idempotency key never charges, sends or posts twice |
| Idempotency key | leaseline-prospect-reach-1 |
| Lookups | A 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
| Step | Function | Doer | Checker | Assurance | Inputs |
|---|---|---|---|---|---|
| 1. Read each row’s evidence and buying trigger | code | Code | Code checks each result against its declared type | measured | $0.audience |
| 2. Re-read each evidence page | code | Code | Code checks each result against its declared type | measured | $1.pages |
| 3. Write each first touch | agentic | The SDR agent | Adversarial review by a second model: Pass, or cut with the reason; a failure is redrafted once, then held | graded | $2.rows, |
| 4. Enroll each person who passes | code | Code | Code reads the result back from where it landed | measured | $3.passed, |
| 5. Write each follow-up | agentic | The SDR agent | Adversarial review by a second model: Pass, or held | graded | $4.silent, |
| 6. Hand each reply on | code | Code | Code reads the result back from where it landed | measured | replies |
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
| Kind | What it touches |
|---|---|
| Systems | Prospecting, Sequences, sites and content |
| Nouns | Collection, Page, Content, Action, Contact, Prospect |
| Events | Collection.queried, Content.drafted, Action.evaluated, Contact.enrolled, Prospect.engaged |
Reference
Inputs, outputs, its Event and its errors
| Name | Type | Default | Meaning |
|---|---|---|---|
audience | Audience | Required | The list, from Build a verified list or your own. |
offer | Offer | Required | What you are offering; no message claims what it cannot do. |
ask | string | 'a 15-minute call' | The one thing each message asks for. |
touches | integer | 3 | Messages per person: day 0, day 3 and day 8. Any reply ends the sequence. |
mailbox | Mailbox[] | your two warmed inboxes | Where 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. |
maxCost | number | 3.00 | 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 Prospect.reached. Refuses with max-cost before it spends, and with the Data calls and Accounts groups (refusals).