Skip to content

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
Domain.pitch
You, to your agent

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' } })
Response
{
  "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"
  }
}
Try mode reads, stores nothing and spends nothingAPI referenceIts Nouns

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: live or pending, 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.

TypeScript
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.

StepVerbReturnsPriceApproval
1. Read each linking pagePage.auditWhat the page lists and who writes it; paid-placement pages dropAbout 0.8¢Runs on its own
2. Find the editor or authorProspect.findThe person who keeps each page, names and titles freeFreeRuns on its own
3. Find each work emailContactPoint.findEmailAn address for most editors; a miss is freeAbout 18¢Runs on its own
4. Check each emailContactPoint.verifyEmailvalid, catch-all, invalid or unknownAbout 7.1¢Runs on its own
5. Pick the asset and write the pitchContent.draftOne pitch per page: the exact page, why the asset fits its readers, and one placement asked forAbout 16¢Runs on its own
6. Send the pitch and one follow-upMessage.schedulePitches sent from the warmed Mailbox, the follow-up 5 days later to silenceAbout 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
WhenThen
A page has no findable editorIt is held with no-contact, and the find miss costs nothing.
A pitch fails the gradeIt is rewritten once; a second failure holds the page and that pitch is not billed.
An address checks invalid or catch-allThe 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 assetNo 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

AskCompiles 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
TypeScript
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: 'Domain.pitch' 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
FactValue
GuideDomain.pitch
Guide approvallimits
GoalPitch 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.
Triggerevery.month(($) => $.Domain.pitch({ hostname: 'leaseline.app', from: 'gap', take: 40 }))
RetryThree times with backoff, then the next route; the same idempotency key never charges, sends or posts twice
Idempotency keyleaseline-domain-pitch-1
LookupsA 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
StepFunctionDoerCheckerAssuranceInputs
1. Read each linking pagecodeCodeCode checks each result against its declared typemeasured$0.from, $0.take
2. Find the editor or authorcodeCodeCode checks each result against its declared typemeasured$1.pages
3. Find each work emailcodeCodeCode checks each result against its declared typemeasured$2.prospects
4. Check each emailcodeCodeCode checks each result against its declared typemeasured$3.addresses
5. Pick the asset and write the pitchagenticThe Marketing lead agentAdversarial review by a second model: Pass, or held: the asset must exist and no claim may pass the offergraded$1.pages, $0.assets
6. Send the pitch and one follow-upcodeCodeCode reads the result back from where it landedmeasured$5.passed, $0.mailbox

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
SystemsMailboxes, sites and content
NounsPage, Prospect, Contact point, Content, Action, Message
EventsContent.drafted, Action.evaluated, Message.scheduled

Reference

Inputs, outputs, its Event and its errors
NameTypeDefaultMeaning
hostnameHostnameRequiredYour site.
from'gap' | 'cited' | Url[]'gap'Pages from Competitor gap, the domains AI answers cite, or your own list.
takeinteger40Pages to pitch, highest authority first.
assetsUrl[]your data pages, free tools and templatesWhat you offer each editor. A pitch with nothing to link to is held.
mailboxMailboxyour warmed sending domainWhere pitches send from; never your main inbox.
maxCostnumber1.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 Domain.pitched. Refuses with max-cost before it spends, and with the Data calls and Accounts groups (refusals).

Contents