URL: /plays/family-opt-in · Id: `play:family-opt-in`

# Family opt-in

> Family opt-in shows one unticked box on your other Startup’s sign-up, asking for your updates, and records each yes as its own Consent.

Family opt-in shows one unticked box on your other [Startup](https://startupbuilder.dev/business/nouns/startup)’s sign-up, asking for your updates, and records each yes as its own [Consent](https://startupbuilder.dev/business/nouns/consent).

```text title="Set it up · Ask"
Be our Marketing Lead across my two Startups: put one unticked Leaseline box on my maintenance-request app’s sign-up, welcome every yes from Leaseline within a minute, and leave 1 in 10 with no box so we can tell. Show me what’s missing; don’t send.
```

```text title="Run one · Ask"
Hand our latest family yes to the Marketing Lead agent and show me what it chose and why. Don’t send.
```

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

const { events: [last] } = await $.Event.list({ where: 'name in ("Form.opened", "Contact.subscribed", "Consent.withdrawn", "Offer.published", "Brand.lapsed", "Portfolio.changed")', 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 }
```

```json title="Run one · Response · sample"
{
  "decision": {
    "$id": "https://api.sb/leaseline.app/decisions/decision_495",
    "$type": "Decision",
    "choice": "welcome-them",
    "reason": "ticked the Leaseline box on your other Startup’s sign-up at 10:04, address verified, and no Suppression covers Leaseline",
    "stakes": "low",
    "options": [
      "show-the-box",
      "ask-after-sign-up",
      "welcome-them",
      "hold-for-a-confirm",
      "send-the-launch",
      "to-the-ae",
      "not-now"
    ],
    "by": "worker_6",
    "on": "contact_77",
    "expected": "the welcome delivered within 1 minute, and a Leaseline trial within 90d",
    "reopensWhen": "Consent.withdrawn for contact_77, or no Message.delivered within 24h"
  },
  "actions": [
    {
      "$id": "https://api.sb/leaseline.app/actions/action_1",
      "$type": "Action",
      "status": "done",
      "mode": "try",
      "by": "worker_6",
      "verb": "Suppression.list",
      "checks": []
    },
    {
      "$id": "https://api.sb/leaseline.app/actions/action_2",
      "$type": "Action",
      "status": "done",
      "mode": "try",
      "by": "worker_6",
      "verb": "Consent.create",
      "checks": [
        {
          "check": "consent-stop",
          "status": "pass"
        }
      ]
    },
    {
      "$id": "https://api.sb/leaseline.app/actions/action_3",
      "$type": "Action",
      "status": "done",
      "mode": "try",
      "by": "worker_6",
      "verb": "Contact.create",
      "checks": []
    },
    {
      "$id": "https://api.sb/leaseline.app/actions/action_4",
      "$type": "Action",
      "status": "pending",
      "reason": "awaitingTime",
      "mode": "try",
      "by": "worker_6",
      "verb": "Message.schedule",
      "checks": [
        {
          "check": "owned-by-a-person",
          "status": "pass"
        },
        {
          "check": "consent-stop",
          "status": "pass"
        },
        {
          "check": "claims-resolve",
          "status": "pass"
        }
      ]
    }
  ]
}
```

`action_2` records one Consent naming Leaseline, stamped with the host, the Page, the words shown and the time; `action_3` creates the Contact with the address, a first name and the region, and nothing else. `action_4` is the welcome, sent after a 30-second hold.

`+23 yeses per 100 sign-ups · ■■■□ Likely · n 260 · path → arr`

| Try another: the yes | Choice | Reason |
| --- | --- | --- |
| Mia Chen ticked the Leaseline box on your other Startup’s sign-up | `welcome-them` | a verified yes naming Leaseline; no Suppression |
| A sign-up whose address unsubscribed from Leaseline in June, now ticks the box | `hold-for-a-confirm` | a standing Suppression: one confirm request, nothing else |
| A welcome reply: “We have 200 units, what’s the price?” | `to-the-ae` | asks to buy, over 100 units |
| A sign-up already on Leaseline Pro | `not-now` | already buys from Leaseline: no box renders |
| Leaseline opens its owner-portal add-on on Nov 2 | `send-the-launch` | 61 family subscribers said yes to Leaseline; you approve the launch |

| Worth | Your time | Spend | First signal | Use it when | Instead |
| --- | --- | --- | --- | --- | --- |
| ≈ +$380 ARR a month at 120 sign-ups | 40 min week 1, then 5 a week | $0.01 per welcome, ~$1 a month at 30 yeses | Day 1: first yes and its welcome | 2+ Startups with overlapping buyers, 100+ sign-ups a month at the host | One Startup: `customer-referrals` · under 100 sign-ups: `owned-newsletter` |

**Where it sits:** **Stranger** → Lead → Trialing → Activated → At risk → Churned → Won back · Family opt-in rate → Sent by your other Startups → New Customers ↑ → ARR added each week → ARR

Worth’s formula: about +23 yeses per 100 sign-ups (your held-back read) × 120 sign-ups a month at your other Startup × 4 paid per 100 yeses × $347 first order ARR ≈ +$383. Under First signal: The yes rate reads within 14 days at 120 sign-ups a month; Customers from the yeses take about 90 days, so `new-arr` reads Too early to tell until January.

`sample · api.sb/leaseline.app · 41 Customers at 2026-10-19 · on Contact.subscribed · first welcome 38s · 8 keys Not live yet`

## Start it this week
```bash
npx startup-builder add play family-opt-in   # 1 · writes plays/family-opt-in.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":"family-opt-in"}'

# 3 · keep it, at You approve the first wave
curl -X POST https://api.sb/leaseline.app/campaigns \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"play":"family-opt-in"}'

# on
curl -X PATCH https://api.sb/leaseline.app/campaigns/family-opt-in \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"endsAt":null}'

# off: the box comes off the host within 1 hour; every recorded yes stands
curl -X PATCH https://api.sb/leaseline.app/campaigns/family-opt-in \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"endsAt":"now"}'
```


```json title="The dry run’s 422 · 2026-08-24 · sample"
{
  "type": "https://startupbuilder.dev/errors/refused",
  "title": "Refused",
  "status": 422,
  "detail": "family-opt-in can’t start yet: 1 check fails and 2 warn.",
  "checks": [
    {
      "check": "use-when",
      "status": "pass",
      "observed": "2 Startups, buyer overlap 0.72; 141 sign-ups in 30 days at the host",
      "doc": "/plays/family-opt-in#check-use-when",
      "fix": "None needed: 100 sign-ups a month is enough.",
      "then": "Metric.query"
    },
    {
      "check": "baseline",
      "status": "warn",
      "observed": "0 yeses: no box has run",
      "doc": "/plays/family-opt-in#check-baseline",
      "fix": "The 1 in 10 shown no box is the comparison from day 1.",
      "then": "Metric.query"
    },
    {
      "check": "approved-pages",
      "status": "fail",
      "doc": "/plays/family-opt-in#check-approved-pages",
      "fix": "Approve the box line and the welcome; each says only what your Page says.",
      "then": "Approval.approve"
    },
    {
      "check": "role-unstaffed",
      "status": "warn",
      "roles": [
        "marketing-lead",
        "sales-lead"
      ],
      "owned": 31,
      "doc": "/plays/family-opt-in#check-role-unstaffed",
      "fix": "Staff the Marketing Lead, or leave it: buying replies come to you.",
      "then": "Role.staff"
    }
  ]
}
```

| Check | Fix (one call) | Your time | Wait |
| --- | --- | --- | --- |
| <a id="check-use-when"></a>`use-when` · <a id="check-baseline"></a>`baseline` · pass: 2 Startups, overlap 0.72, 141 sign-ups a month; warn: no box has run (gaps #8) | <Op verb="Metric.query" /> on `family-opt-in-rate`; the no-box tenth fills Before | 1 min | none |
| <a id="check-approved-pages"></a>`approved-pages` · fail (Not live yet) | <Op verb="Approval.approve" /> the box line and the welcome | 10 min | none |
| <a id="check-role-unstaffed"></a>`role-unstaffed` · warn (gaps #8): 31 of your open Leads have a person owner, you | <Op verb="Role.staff" />, or leave it: buying replies come to you | 0 min | none |

`Your number · Before 0 · Now 23 yeses per 100 sign-ups · Target 20 by Dec 31 · ■■■□ Likely · held back at random · n 260`

Each welcome and discovery note costs about $0.01 from Leaseline’s broadcast Mailbox (Worker cost); the box and the Consent cost nothing. That comes to about $1 a month at 30 yeses.

## When it starts
| When | Trigger id | Starts on | Window | How it gets in |
| --- | --- | --- | --- | --- |
| Someone ticks the Leaseline box on your other Startup’s sign-up | `family-yes` | `event` on `Contact.subscribed` where consent.recipient = receiver | welcome within 1 min; 1d | the host Form posts to <Op verb="Contact.subscribe" /> |
| Someone unsubscribes or withdraws anywhere in your Startups | `family-withdrawal` | `event` on `Consent.withdrawn` where any Consent this Play recorded, at any scope | binds every Startup within 1 h | <Op verb="Consent.withdraw" />, from any footer |
| + 4 more for ask points, launches, a silent receiver and changes to your Startups (`?lanes=all`) | | | | |

```json title="One signed delivery · StartupBuilder-Signature"
{
  "$id": "https://api.sb/leaseline.app/events/event_1702",
  "$type": "Event",
  "name": "Contact.subscribed",
  "at": "2026-10-19T10:04:51Z",
  "seq": 1702,
  "data": {
    "contact": "contact_77",
    "host": "startup_2",
    "form": "form_3",
    "receiver": "leaseline.app",
    "words": "Leaseline: lease renewal software, same team as this app. Monthly updates.",
    "region": "us"
  }
}
```

```bash title="Reproduce it in test"
npx startup-builder trigger Contact.subscribed --like event_1702   # replays the delivery
npx startup-builder clock advance --by 3d   # then sends the discovery note, unless they replied
npx startup-builder listen Contact.subscribed --forward-to http://localhost:3000/hook
```

## Who runs it
**Marketing Lead · your agent** (`role:marketing-lead`, `worker_6`) holds the Key Result for both Startups. Buying replies go to **AE · Priya Shah (person)** (`role:sales-lead`, `worker_3`), and each launch to your family subscribers comes to **you** (Dana Okafor, person, `worker_1`). Your number links to Did it work.

| Option | Rung today | Evidence (passed of n) | Moves up at | Drops back when | You can |
| --- | --- | --- | --- | --- | --- |
| Show the box · Ask after sign-up · Not now | It does it | 2,140 renders, 0 render checks failed | — | one render check fails → every box on that host comes off | <Op verb="Form.update" /> |
| Welcome them · Hold for a confirm | It does it, you can stop it | 54 of 54 · 3 of 3 | met on day 6 (the first wave of 10) | welcome unsubscribes over 8 % → the box comes off | <Op verb="Action.cancel" /> inside the hold |
| Send the launch | You approve each one | 0 in the window | never | — | <Op verb="Approval.approve" /> · <Op verb="Approval.refuse" /> |
| To the AE | It does it | 2 of 2 | — | a reply waits past 60 min → You approve each one | <Op verb="Action.redirect" /> |
| Change the box rule | You approve each one | 0 merged | never | — | <Op verb="Change.merge" /> · <Op verb="Change.revert" /> |
| Never: a ticked or required box, a box on a payment step, anything they did at the host in a Message | To a person, every time | 0 asked | never | — | <Op verb="Task.claim" /> |

**What it reads · What it may call.** It reads the yes itself, the address, a first name and the region. It never passes the host’s plan, Form answers, Orders, Visits or usage to Leaseline, and no Message mentions anything they did at the host. Its tools are <Op verb="Form.update" />, <Op verb="Page.update" />, <Op verb="Consent.create" />, <Op verb="Message.schedule" />, <Op verb="Approval.create" />, <Op verb="Conversation.escalate" />, <Op verb="Task.assign" />, and any other call becomes an Approval under the Role’s Mandate.

```md title="The brief (folded): compiled Agent-register Markdown · play:family-opt-in@1 · each line names its record key"
You hold the Marketing Lead Key Result: 20 yeses per 100 sign-ups by 2026-12-31. ← worker, metric
On each ask point, yes or withdrawal, choose one option and say why.             ← triggers, chooses
Render each box unticked, never required, naming Leaseline, same team and its cadence. ← copyRules 1, 2
Record one Consent per yes, naming Leaseline as recipient.                       ← your file, line 4
The welcome’s first line names the host and the date of the yes.                 ← copyRules 5
Show no box to 1 Visitor in 10, held for 90 days.                                ← your file, line 2
```

**The slow loop.** The weekly Decisions kept the box rule as written, and no Change was merged in the window, so there is no replay or read-back to print.

**Bring your own agent** (folded) starts at It does it, you can stop it on welcomes when it matches or beats the seated agent on the eval suite; renders stay rule-bound either way; <Op verb="Role.staff" /> seats it.

```bash
npx startup-builder eval --scenario family-opt-in-260 --agent "claude -p" --seat marketing-lead   # seated: 23 yeses per 100, 2,140 of 2,140 renders passed, welcome in 38 s · yours: 23, 2,139 passed, 1 failed `render-check`
```

## What it can do next
| Option | Picked when | Decided by | Runs | Hands to | If it’s just you |
| --- | --- | --- | --- | --- | --- |
| Show the box `show-the-box` | a host sign-up Form, buyer overlap 0.5 or more | Your rule (line 4) | <Op verb="Form.update" /> with one unticked box, below every host field | nobody | the same |
| Ask after sign-up `ask-after-sign-up` | a smoke-test Page, or host completion fell 2 points | Your rule (the completion Stop) | <Op verb="Page.update" />: two equal buttons after the act completes | nobody | the same |
| Welcome them `welcome-them` | a yes with a verified address | Your rule (line 4) | <Op verb="Consent.create" /> → `family-welcome` from Leaseline | nobody | the same |
| Hold for a confirm `hold-for-a-confirm` | a standing Suppression, or a confirmed-yes region | Agent’s call, with its reason | <Op verb="Message.schedule" /> one confirm request, nothing else | nobody | the same |
| Send the launch `send-the-launch` | you open an Offer, to Persons whose yes names you | Agent’s call, with its reason | `launch-sequence` → <Op verb="Approval.create" /> | you approve it, due 1 business day · Take this one | you |
| To the AE `to-the-ae` | a welcome reply asks to buy, or a price for 100+ units | Agent’s call, with its reason | <Op verb="Conversation.escalate" /> → <Op verb="Task.assign" /> | AE · Priya Shah, due 60 min · Take this one | you, within 60 min |
| Not now `not-now` | already subscribes or buys, declined twice in 180 days, or kept apart | Your rule (the exclusion) | nothing; no box renders for them | nobody | the same |

| When they write back | Fixture reply | What happens | Who answers | By when | Call |
| --- | --- | --- | --- | --- | --- |
| Interested | “We’d like to try it for our 200 units.” | Priya offers times; the Brief goes with it (When it hands to you) | AE · Priya Shah (person) | 60 min | <Op verb="Meeting.book" /> |
| Send me pricing | “What does Leaseline cost?” | answers with the Pricing Page’s list Prices, $29 and $79 a month | Marketing Lead · your agent | 5 min | <Op verb="Conversation.answer" /> |
| Not now | “Just the updates for now.” | the discovery note is skipped; the monthly update keeps coming | nobody | at once | <Op verb="Enrollment.cancel" /> |
| Wrong person | “My business partner handles software.” | asks them to forward the welcome; it never writes to an address from their text | Marketing Lead · your agent | 1 business day | <Op verb="Conversation.answer" /> |
| Out of office | an auto-reply until Oct 28 | the discovery note waits for the return date | nobody | the return date | <Op verb="Message.schedule" /> |
| Question it can’t answer | “Can one login cover both your apps?” | To a person: no record answers it yet, so it says nothing of it | you | 1 business day | <Op verb="Conversation.escalate" /> |
| Stop | “Unsubscribe.” | withdrawn and suppressed for all your Startups within the hour | nobody | at once | <Op verb="Consent.withdraw" /> |

## When it hands to you
Buying replies go to **AE · Priya Shah (person)**; each launch to your family subscribers comes to **you**. One Brief per Role, as captured (`brief` is Not live yet, gaps #7).

```json title="What lands in each queue · sample"
[
  {
    "$id": "https://api.sb/leaseline.app/tasks/task_622",
    "$type": "Task",
    "assignee": "worker_3",
    "due": "2026-10-02T15:31:00Z",
    "on": "contact_71",
    "brief": {
      "who": "Sam Ortiz, 200 units",
      "said": "We’d like to try it for our 200 units.",
      "chose": "to-the-ae",
      "why": "asks to buy, over 100 units",
      "yes": "ticked the Leaseline box on your other Startup’s sign-up, 2026-09-28",
      "never": [
        "anything they did at the other Startup",
        "a discount"
      ]
    }
  },
  {
    "$id": "https://api.sb/leaseline.app/approvals/approval_74",
    "$type": "Approval",
    "verdict": null,
    "deadline": "2026-10-27T17:00:00Z",
    "draft": "Leaseline’s owner portal opens Nov 2: owners see every renewal in one place. You said yes to Leaseline updates on Sep 3 on our maintenance app.",
    "route": "send-the-launch",
    "checks": [
      {
        "check": "consent-stop",
        "status": "pass"
      },
      {
        "check": "claims-resolve",
        "status": "pass"
      }
    ]
  }
]
```

Priya and you answer with <Op verb="Task.claim" /> (Take this one), <Op verb="Task.return" /> with a reason, <Op verb="Approval.approve" /> or <Op verb="Approval.refuse" />, each a one-call window; an Approval unanswered by its deadline expires and nothing sends. Once a person claims, the agent’s pending notes to that Person cancel with `claimedByPerson` (Not live yet, gaps #7). A Task nobody claims by its due time goes up through <Op verb="Task.escalate" />, and `person-promise` fires.

**On time:** AE · Priya Shah, 2 of 2 claimed within 60 min, median 26 min, n 2, ■□□□ Too early to tell beside Other Startups’ 95 % on time. **Your queue:** counted by kind: in week 1, 1 approval for the box line and welcome; since then about 1 hand-off a month and 1 launch approval.

**The fixed line:** <Op verb="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 |
| --- | --- | --- | --- |
| <a id="check-owned-by-a-person"></a>`owned-by-a-person` (rule j; Not live yet, gaps #8) | a welcome or note to someone whose Lead or open Deal a person owns | Leave it to the person who owns it: the draft waits in their queue. | <Op verb="Task.assign" /> |
| <a id="check-consent-stop"></a>`consent-stop` (gaps #3) | a send to someone whose yes names your other Startup, not this one | Ask once for a yes to this Startup. | <Op verb="Message.schedule" /> |
| <a id="check-render-check"></a>`render-check` (the record’s Render check) | a box that renders ticked, required, bundled, or without the name, same team or cadence | Fix the box; every box on that host stays off until it passes. | <Op verb="Form.update" /> |

| Stop (pauses the Play) | Pauses when | Then |
| --- | --- | --- |
| <a id="stop-consent-audit"></a>`consent-audit` | a Leaseline Contact from this Play with no Consent naming Leaseline | every Tactic pauses |
| <a id="stop-host-completion"></a>`host-completion` | host completion in the boxed half 2 points under the no-box tenth, over 500 opens | that host moves to the after-sign-up ask only |
| <a id="stop-welcome-unsubscribe"></a>`welcome-unsubscribe` | unsubscribes over 8 % of welcomes, over 100 | the box comes off every host |
| <a id="stop-complaint"></a>`complaint` | complaints near 0.1 % of family sends | family sends and boxes pause |
| <a id="stop-welcome-lag"></a>`welcome-lag` | a yes with no welcome or confirm request inside 24 h | the box comes off until the backlog clears |
| <a id="stop-silence"></a>`silence` | no family update outside the welcome for 45 days | the box comes off until one sends |
| <a id="stop-pool"></a>`pool` | a Startup leaves the pool, closes or turns cross-sell off | its boxes come off within 1 h |
| <a id="stop-suppression-audit"></a>`suppression-audit` | any send to an address suppressed over 1 h | every family send pauses on every Startup |
| <a id="stop-kill-switch"></a>`kill-switch` | you press it | every Tactic pauses on every host; recorded yeses stand |

The captured refusal, then the Stop that fired as a `Campaign.paused` reason (Not live yet, gaps #2); the box that rendered after the fix is the second thread in Example.

```json title="The captured refusal · sample"
{
  "type": "https://startupbuilder.dev/errors/refused",
  "title": "Refused",
  "status": 422,
  "detail": "The welcome to contact_63 did not send: their yes names your maintenance-request Startup, not Leaseline.",
  "checks": [
    {
      "check": "consent-stop",
      "status": "fail",
      "doc": "/plays/family-opt-in#check-consent-stop",
      "fix": "Ask once for a yes to this Startup.",
      "then": "Message.schedule"
    },
    {
      "check": "owned-by-a-person",
      "status": "pass"
    }
  ]
}
```


```json title="The Stops that fired, as Campaign.paused reasons · sample"
[
  {
    "reason": {
      "limit": "welcome-lag",
      "observed": "7 yeses with no welcome at 24 h",
      "bound": "0",
      "window": "24h"
    },
    "doc": "/plays/family-opt-in#stop-welcome-lag",
    "task": "task_617"
  }
]
```

## Did it work
Each Visitor at the host was assigned at random, 9 in 10 to the box and 1 in 10 to no ask, so both halves read the same assignment (rules m–o); each figure’s call is <Op verb="Metric.query" /> by node and by arm, and every node above the leaf is Not live yet (gaps #4).

`+23 yeses per 100 sign-ups · ■■■□ Likely · n 260 · path → arr`

| Node | Shown the box (234) | No ask (26) | Added | Confidence | Basis |
| --- | --- | --- | --- | --- | --- |
| `family-opt-in-rate` | 23 per 100 (54 of 234 sign-ups) | 0 (0 of 26) | +23 [+18, +28] | ■■■□ Likely | held back at random · n 260 · 31 inside their 14 days · minN 300 |
| `sibling-sourced-share` | new Leaseline Customers from boxed sign-ups: 0.9 per 100 (2 of 234) | 0 (0 of 26) | +0.9 [−0.4, +2.1] · Not carrying up yet | ■□□□ Too early to tell | the same assignment · Other Startups’ results +1.4 per 100, ■■□□ Early sign, n 18,300 sign-ups |
| `new-arr` ↑ | $694, 2 Customers on Pro, kind family-opt-in from your other Startup · Booked: credited, not all caused by this Play | $0 | +$694 [−$350, +$1,800] · Worth about +$380 a month | ■□□□ Too early to tell (2 vs 0 Customers) | the same · 6 trials still deciding |
| `net-new-arr` · `arr` | +$694 | $0 | +$694, carried: it moves no other bridge term | ■□□□ Too early to tell | carried from `new-arr` |

The leaf counts yeses per 100 completed sign-ups at the host’s ask point, over 14 days, to 300; at 260 the Experiment runs on. The parent reads Leaseline Customers per 100 host sign-ups in each arm, 90 days out.

**How it moves it:** one honest, unticked box on the host’s sign-up, and a welcome from Leaseline 38 seconds after the yes, naming the host and the date.

**Guardrails:** boxed against no ask: host sign-up completion, 71 % against 73 %, inside the 2-point Stop, and welcome unsubscribes, 3.7 % of 54 against the 8 % floor; 6 trials are still deciding.

```json title="The Campaign card · sample"
{
  "$id": "https://api.sb/leaseline.app/campaigns/family-opt-in",
  "$type": "Campaign",
  "play": "play:family-opt-in@1",
  "path": [
    "family-opt-in-rate",
    "sibling-sourced-share",
    "new-arr",
    "net-new-arr",
    "arr"
  ],
  "experiment": {
    "metric": "family-opt-in-rate",
    "unit": "per-100",
    "heldBack": 26,
    "decision": null
  },
  "readout": {
    "value": 23,
    "n": 260,
    "maturing": 31,
    "interval": [
      18,
      28
    ],
    "readability": "B",
    "basis": "held-back",
    "pooled": false
  },
  "ledger": {
    "spend": 0,
    "workerCost": 1,
    "hours": 1.1,
    "openArr": 2082,
    "wonArr": 694,
    "projectedArr": 383,
    "paybackDays": 1
  }
}
```

Outside benchmark: no measured range for a same-team box survived the research’s check, so Other Startups’ results (n 18,300 host sign-ups) stand in.

**Before it · After it.** Before it: your other Startup’s own Plays bring the sign-ups that see the box. After it: `owned-newsletter` hears every yes as a new subscriber, and `instant-response` answers the ones who write in.

## Example
Leaseline ran it from 2026-08-24 to 2026-10-19 (56 days) on your other Startup’s sign-up: 2,140 Form opens, 260 completed sign-ups, 234 shown the box and 26 shown no ask. Read every captured thread at `?threads=all`; three follow the tables.

| What happened, by option | Decisions | Done | After |
| --- | --- | --- | --- |
| Show the box · Not now | 2,140 renders · 118 | 0 render checks failed · 118 already Leaseline Customers or subscribers | — |
| Welcome them · Hold for a confirm | 54 · 3 | 54 welcomes, median 38 s · 3 confirm requests, 1 pressed | 2 unsubscribed; 8 trials |
| To the AE · Send the launch | 2 · 0 | 2 hand-offs · the Nov 2 launch awaits you | 1 Meeting, 1 Deal open |
| **Shown the box** · no ask | **234** · 26 | **54 yeses** · 0 | **2 Customers ($694)** · 0 |

| Day | Event | What happened | Your time |
| --- | --- | --- | --- |
| 0 | `event_1660` Campaign.created | the three steps; the dry run fails `approved-pages` | 20 min |
| 0 | `event_1662` Approval.approved | you approved the box line and the welcome | 10 min |
| 1 | `event_1665` Contact.subscribed | the first yes; the welcome landed in 41 s | — |
| 6 | `event_1671` Role.updated | promoted: welcomes send after a 30-second hold | — |
| 23 | `event_1684` Campaign.paused | `welcome-lag`: a Mailbox token expired and 7 yeses waited; reconnected in 2 h, all 7 welcomed | 15 min |
| 39 | `event_1693` Offer.subscribed | the first Leaseline Customer from a family yes, on Pro | — |
| 1–56 | `event_1663` <Op verb="Task.next" />, weekly | 2 hand-offs and the weekly review, from the <Op verb="Task.next" /> log | 25 min · **total 1.1 h measured, 1 h asked** |

> **Leaseline → Mia Chen, Sep 3, 09:12:40** · You said yes to Leaseline updates on our maintenance-request app at 09:12 today.¹ Leaseline sends every renewal notice 60 days out, per owner; updates come monthly.² *Signed with the Role’s disclosure line.* Footer: where and when you said yes, and one-click unsubscribe.³
>
> **Mia, Sep 6** · replied to the discovery note: “Renewals are our biggest headache.” She started a trial on Sep 9 and moved to Pro on Oct 4.

```diff title="The box line, 2026-08-24 · the draft render-check refused, then the box that rendered"
- ☑ Get Leaseline updates (recommended)
+ ☐ Leaseline: lease renewal software, same team as this app. Monthly updates.
```

> **Sam Ortiz, 2026-10-02 14:31** · “We’d like to try it for our 200 units.” · **Leaseline, 14:33** · *Thanks, Sam. Priya, our account lead, replies with times by 15:31.*⁴ The Brief went to Priya (`task_622`); she wrote at 14:57.

¹ The first line names the host and the date of the yes. ² What Leaseline makes, and its cadence. ³ Every footer says where and when they said yes. ⁴ The routed line, compiled from the Role’s Settings.

## Your settings
```mdx title="plays/family-opt-in.mdx"
---
holdout: 0.1    # 1 Visitor in 10 at the host sees no box, sticky 90 days
---
One unticked Leaseline box on the maintenance app’s sign-up, below every field. Welcome each yes from Leaseline; buying replies go to Priya.
```

```text
$ npx startup-builder show plays/family-opt-in
holdout 0.1 · 1 box · welcome from Leaseline · AE on buying replies     your file  plays/family-opt-in.mdx:2,4 (Ask 2026-08-24)
worker role:marketing-lead, owns never · chooses 7 (4 rule, 3 read) · sequences family-welcome, launch-sequence, launch-note, closing-note · approval first-send · checks owned-by-a-person, consent-stop, render-check · stops 9 · placements portfolio-network, gmail, outlook, yahoo-mail    catalog  play:family-opt-in@1 (Not live yet, gaps #5)
```

### Every key from the catalog
| Key | Id | What it is |
| --- | --- | --- |
| Tactic | <a id="tactic-family-pairing"></a>`family-pairing` | Family pairing picks up to six of your other Startups a week whose ICP overlaps a host’s and whose box line is approved, ranked by expected yes rate. Starts at You approve the first wave. |
| Tactic | <a id="tactic-family-ask"></a>`family-ask` | Family ask renders at most two unticked boxes on a host Form, each naming one of your other Startups, or one equal-button ask after the act completes. Starts at Sends nothing. |
| Tactic | <a id="tactic-family-consent"></a>`family-consent` | Family consent records one Consent per yes naming its recipient, and hands the receiver only the address, a first name, the region and the Consent. Starts at Sends nothing. |
| Tactic | <a id="tactic-family-welcome"></a>`family-welcome` | Family welcome sends a new family subscriber the receiver’s welcome inside a minute, naming the host and date of the yes, then one discovery note. Starts at You approve the first wave. |
| Tactic | <a id="tactic-launch-day"></a>`launch-day` | Launch day opens a receiver’s Offer to every family subscriber at once, beside its direct Waitlist, and asks hosts’ launch-list subscribers once each. Starts at You approve each one. |
| Tactic | <a id="tactic-family-withdrawal"></a>`family-withdrawal` | Family withdrawal makes one opt-out at any of your Startups stop all of them within the hour, and ends or narrows family Consents only as the Person chooses. Starts at Sends nothing. |
| Tactic | <a id="tactic-family-readout"></a>`family-readout` | Family readout reads opt-ins per 100 sign-ups, the host completion cost and receiver Customers against the no-ask arm and the stepped wedge. Starts at Sends nothing. |
| Tactic | <a id="tactic-sibling-exclusion"></a>`sibling-exclusion` | Sibling exclusion decides, before every sibling render, Message and Audience sync, whether a Person may see a receiver’s Offer, and logs the reason. Starts at Sends nothing. |
| Trigger | <a id="trigger-ask-point-open"></a>`ask-point-open` | Ask point open: `event` on `Form.opened`, window 1h. |
| Trigger | <a id="trigger-family-yes"></a>`family-yes` | Family yes: `event` on `Contact.subscribed`, window 1d. |
| Trigger | <a id="trigger-family-withdrawal"></a>`family-withdrawal` | Family withdrawal: `event` on `Consent.withdrawn`, window 1h. |
| Trigger | <a id="trigger-receiver-launch"></a>`receiver-launch` | Receiver launch: `event` on `Offer.published`, window 3d. |
| Trigger | <a id="trigger-receiver-silent"></a>`receiver-silent` | Receiver silent: `state` on `Brand.lapsed`, window 15d. |
| Trigger | <a id="trigger-pool-change"></a>`pool-change` | Pool change: `event` on `Portfolio.changed`, window 7d. |
| Trigger | <a id="trigger-sibling-offer-change"></a>`sibling-offer-change` | Sibling offer change: `event` on `Offer.published`, window 7d. |
| Sequence | <a id="sequence-family-welcome"></a>`family-welcome` | Family welcome: 2 steps; it ends on The Person unsubscribes.; The Person withdraws the Consent.; The Person is suppressed.; The discovery note is sent or skipped, and the receiver’s own cadence takes over.. |
| Sequence | <a id="sequence-launch-sequence"></a>`launch-sequence` | Launch sequence: 4 steps; it ends on The Person signs up.; The Person pays.; The Person replies.; The Person unsubscribes, withdraws or is suppressed.. |
| Sequence | <a id="sequence-launch-note"></a>`launch-note` | Launch note: 1 steps; it ends on The note is sent.; The Person is suppressed or unsubscribes before the send.. |
| Sequence | <a id="sequence-closing-note"></a>`closing-note` | Closing note: 1 steps; it ends on The note is sent.. |