Link pitches
Forty pages that link to your rivals and not to you, each pitched by email with an asset that fits the page, and one follow-up.
- Kind
- Agent task
- Price
- 46¢ per 40 pitches
- Your time
- 20 minutes
- Approval
- Runs on its own
- Deepens
- Content marketing
Pitch the 40 pages that link to my competitors but not to me, spend up to $2, and show me the plan first.
curl -X POST "https: //api.sb/ leaseline.app/ domains/ pitch? mode= try" \
-H "Authorization: Bearer $SB_TOKEN" \
-H "Content-Type: application/ json" \
-d '{"goal": "Pitch the 40 pages that link to my competitors but not to me, spend up to $2, and show me the plan first.", "budget": {"amount": 2, "currency": "USD"}}'
npx startup-builder do Domain.pitch --try --data '{"goal": "Pitch the 40 pages that link to my competitors but not to me, spend up to $2, and show me the plan first.", "budget": {"amount": 2, "currency": "USD"}}'
{
"name": "do",
"arguments": {
"mode": "try",
"code": "return await $.Domain.pitch({ goal: 'Pitch the 40 pages that link to my competitors but not to me, spend up to $2, and show me the plan first.', budget: { amount: 2, currency: 'USD' } })"
}
}
import { $ } from 'startup-builder'
const result = await $.try.Domain.pitch({ goal: 'Pitch the 40 pages that link to my competitors but not to me, spend up to $2, and show me the plan first.', budget: { amount: 2, currency: 'USD' } })
{
"mode": "try",
"stored": false,
"estimate": "About 46¢ per 40 pitches",
"sample": {
"page": "smalllandlord.example/ resources",
"authority": "38",
"asset": "the free rent ledger template",
"state": "live",
"found on": "day 12"
}
}
What you get
- 40 pitches · Forty pages that link to your rivals and not to you, each pitched by email with an asset that fits the page, and one follow-up. · Domain
pitched· Pitches sent.held[]· pages not pitched and why: no findable editor, a paid-placements-only page, or a closed thread.links[]· Each new link found on a pitched page:liveorpending, read weekly by Listing check.
Connect once: Gmail. How long: Minutes to send; links arrive over weeks.
Trigger
It runs monthly, from the latest Competitor gap and AI answer audit.
every.month(($) => $.Domain.pitch({ hostname: 'leaseline.app', from: 'gap', take: 40 }))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 linking page | Page.audit | What the page lists and who writes it; paid-placement pages drop | About 0.8¢ | Runs on its own |
| 2. Find the editor or author | Prospect.find | The person who keeps each page, names and titles free | Free | Runs on its own |
| 3. Find each work email | ContactPoint.findEmail | An address for most editors; a miss is free | About 18¢ | Runs on its own |
| 4. Check each email | ContactPoint.verifyEmail | valid, catch-all, invalid or unknown | About 7.1¢ | Runs on its own |
| 5. Pick the asset and write the pitch | Content.draft | One pitch per page: the exact page, why the asset fits its readers, and one placement asked for | About 16¢ | Runs on its own |
| 6. Send the pitch and one follow-up | Message.schedule | Pitches sent from the warmed Mailbox, the follow-up 5 days later to silence | About 4¢ | Runs on its own |
Calls: Page.audit, then Prospect.find, then ContactPoint.findEmail, then ContactPoint.verifyEmail, then Content.draft, then Message.schedule.
About 46¢ per 40 pitches, each time it runs, capped at $1.00.
Example: give it a goal
Pitch the 40 pages that link to my competitors but not to me, spend up to $2, and show me the plan first.
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 page has no findable editor | It is held with no-contact, and the find miss costs nothing. |
| A pitch fails the grade | It is rewritten once; a second failure holds the page and that pitch is not billed. |
| An address checks invalid or catch-all | The pitch is held; a catch-all address can be resolved with a deep check for about 0.4¢. |
| You call it again with the same asset | No editor is pitched twice for the same asset: it is idempotent per page and asset. |
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-domain-pitch-1-<step>-<item>.
How it is graded
A second model reads each pitch beside the target page, the asset and your offer. It passes when the pitch names the exact page and why the asset serves its readers, the asset exists, no claim passes the offer, and it asks for one placement. A miss is redone at no charge.
Change it
| Ask | Compiles to |
|---|---|
| Only pitch pages with authority over 30. | Domain.pitch |
| Build a free tool to pitch first: a rent ledger template. | Page.answer |
| Check every week whether the new links are live. | Post.check |
import { $, workflow } from 'startup-builder'
export default workflow({
name: 'domain-pitch',
verb: 'Domain.pitch',
input: { hostname: 'string', from: "'gap' | 'cited' | string[]", take: 'integer' },
maxCost: 1,
grade: { evaluator: 'target page, asset, Offer' , passWhen: 'names the page; the asset exists; no claim past the Offer; one ask' , remedy: 'redo' },
execute: async (ctx, { hostname, from = 'gap', take = 40 }) => {
const pages = await ctx.step('pages', () => $.Page.audit({ urls: targetsFrom(hostname, from, take), fields: ['content', 'author'], maxCost: 0.03 }))
const editors = await ctx.step('editors', () => $.Prospect.find({ pages: pages.map((p) => p.url), roles: ['editor', 'author'] }))
const reach = []
for (const e of editors.prospects) reach.push(await ctx.step(`verify: ${e.$id}`, () => $.ContactPoint.findEmail({ prospect: e.$id, check: true, maxCost: 0.02 })))
for (const r of reach.filter((x) => x.reach.status === 'valid')) {
const pitch = await ctx.step(`pitch: ${r.$id}`, () => $.Content.draft({ kind: 'link-pitch', to: r, page: r.page, assets: 'own', maxCost: 0.01 }))
await ctx.step(`send: ${r.$id}`, () => $.Message.schedule({ to: r.$id, body: pitch.body, followUp: { after: '5d', ifNoReply: true } }))
}
},
})Extend it with extends: and base(), or compose your own from these steps.
Works with
Plays that order it: Data stories, Guest on podcasts and newsletters.
It serves Earn first links, and runs in Launch and search. It deepens Content marketing.
For agents
For agents: the guide, its approvals, retries and lookups
| Fact | Value |
|---|---|
| Guide | Domain.pitch |
| Guide approval | limits |
| Goal | Pitch the 40 pages that link to my competitors but not to me, spend up to $2, and show me the plan first. 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 | every.month(($) => $.Domain.pitch({ hostname: |
| Retry | Three times with backoff, then the next route; the same idempotency key never charges, sends or posts twice |
| Idempotency key | leaseline-domain-pitch-1 |
| Lookups | A find: input asks unless exactly one record fits |
| Step |
|---|
| 1. Read each linking page |
| 2. Find the editor or author |
| 3. Find each work email |
| 4. Check each email |
| 5. Pick the asset and write the pitch |
| 6. Send the pitch and one follow-up |
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 linking page | code | Code | Code checks each result against its declared type | measured | $0.from, |
| 2. Find the editor or author | code | Code | Code checks each result against its declared type | measured | $1.pages |
| 3. Find each work email | code | Code | Code checks each result against its declared type | measured | $2.prospects |
| 4. Check each email | code | Code | Code checks each result against its declared type | measured | $3.addresses |
| 5. Pick the asset and write the pitch | agentic | The Marketing lead agent | Adversarial review by a second model: Pass, or held: the asset must exist and no claim may pass the offer | graded | $1.pages, |
| 6. Send the pitch and one follow-up | code | Code | Code reads the result back from where it landed | measured | $5.passed, |
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
Reference
Inputs, outputs, its Event and its errors
| Name | Type | Default | Meaning |
|---|---|---|---|
hostname | Hostname | Required | Your site. |
from | 'gap' | 'cited' | Url[] | 'gap' | Pages from Competitor gap, the domains AI answers cite, or your own list. |
take | integer | 40 | Pages to pitch, highest authority first. |
assets | Url[] | your data pages, | What you offer each editor. A pitch with nothing to link to is held. |
mailbox | Mailbox | your warmed sending domain | Where pitches send from; never your main inbox. |
maxCost | number | 1.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 Domain.pitched. Refuses with max-cost before it spends, and with the Data calls and Accounts groups (refusals).