Lead reactivation
Writes once to each old Lead nobody works, only when something new answers what they asked, from their last contact at the Startup.
- Channel
- Lists
- Kind
- Conversion
Be our SDR for old Leads: write once to each person we collected and nobody works, only when something we shipped or published answers what they asked. Send from whoever they last talked to here. People who had a call with us get a note I write myself. Hold 1 in 10 back. Show me what’s missing; don’t send.
# Not live yet (gaps #2)
curl -X POST "https://api.sb/leaseline.app/campaigns?mode=try" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"play":"lead-reactivation"}'import { $ } from 'startup-builder'
return await $.Campaign.create({ play: 'lead-reactivation' }, { mode: 'try' }) // dry run on the day-0 Startup, 2026-08-24 · Not live yet, gaps #2
npx startup-builder do Campaign.create --json '{"play":"lead-reactivation"}' --try # Not live yet (gaps #2){
"name": "do",
"arguments": {
"code": "return await $.Campaign.create({ play: 'lead-reactivation' }, { mode: 'try' }) // dry run on the day-0 Startup, 2026-08-24 · Not live yet, gaps #2",
"mode": "try"
}
}application/json{
"type": "https: //startupbuilder.dev/ errors/ refused",
"title": "Refused",
"status": 422,
"detail": "lead-reactivation can’t start: 2 checks fail and 1 warns.",
"checks": [
{
"check": "use-when",
"status": "pass",
"observed": "612 owned records no Play works, quiet 90 days or more",
"doc": "/plays/ lead-reactivation#check-use-when",
"then": "Metric.query"
},
{
…
Hand our latest reason match to the SDR agent and show me what it chose and why. Don’t send.
curl "https://api.sb/leaseline.app/events?where=name%20in%20(%22Visitor.identified%22%2C%20%22Lead.nurtured%22%2C%20%22Gathering.registered%22%2C%20%22Offer.trialed%22%2C%20%22Contact.imported%22%2C%20%22Version.deployed%22%2C%20%22Content.published%22%2C%20%22Offer.published%22%2C%20%22Gathering.opened%22%2C%20%22Opening.published%22%2C%20%22Company.funded%22%2C%20%22Conversation.answered%22)&limit=1" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"
# Not live yet (gaps #7)
curl -X POST "https://api.sb/leaseline.app/triggers/fire?mode=try" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"like":"event_1214"}'
# Not live yet (gaps #7)
curl "https://api.sb/leaseline.app/decisions?where=on%20%3D%20%22lead_73%22" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"
# Not live yet (gaps #7)
curl "https://api.sb/leaseline.app/actions?where=on%20%3D%20%22lead_73%22" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"import { $ } from 'startup-builder'
const { events: [last] } = await $.Event.list({ where: 'name in ("Visitor.identified", "Lead.nurtured", "Gathering.registered", "Offer.trialed", "Contact.imported", "Version.deployed", "Content.published", "Offer.published", "Gathering.opened", "Opening.published", "Company.funded", "Conversation.answered")', limit: 1 }) // { play } compiled to this Play’s Trigger Events
const { subject } = await $.Trigger.fire({ like: last.$id }, { mode: 'try' }) // the same Event again, in try mode · Not live yet, gaps #7
const { decisions: [decision] } = await $.Decision.list({ where: `on = "${subject}"` }) // what it chose, and why · Not live yet, gaps #7
const { actions } = await $.Action.list({ where: `on = "${subject}"` }) // what it did, with its checks · Not live yet, gaps #7
return { decision, actions }
npx startup-builder do Event.list --json '{"where":"name in (\"Visitor.identified\", \"Lead.nurtured\", \"Gathering.registered\", \"Offer.trialed\", \"Contact.imported\", \"Version.deployed\", \"Content.published\", \"Offer.published\", \"Gathering.opened\", \"Opening.published\", \"Company.funded\", \"Conversation.answered\")","limit":1}'
npx startup-builder do Trigger.fire --json '{"like":"event_1214"}' --try # Not live yet (gaps #7)
npx startup-builder do Decision.list --json '{"where":"on = \"lead_73\""}' # Not live yet (gaps #7)
npx startup-builder do Action.list --json '{"where":"on = \"lead_73\""}' # Not live yet (gaps #7){
"name": "do",
"arguments": {
"code": "const { events: [last] } = await $.Event.list({ where: 'name in (\"Visitor.identified\", \"Lead.nurtured\", \"Gathering.registered\", \"Offer.trialed\", \"Contact.imported\", \"Version.deployed\", \"Content.published\", \"Offer.published\", \"Gathering.opened\", \"Opening.published\", \"Company.funded\", \"Conversation.answered\")', limit: 1 }) // { play } compiled to this Play’s Trigger Events\nconst { subject } = await $.Trigger.fire({ like: last.$id }, { mode: 'try' }) // the same Event again, in try mode · Not live yet, gaps #7\nconst { decisions: [decision] } = await $.Decision.list({ where: `on = \"${subject}\"` }) // what it chose, and why · Not live yet, gaps #7\nconst { actions } = await $.Action.list({ where: `on = \"${subject}\"` }) // what it did, with its checks · Not live yet, gaps #7\nreturn { decision, actions }",
"mode": "try"
}
}application/json{
"decision": {
"$id": "https: //api.sb/ leaseline.app/ decisions/ decision_440",
"$type": "Decision",
"choice": "own-the-miss",
"reason": "Teo Marsh at Granite Row asked on Mar 3 whether Leaseline handles a second owner and nobody answered; per-owner batches shipped Sep 28 (the changelog)",
"stakes": "low",
"options": [
"send-the-reason",
"own-the-miss",
"founder-writes-it",
"to-the-ae",
"park-it",
"release-it"
…
sample · 4 Not live yetPreviewOn PATCH /{namespace}/campaigns/{id}Off PATCH /{namespace}/campaigns/{id}Take this one POST /{namespace}/tasks/{task}/claim
action_1 checked Teo’s address is still live. action_2 is the one Message, held to his 09:00 and signed by the labeled agent, since nobody at Leaseline wrote to him before.
+6.6 per 100 delivered · ■■□□ Early sign · n 218 · path → arr
| Try another: the record | Choice | Reason |
|---|---|---|
| Granite Row: a Form on Mar 3, never answered; per-owner batches shipped Sep 28 | own-the-miss | the answer is now published |
| Brookside Flats: a lapsed trial; said “Team is too pricey for 60 units” | send-the-reason | Team’s new 60-unit price answers the blocker |
| Linden Court: a held Meeting in April, then silence | founder-writes-it | first tier: you write it |
| Ashgrove Homes: a registrant with no new reason since May | park-it | no fresh reason; re-matched every Monday |
- 40 min a day week 1, then 30 a week
- Your time
Where it sits: Stranger → Lead → Trialing → Activated → At risk → Churned → Won back · Reactivated pipeline → Deals and trials opened → New Customers ↑ → ARR added each week → ARR
Worth’s formula: about +6.6 Deals or trials opened per 100 delivered × 120 delivered a month × 0.25 opens to paid × $590 first order ARR ≈ +$1,168. Under First signal: About 9 weeks to tell a +6.6 lift at 120 delivered a month with 1 in 10 held back (8 to read, 1 to mature); at 56 days it reads Early sign.
sample · api.sb/
Start it this week
npx startup-builder add play lead-reactivation # 1 · writes plays/lead-reactivation.mdx (Not live yet, gaps #5)
# 2 · dry run; below, its 422 on 2026-08-24
curl -X POST "https://api.sb/leaseline.app/campaigns?mode=try" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"play":"lead-reactivation"}'
# 3 · keep it, at You approve each one for the first 50 drafts
curl -X POST https://api.sb/leaseline.app/campaigns \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"play":"lead-reactivation"}'
# on
curl -X PATCH https://api.sb/leaseline.app/campaigns/lead-reactivation \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"endsAt":null}'
# off: no round sends; parked records keep their reasons
curl -X PATCH https://api.sb/leaseline.app/campaigns/lead-reactivation \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"endsAt":"now"}' | Check | Fix | Then |
|---|---|---|
use-when pass · Not live yet, gaps #8 | 612 owned records no Play works, quiet 90 days or more | POST /{namespace}/metrics/query |
baseline pass · Not live yet, gaps #8 | 0.9 Deals or trials opened per 100 quiet records a month, with no Message, n 612 | POST /{namespace}/metrics/query |
collection-record fail | Map each imported row to the Form, Registration or trial that collected it. | Contact.import |
mailbox-warm fail · Not live yet, gaps #1 | Finish warming the reactivation Mailbox, about 5 more days. | POST /{namespace}/mailboxes/{mailbox}/warm |
domain-authenticated pass | — | POST /{namespace}/domains/{domain}/authenticate |
role-unstaffed warn · Not live yet, gaps #8 | Staff the AE, or leave it: Interested replies come to you. | POST /{namespace}/roles/{role}/staff |
JSON
{
"type": "https://startupbuilder.dev/errors/refused",
"title": "Refused",
"status": 422,
"detail": "lead-reactivation can’t start: 2 checks fail and 1 warns.",
"checks": [
{
"check": "use-when",
"status": "pass",
"observed": "612 owned records no Play works, quiet 90 days or more",
"doc": "/plays/lead-reactivation#check-use-when",
"then": "Metric.query"
},
{
"check": "baseline",
"status": "pass",
"observed": "0.9 Deals or trials opened per 100 quiet records a month, with no Message, n 612",
"doc": "/plays/lead-reactivation#check-baseline",
"then": "Metric.query"
},
{
"check": "collection-record",
"status": "fail",
"observed": "40 of 300 imported rows",
"doc": "/plays/lead-reactivation#check-collection-record",
"fix": "Map each imported row to the Form, Registration or trial that collected it.",
"then": "Contact.import"
},
{
"check": "mailbox-warm",
"status": "fail",
"observed": "day 9 of 14",
"doc": "/plays/lead-reactivation#check-mailbox-warm",
"fix": "Finish warming the reactivation Mailbox, about 5 more days.",
"then": "Mailbox.warm"
},
{
"check": "domain-authenticated",
"status": "pass",
"doc": "/plays/lead-reactivation#check-domain-authenticated",
"then": "Domain.authenticate"
},
{
"check": "role-unstaffed",
"status": "warn",
"roles": [
"sales-lead"
],
"owned": 9,
"doc": "/plays/lead-reactivation#check-role-unstaffed",
"fix": "Staff the AE, or leave it: Interested replies come to you.",
"then": "Role.staff"
}
]
}It says no, says why, and names the fix.
| Check | Fix (one call) | Your time | Wait |
|---|---|---|---|
use-when · baseline · pass: 612 quiet records; 0.9 per 100 a month, n 612 (gaps #8) | POST /{namespace}/metrics/query on reactivated-pipeline, which fills Before in Your number | 1 min | none |
collection-record · fail (gaps #8): 40 of 300 imported rows | Contact.import (Not live yet, gaps #2) again with each row’s collecting Form, Registration or trial; rows without one are refused | 20 min | none |
mailbox-warm · fail: day 9 of 14 | POST /{namespace}/mailboxes/{mailbox}/warm on the reactivation Mailbox | 0 min | about 5 days |
domain-authenticated · role-unstaffed · pass; warn (gaps #8): 9 of your records have a person owner, you | POST /{namespace}/roles/{role}/staff, or leave it: Interested replies come to you, and the agent drafts into your queue for your 9 | 0 min | none |
Your number · Before 0.9 per 100 with no Message · Now 6.6 per 100 delivered · Target 8 by Dec 31 · ■■□□ Early sign · n 218 matured
Each drafted Message costs about $0.05 (Worker cost), address checks cost $0.01 each, and sending runs on the shared broadcast stream. A founder note costs your 3 minutes. That comes to about $7 a month at 120 delivered, plus a one-time $3 to check the 300 imported addresses.
When it starts
| When | Trigger id | Starts on | Window | How it gets in |
|---|---|---|---|---|
| A Change reaches every Customer and answers a need someone in the pool stated | need-shipped | event on Version.deployed where answers a need a pooled Person stated | 30d | from launch-week’s feed of shipped Changes |
| An Offer changes its price, term or plan size, answering a stated blocker | offer-changed | event on Offer.published where answers a blocker a pooled Person stated | 30d | POST /{namespace}/offers/{offer}/publish |
| A Form Lead is 30 days old with no reply, no Enrollment and no Deal | form-ghosted | state on Visitor.identified where made by a Form, 30 days old, no Worker reply, no Enrollment and no Deal | 730d | from your Forms, through POST /{namespace}/leads |
+ 13 more for nurture exits, silent registrants, lapsed trials, imports, owed answers, Gatherings, outcome facts, hiring, funding, not-now dates, known returns and resolved loss reasons (?lanes=) |
{
"$id": "https://api.sb/leaseline.app/events/event_1214" ,
"$type": "Event",
"name": "Version.deployed",
"at": "2026-10-19T07:30:20Z" ,
"seq": 1214,
"data": {
"change": "changes/change_33" ,
"title": "Per-owner renewal batches",
"rollout": 1,
"matches": [
{
"lead": "leads/lead_73" ,
"said": "Do you handle a second owner on one portfolio?" ,
"saidAt": "2026-03-03",
"answered": false
}
]
}
}# each delivery is signed with the Watch’s secret
curl -X POST https://api.sb/leaseline.app/watches \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"selector":"Version.deployed","url":"https://leaseline.app/hooks/sb"}'
npx startup-builder trigger Version.deployed --like event_1214 # replays the delivery through reason matching
npx startup-builder clock advance --by 7d # closes the round after 7 silent daysWho runs it
SDR · your agent (role:, worker_2) holds the Key Result: it pools, dedupes and matches each record to one reason, and sends as the Person’s last correspondent or as the labeled agent. First-tier records hand to you (Dana Okafor, person, worker_1), and Interested replies go to AE · Priya Shah (person) (role:, worker_3, staffed on day 9).
| Option | Rung today | Evidence (passed of n) | Moves up at | Drops back when | You can |
|---|---|---|---|---|---|
| Send the reason · Own the miss | It does it, you can stop it | 50 drafts reviewed, 46 passed | met on day 16 (50 drafts) | a wrong-match or negative-reply Stop → You approve each one | POST /{namespace}/actions/{action}/cancel inside the hold |
| Founder writes it | You approve each one | 14 notes, all yours | never: a note in your name stays yours | — | POST /{namespace}/tasks/{task}/return with a reason |
| To the AE · Not now · Let them go | It does it | 9 of 9 · 211 parked · 64 released | — | a customer-collision → You approve each one, plus an ownership check | POST /{namespace}/actions/{action}/redirect |
| Change the match rule | You approve each one | 1 merged of 1 | never | — | POST /{namespace}/changes/{change}/merge · POST /{namespace}/changes/{change}/revert |
| Never: a check-in with no reason, a Visit or Page view in the body, a reply prefix that implies a thread | Refused, every time | 3 drafted, 3 refused | never | — | the watched-event Check and the critic pass |
What it reads · What it may call. It reads what each Person did with Leaseline and said, with dates, and only public sources for a reason; a return to the site may raise a record’s rank but never appears in what it writes. It may call POST /{namespace}/contacts/{contact}/enrich, POST /{namespace}/messages/{message}/schedule and POST /{namespace}/leads/{lead}/assign. Its tools are POST /{namespace}/messages/{message}/schedule, POST /{namespace}/tasks/{task}/assign, POST /{namespace}/meetings/book, and any other call becomes an Approval under the Role’s Mandate.
You hold the SDR Key Result: 8 Deals or trials opened per 100 delivered by 2026-12-31. ← worker, metric
On each reason Event, match a record and choose one option, or write NO_REASON. ← triggers, chooses
Match by reason code first, then by model at 0.8 or more; under that, park it. ← your file, line 4
State only what they did with Leaseline, with its date, and a reason with its public source. ← copyRules 2, 3
Never name a Visit, an open, a click or a return to the site. ← copyRules 4, watched-event
One round per reason kind; never repeat an Offer, fact or reason to a Person. ← copyRules 11 The slow loop, as a Change. decision_398 (2026-09-21, weekly) read 4 positive replies from 31 model matches at 0.7 to 0.8 against 11 from 62 at 0.8 or more, with 3 wrong matches in the lower band, so change_31 raised the model match line from 0.7 to 0.8 (your file, line 4), and Dana merged it with POST /{namespace}/changes/{change}/merge. Replayed on past matches, POST /{namespace}/changes/{change}/review cut wrong matches to 0 of 40; read back at +21d it is 1 of 37, n 37, ■□□□ Too early to tell (Example, day 49).
Bring your own agent (folded) sends at It does it, you can stop it when it matches or beats the seated agent on the eval suite, and at You approve each one when it does not; POST /{namespace}/roles/{role}/staff seats it.
npx startup-builder eval --scenario lead-reactivation-251 --agent "claude -p" --seat prospector # seated: 6.6 per 100, 0 watched Events sent, 0 repeats, $0.06 a delivered · yours: 5.9, 1 watched Event drafted, 0 repeats, $0.08 → You approve each one What it can do next
| Option | Picked when | Decided by | Runs | Hands to | If it’s just you |
|---|---|---|---|---|---|
Send the reason send-the-reason | a fresh announced reason with a public source answers what they stated | Agent’s call, with its reason | reactivation-round: one Message, then the round closes after 7 silent days | nobody; replies go through the reply matrix | the same |
Own the miss own-the-miss | a Form nobody answered, and the answer is now published | Your rule (the ghosted-Form template) | reactivation-round with the miss owned in one clause → POST /{namespace}/messages/{message}/schedule | nobody | the same |
Founder writes it founder-writes-it | first tier: a held Meeting or a thread with a person | Your rule (the tiers) | reactivation-round-founder in their last thread, or reactivation-tray-founder on LinkedIn → POST /{namespace}/tasks/{task}/assign | you write it, due 1 business day · Take this one | you |
To the AE to-the-ae | an Interested reply, or a question only a person can answer | Agent’s call, with its reason | POST /{namespace}/meetings/book with the AE’s times → POST /{namespace}/tasks/{task}/assign | POST /{namespace}/tasks/{task}/assign → AE · Priya Shah, due 60 min (response-60m), Brief attached · Take this one | you, within 60 min |
Not now park-it | no fresh reason, or every reason kind already used with them | Your rule (line 4) | nothing; parked and re-matched every Monday | nobody | the same |
Let them go release-it | an active Customer or open Deal at their Organization, a lost Deal, or another Play holds them | Your rule (one Play per record) | nothing; names the Play or the owner that holds them | the holder | the same |
| When they write back | Fixture reply | What happens | Who answers | By when | Call |
|---|---|---|---|---|---|
| Interested | “Yes, this is what we needed. Can we talk Thursday?” | To the AE: three times from Priya’s calendar; the round closes | AE · Priya Shah (person) | 60 min | POST /{namespace}/meetings/book |
| Send me pricing | “What does it cost for 64 units?” | answers with the Pricing Page’s list Price and one next step | SDR · your agent | 5 min | POST /{namespace}/conversations/{conversation}/answer |
| Not now | “Not this year.” | no round for 12 months; a date they name becomes a not-now-date | SDR · your agent | at once | POST /{namespace}/messages/{message}/schedule |
| Wrong person | “I left Granite Row in June.” | thanks them; the record closes, and it never writes to an address from their text | SDR · your agent | 1 business day | POST /{namespace}/conversations/{conversation}/answer |
| Out of office | an auto-reply until Oct 28 | the round waits for the return date and closes 7 days after | nobody | the return date | POST /{namespace}/messages/{message}/schedule |
| Question it can’t answer | “Can you move our 4 years of history over?” | To the AE, the question in the Brief | AE · Priya Shah, or you | 60 min | POST /{namespace}/conversations/{conversation}/escalate |
| Stop | “Remove me.” | a Suppression on every scope; nothing sends again, from any of your Startups | nobody | at once | POST /{namespace}/contacts/{contact}/suppress |
When it hands to you
First-tier records go to you (Dana Okafor, person) to write; Interested replies go to AE · Priya Shah (person), and before day 9 they came to you. As captured, one Brief per Role (brief is Not live yet, gaps #7).
[
{
"$id": "https://api.sb/leaseline.app/tasks/task_530" ,
"$type": "Task",
"assignee": "worker_1",
"due": "2026-10-20T17:00:00Z" ,
"on": "lead_79",
"brief": {
"who": "Linden Court, 88 units; you held a Meeting on 2026-04-14" ,
"said": "we’ll revisit once owners can see their own renewals",
"reason": "the owner portal shipped 2026-10-18 (change_41)",
"chose": "founder-writes-it",
"thread": "your April thread",
"never": [
"a check-in with no reason",
"a Visit in the body"
]
}
},
{
"$id": "https://api.sb/leaseline.app/tasks/task_486" ,
"$type": "Task",
"assignee": "worker_3",
"due": "2026-10-07T15:10:00Z" ,
"on": "lead_47",
"brief": {
"who": "Brookside Flats, 60 units, a lapsed trial" ,
"said": "Yes, the 60-unit price works. Can we talk Thursday?" ,
"chose": "to-the-ae",
"why": "an Interested reply",
"never": [
"a discount beyond the Pricing Page"
]
}
}
]You answer a first-tier brief by writing the note in their last thread, with POST /{namespace}/tasks/{task}/return and a reason, or with POST /{namespace}/actions/{action}/redirect. The AE answers with POST /{namespace}/tasks/{task}/claim (Take this one), POST /{namespace}/tasks/{task}/return with a reason, or POST /{namespace}/actions/{action}/redirect; once claimed, the agent’s pending sends to that Person cancel with claimedByPerson (Not live yet, gaps #7). A Task nobody claims by its due time goes up a Role through POST /{namespace}/tasks/{task}/escalate (AE to you), and unanswered-positive fires.
On time: AE · Priya Shah: 8 of 9 claimed within 60 min, median 24 min, n 9 (the 9th waited 78 min and paused new sends). You: 14 of 14 notes within 1 business day, n 14, ■■□□ Early sign beside Other Startups’ 90 %. Your queue: counted by kind: in week 1, 50 draft reviews and 6 founder briefs; after day 16, about 2 founder briefs and 1 AE hand-off a week.
The fixed line: POST /{namespace}/deals/{deal}/assign sets owner to the receiving person, the agent is never a Deal’s owner, and Meetings land on the owner’s calendar under the owner’s name.
When it won’t send
| Check (refuses one send) | Refuses | Fix | Then |
|---|---|---|---|
owned-by-a-person (rule j; Not live yet, gaps #8) | a round to a Contact whose Lead or open Deal a person owns, or whose Conversation a person claimed | Leave it to the person who owns it: the draft waits in their queue. | POST /{namespace}/tasks/{task}/assign |
customer-collision (gaps #1; one sent pauses the Play) | a round to a Person at an Organization with an active Subscription or an open Deal | Send it to their CSM or AE instead. | POST /{namespace}/conversations/{conversation}/assign |
approved-pages (Not live yet) | a fact, price or outcome that the claims source and the Person’s own record lack | Cut the line, or publish the Page that says it. | POST /{namespace}/sites/{site}/publish |
watched-event (copy rule 4) | a Visit, an open, a click, a resolved Organization or a return to the site in the body | Cut it; lead with the reason and its public source. | POST /{namespace}/content/draft |
verification (email) | a List whose address check finds over 20 % invalid | Review the List; send to the verified addresses only. | POST /{namespace}/tasks/next |
market-hold | a draft to an unresolved Market or an unverified row | Resolve the Market; the draft waits and never retries another way. | POST /{namespace}/tasks/next |
| Stop (pauses the Play) | Pauses when | Then |
|---|---|---|
bounce (email) | hard bounces over 2 % of a day’s sends, at 50 or more | the stream pauses and its queue is checked again |
complaint (email) | stop and hostile replies over 0.3 % of the last 1,000 on the broadcast stream, hostile counted 5 times | the stream pauses |
outbound-complaint (email) | the same weighting over 1 % of an outbound Domain’s last 1,000 | the Domain pauses |
provider-spam (email) | a mailbox provider reports 0.08 % spam | every Mailbox of the Play pauses on that provider |
repeat-complaint (email) | 2 complaints in 7 days from People written to before | every send pauses until you review a dedupe audit of the last 200 |
wrong-match | the monthly hand check finds over 10 % of 30 reasons miss the stated need | matching halts |
reply-floor (email) | positive replies under 2 % after 200 delivered | the List stops |
negative-reply (email) | stop and hostile replies over 3 % of delivered, after 100 | the List pauses |
unanswered-positive (standard response-60m) | an Interested reply waits past its due time | new sends pause; POST /{namespace}/tasks/{task}/claim resumes it |
freshness | a failed Monday canary, or 14 days with no reason Event while Changes shipped | matching pauses |
kill-switch | you press it | every Tactic pauses on every stream at once |
The captured refusal, then the Stop that fired as a Campaign.paused reason (Not live yet, gaps #2); the rewrite that sent is the Granite Row thread in Example.
| Check | Fix | Then |
|---|---|---|
watched-event fail | Cut it; lead with the reason and its public source. | POST /{namespace}/content/draft |
customer-collision pass · Not live yet, gaps #1 | — | POST /{namespace}/leads/{lead}/assign |
JSON
{
"type": "https://startupbuilder.dev/errors/refused",
"title": "Refused",
"status": 422,
"detail": "The round to Granite Row did not send: it names a return to the site.",
"checks": [
{
"check": "watched-event",
"status": "fail",
"doc": "/plays/lead-reactivation#check-watched-event",
"fix": "Cut it; lead with the reason and its public source.",
"then": "Content.draft"
},
{
"check": "customer-collision",
"status": "pass"
}
]
}It says no, says why, and names the fix.
[
{
"reason": {
"limit": "unanswered-positive",
"observed": "78m",
"bound": "60m",
"window": "1 reply"
},
"doc": "/plays/lead-reactivation#stop-unanswered-positive" ,
"task": "task_489"
}
]Did it work
Both halves are read from the same random assignment, 1 in 10 newly eligible records per List held back for 60 days (rules m–o), and each figure’s call is POST /{namespace}/metrics/query by node and by arm; every node above the leaf is Not live yet (gaps #4).
+6.6 per 100 delivered · ■■□□ Early sign · n 218 · path → arr
- Chose
- own-the-miss
- Why
- Teo Marsh at Granite Row asked on Mar 3 whether Leaseline handles a second owner and nobody answered; per-owner batches shipped Sep 28 (the changelog)
- From
- send-the-reason · own-the-miss · founder-writes-it · to-the-ae · park-it · release-it
- Expects
- a Deal or trial opened within 30d
- Reopens when
- no Lead.convert or Offer.trialed for lead_73 within 30d
- By
- worker_2
JSON
{
"$id": "decisions/decision_440",
"$type": "Decision",
"choice": "own-the-miss",
"reason": "Teo Marsh at Granite Row asked on Mar 3 whether Leaseline handles a second owner and nobody answered; per-owner batches shipped Sep 28 (the changelog)",
"stakes": "low",
"options": [
"send-the-reason",
"own-the-miss",
"founder-writes-it",
"to-the-ae",
"park-it",
"release-it"
],
"by": "worker_2",
"on": "lead_73",
"expected": "a Deal or trial opened within 30d",
"reopensWhen": "no Lead.convert or Offer.trialed for lead_73 within 30d"
}| Node | Agent half (delivered) | Held back | Added | Confidence | Basis |
|---|---|---|---|---|---|
reactivated-pipeline | 6.6 per 100 (13 of 196 matured) | 0 (0 of 22) | +6.6 [0.4, 12.8] | ■■□□ Early sign | held back at random · n 218 · 33 still maturing |
opened-deals-and-trials | 13 Deals and trials opened | 0 | +13, carried: the leaf counts this node’s units | ■■□□ Early sign | the same assignment |
new-arr ↑ | $4,375, 5 Customers (3 on Pro, 1 on Team, 1 on the $2,400 plan) · Booked: credited, not all caused by this Play | $0 | +$2,232 per 100 delivered [−$800, +$5,300] · Worth about +$1,200 a month | ■□□□ Too early to tell (5 vs 0 Customers) | the same · 4 trials still deciding |
net-new-arr · arr | +$4,375 | $0 | carried: it moves no other bridge term | ■□□□ Too early to tell | carried from new-arr |
The leaf counts Deals or trials opened within 30 days per 100 Persons delivered one Message, against the held-back half, where a self-serve first paid Order counts as one, to 200 matured in each half; at 196 and 22 so far the held-back half runs on.
How it moves it: each Message cites one shipped Change, Offer or published answer that matches what the Person said, with its public source, from their last correspondent.
Guardrails: treated against held back: outside-paying-customers, 5 against 0, and stops plus hostile replies, 1.4 % of delivered against the 3 % Stop; 0 repeat complaints.
{
"$id": "https://api.sb/leaseline.app/campaigns/lead-reactivation" ,
"$type": "Campaign",
"play": "play:lead-reactivation@1" ,
"path": [
"reactivated-pipeline",
"opened-deals-and-trials",
"new-arr",
"net-new-arr",
"arr"
],
"experiment": {
"metric": "reactivated-pipeline",
"unit": "per-100",
"heldBack": 29,
"decision": null
},
"readout": {
"value": 6.6,
"n": 218,
"maturing": 33,
"interval": [
0.4,
12.8
],
"readability": "C",
"basis": "held-back",
"pooled": false
},
"ledger": {
"spend": 3,
"workerCost": 13,
"hours": 6.8,
"pipelineArr": 4800,
"wonArr": 4375,
"projectedArr": 1168,
"paybackDays": 1
}
}Outside benchmark: the record’s reactivation-demo-requests-unanswered range, from a vendor-run field test of business demo requests, finds most go unanswered, which is the pool own-the-miss works.
Before it · After it. Before it: lead-nurture hands its silent exits here after 60 days, and events-and-webinars its quiet registrants. After it: Interested replies go to the AE, and a lost Deal or a closed-lost reason goes back to its owner.
Example
Leaseline ran it from 2026-08-24 to 2026-10-19 (56 days) on 612 pooled records: 47 merged or released, 36 refused or invalid, 29 held back, and 251 Messages delivered against 229 matches. Read every captured thread at ?threads=; three follow the tables.
| What happened, agent half, by option | Decisions | Delivered | Deals or trials opened | Won (credited, not all caused by this Play) |
|---|---|---|---|---|
| Send the reason | 182 | 182 | 8 | 2 on Pro, 1 on Team |
| Own the miss · Founder writes it | 33 · 14 | 33 · 14 | 3 · 2 | 1 on Pro · 1 on the $2,400 plan (+$2,393 ARR) |
| To the AE · Not now · Let them go | 9 · 211 · 64 | — | 9 Meetings booked | — |
| Agent half · held back | 513 | 229 matched, 251 sent with rounds | 13 of 196 matured · 0 of 22 | $4,375 · $0 |
| At 120 a month (the Use it when volume) | about 260 a month | 120 | held back at random | Other Startups’ results: +5.1 per 100, ■■■□ Likely, n 14,600 |
| Day | Event | What happened | Your time |
|---|---|---|---|
| 0 | event_132 Campaign.created | the three steps; dry run fails collection-record and mailbox-warm | 30 min |
| 1 | event_150 Contact.imported | the 300 spreadsheet rows imported again with their collecting Forms; 40 refused | 20 min |
| 2 | event_163 Meeting.booked | the first Meeting from a reactivated Lead, a founder note to a past Meeting | — |
| 9 | event_301 Role.staffed | AE · Priya Shah (person) takes role: | — |
| 16 | event_352 Role.updated | promoted: rounds send after a 2-minute hold, 46 of 50 drafts passed | — |
| 28 · 49 | event_540 Change.merged · event_870 Readout.created | the 0.8 match line: replayed at 0 of 40 wrong matches, read back at +21d at 1 of 37 | 5 min |
| 44 | event_745 Campaign.paused | unanswered-positive: an Interested reply waited 78 min; resumed when Priya claimed it | 5 min |
| 1–56 | event_140 Approval.created, first of 50 | you reviewed 50 drafts and wrote 14 founder notes, from the POST /{namespace}/tasks/next log | 4.6 h · total 6.8 h measured, 7 h asked |
Leaseline’s agent → Teo Marsh, Granite Row, Oct 2, 09:00 · On Mar 3 you asked whether Leaseline handles a second owner on one portfolio. Nobody answered you; that was our miss.¹ It does now: per-owner renewal batches shipped on Sep 28.² https://leaseline.app/changelog/per-owner Want it on in a trial with your 64 units?³ *Signed by Leaseline’s agent, named as an agent, with the why-you and stop lines.*⁴
Teo, Oct 2, 11:15 · “Yes, start the trial.”
POST /{namespace}/offers/{offer}/trialopened it, and Granite Row paid for Pro on Oct 14, +$347 ARR.
- I saw you were back on our pricing page this week, so I wanted to reach out.
+ On Mar 3 you asked whether Leaseline handles a second owner on one portfolio. Nobody answered you; that was our miss. Brookside Flats, 2026-10-06 14:10 · replied to the Team price round: “Yes, the 60-unit price works. Can we talk Thursday?” The agent chose To the AE, Brief attached, due 15:10. · Priya, 14:31 · claimed it with
POST /{namespace}/tasks/{task}/claimand booked Thursday 10:00; the Deal opened Oct 9. The Brief is in When it hands to you (task_486).
¹ What he did with Leaseline, with its date, and the miss owned in one clause. ² A fresh reason with its public source, the changelog. ³ One ask, at Behavior grade. ⁴ The fixed lines, compiled from the Role’s Settings.
Your settings
---
holdout: 0.1 # 1 in 10 newly eligible records per List gets no Message for 60 days
---
Match by reason code first, then by model at 0.8 or more; park the rest.
People who had a call with us get a note I write myself.$ npx startup-builder show plays/lead-reactivation
holdout 0.1 · match line 0.8 · founder notes for first tier your file plays/lead-reactivation.mdx:2,4,5 (Ask 2026-08-24; line 4 by change_31, 2026-09-21)
family demand · worker role:prospector, owns never · chooses 6 (4 rule, 2 read) · sequences reactivation-round, reactivation-round-founder, reactivation-tray-founder · checks owned-by-a-person, customer-collision, approved-pages, watched-event, verification, market-hold · stops 11 · placements gmail, outlook, yahoo-mail, linkedin catalog play:lead-reactivation@1 (Not live yet, gaps #5) Every key from the catalog
Compiled, not written: edit the Play record lead-reactivation and its Leaseline run, then regenerate. Every step runs from a sentence or a line of code. In your words: the CRM words you know, and where each lives here.