Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
273 changes: 273 additions & 0 deletions app/(marketing)/lead-generation/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
import Link from "next/link";
import { LEAD_RUN_CREDITS, CREDIT_RACK_CENTS, dollars } from "@/lib/credits";
import { LEADS_PER_CHARGE } from "@/lib/outreach/billing";

export const metadata = {
title: "Lead generation & cold outreach",
description:
"Point CrawlProof at a directory or a search, and it finds the companies, finds the people, finds the addresses, and writes the email — from your own mailbox, grounded in facts you supply. 3 credits a run.",
alternates: { canonical: "/lead-generation" },
openGraph: {
title: "Lead generation & cold outreach · CrawlProof",
description:
"Find the companies, find the people, find the addresses, write the email — from your own mailbox, grounded in facts you supply. 3 credits a run.",
url: "/lead-generation",
},
};

/**
* The sales page for project-level lead generation.
*
* Every claim here is one the pipeline actually makes good on. Two things it
* deliberately does not claim: open tracking and automatic reply detection.
* Neither is built, and a lead-gen page that oversells is the exact thing the
* drafting guard exists to stop the product doing to other people's inboxes.
*/

const LADDER = [
{
step: "1",
title: "The site itself",
body: "Contact and about pages, footers, mailto: links, and the obfuscated addresses that are written to defeat scrapers.",
},
{
step: "2",
title: "Team & leadership pages",
body: "A named person outperforms a shared inbox, so /our-team, /leadership and /people are read before anything is paid for.",
},
{
step: "3",
title: "Linked PDFs",
body: "Capability statements, media kits and data-sheets routinely carry an address the page linking to them never mentions.",
},
{
step: "4",
title: "Search",
body: "Only now does a run spend on a search call — for the people it has a name for but no way to reach.",
},
{
step: "5",
title: "A role address, last",
body: "When a company publishes nothing at all, a conventional address like press@ is tried — and marked as a guess, not passed off as found.",
},
];

export default function LeadGenerationPage() {
const runRack = LEAD_RUN_CREDITS * CREDIT_RACK_CENTS;

return (
<main className="mx-auto max-w-5xl px-4 py-16 sm:px-6">
<section className="text-center">
<p className="mb-3 text-sm font-medium uppercase tracking-wider text-[var(--color-accent)]">
Project feature · Leads
</p>
<h1 className="text-4xl font-extrabold sm:text-5xl">
The boring half of cold outreach, done for you
</h1>
<p className="mx-auto mt-4 max-w-2xl text-lg text-[var(--color-muted)]">
Finding who to email is most of the work and none of the fun. Point a campaign at a
directory or a search, and CrawlProof finds the companies, finds the people, finds the
addresses, and drafts the email — from your mailbox, in your words, about your thing.
</p>
<div className="mt-8 flex flex-wrap justify-center gap-3">
<Link href="/signup" className="btn btn-primary">
Start a campaign
</Link>
<Link href="/pricing" className="btn">
See pricing
</Link>
</div>
<p className="mt-4 text-sm text-[var(--color-muted)]">
{LEAD_RUN_CREDITS} credits a run (~{dollars(runRack)}). A run that finds nothing to do
costs nothing.
</p>
</section>

<section className="mt-20">
<h2 className="text-2xl font-bold">It reads the directory like a person would</h2>
<p className="mt-2 max-w-3xl text-[var(--color-muted)]">
Most lead directories are a wall of JavaScript. Fetching the HTML gets you an empty shell,
which is why so much of this work ends up being done by hand.
</p>
<div className="mt-6 grid gap-4 sm:grid-cols-3">
<Card
title="Renders the page"
body="A real browser loads the listing, so AJAX results exist by the time anything is read."
/>
<Card
title="Scrolls and clicks Next"
body="Infinite-scroll lists are scrolled; paginated ones are stepped through, and stop the moment a page repeats itself."
/>
<Card
title="Follows through to detail pages"
body="A listing row is rarely the whole record, so entries are opened for the details the index leaves out."
/>
</div>
</section>

<section className="mt-20">
<h2 className="text-2xl font-bold">Directories that name people, not companies</h2>
<p className="mt-2 max-w-3xl text-[var(--color-muted)]">
Plenty of the best lists give you a name, a title and a location and withhold the email —
that is the product they are selling. Those are read as people: name, role, employer,
location and profile links are kept, and the address becomes something to go and find
rather than a reason to discard the lead.
</p>
</section>

<section className="mt-20">
<h2 className="text-2xl font-bold">Five places to look, cheapest first</h2>
<p className="mt-2 max-w-3xl text-[var(--color-muted)]">
The order is deliberate. Everything free is exhausted before anything paid is tried, so a
run only spends on the prospects that genuinely publish nothing.
</p>
<ol className="mt-6 space-y-3">
{LADDER.map((l) => (
<li
key={l.step}
className="flex gap-4 rounded-lg border border-[var(--color-border)] bg-[var(--color-card)] p-4"
>
<span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[var(--color-bg)] font-mono text-sm font-bold">
{l.step}
</span>
<div>
<h3 className="font-semibold">{l.title}</h3>
<p className="mt-1 text-sm text-[var(--color-muted)]">{l.body}</p>
</div>
</li>
))}
</ol>
</section>

<section className="mt-20">
<h2 className="text-2xl font-bold">The email can only say what you said</h2>
<p className="mt-2 max-w-3xl text-[var(--color-muted)]">
A campaign carries three things: who is writing, the one small thing you are asking for,
and a list of checkable facts. Every draft is written from those and then checked against
them — a draft that states something outside the list is thrown away rather than sent.
</p>
<div className="mt-6 grid gap-4 sm:grid-cols-2">
<Card
title="Grounded, not generated"
body="An AI writing cold email will happily invent a credential, a client list or a statistic. This one is not allowed to: the guard is the feature."
/>
<Card
title="Written for the recipient"
body="Each draft opens with something specific to that company, taken from their own site — not a mail-merge of their domain name."
/>
<Card
title="Describe the goal, get the fields"
body="If writing an intro, an ask and a fact list sounds like homework, describe what you are trying to do and have the fields drafted from it."
/>
<Card
title="Follow-ups that stop"
body="At most two follow-ups on a schedule, and marking a lead replied ends the sequence. Nobody gets a fourth email."
/>
</div>
</section>

<section className="mt-20">
<h2 className="text-2xl font-bold">Sent from your mailbox</h2>
<p className="mt-2 max-w-3xl text-[var(--color-muted)]">
Give it the address you already send from and its settings are discovered automatically —
the same lookup your mail client does. Replies come back to you, because the mail came
from you.
</p>
<div className="mt-6 grid gap-4 sm:grid-cols-3">
<Card
title="Auto-discovered"
body="Server, ports and encryption are resolved from your domain. The password is encrypted at rest with a key that is never in the database."
/>
<Card
title="Verified before it is trusted"
body="The connection is tested when you save it, so a typo surfaces then rather than as a campaign that quietly sends nothing."
/>
<Card
title="Dry-run first"
body="Leave auto-send off and drafts pile up for you to read. Turning it on is a deliberate act."
/>
</div>
</section>

<section className="mt-20">
<h2 className="text-2xl font-bold">It will refuse to email some people</h2>
<p className="mt-2 max-w-3xl text-[var(--color-muted)]">
Cold outreach earns its reputation one careless send at a time. Privacy, legal, abuse,
opt-out and accessibility addresses are never contacted, whatever a page publishes. A
postal address for the footer is required before live sending is possible at all, because
CAN-SPAM requires one. Anyone who asks to be left alone is suppressed across every
campaign you run, permanently.
</p>
</section>

<section className="mt-20">
<h2 className="text-2xl font-bold">Your numbers, not somebody else&apos;s</h2>
<p className="mt-2 max-w-3xl text-[var(--color-muted)]">
Sends, replies and closes are counted at three levels: the whole project, each campaign,
and each individual run. Rates stay hidden until there is enough volume to mean anything —
one reply out of three sends is not a 33% reply rate, and showing it as one invites a
decision the sample cannot support.
</p>
</section>

<section className="mt-20 rounded-xl border border-[var(--color-border)] bg-[var(--color-card)] p-8">
<h2 className="text-2xl font-bold">What it costs</h2>
<div className="mt-6 grid gap-6 sm:grid-cols-2">
<div>
<div className="text-3xl font-extrabold">
{LEAD_RUN_CREDITS} credits
<span className="ml-2 text-base font-normal text-[var(--color-muted)]">
per run (~{dollars(runRack)})
</span>
</div>
<p className="mt-3 text-sm text-[var(--color-muted)]">
A campaign run is one pass of the whole funnel: discovery, contact lookup, drafting
and sending. A run with nothing to do is not charged, so leaving a campaign switched
on between batches is free.
</p>
</div>
<div>
<div className="text-3xl font-extrabold">
{LEAD_RUN_CREDITS} credits
<span className="ml-2 text-base font-normal text-[var(--color-muted)]">
per {LEADS_PER_CHARGE} leads
</span>
</div>
<p className="mt-3 text-sm text-[var(--color-muted)]">
For a one-off search of a directory, priced by how much you ask for. Find nothing and
it is refunded.
</p>
</div>
</div>
<p className="mt-6 text-sm text-[var(--color-muted)]">
Credits are the same ones scans and articles use — no separate plan, no subscription, and
they do not expire. Volume packs bring a run down to about{" "}
{dollars(Math.round(runRack / 2))}.
</p>
<div className="mt-6 flex flex-wrap gap-3">
<Link href="/signup" className="btn btn-primary">
Get started
</Link>
<Link href="/pricing" className="btn">
Credit packs
</Link>
</div>
</section>

<p className="mt-10 text-center text-xs text-[var(--color-muted)]">
Lead generation runs on projects you own. You are responsible for the mail you send and for
complying with the rules where your recipients are — CrawlProof enforces a suppression list
and a footer address, not the law.
</p>
</main>
);
}

function Card({ title, body }: { title: string; body: string }) {
return (
<div className="card p-5">
<h3 className="font-semibold">{title}</h3>
<p className="mt-2 text-sm text-[var(--color-muted)]">{body}</p>
</div>
);
}
33 changes: 29 additions & 4 deletions app/(marketing)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export default function HomePage() {
<ModuleCard emoji="📈" title="Uptime Monitoring" body="HTTP, keyword, SSL &amp; TCP checks with instant down/recovery alerts to email, Slack, and Discord." />
<ModuleCard emoji="🛡️" title="Exposed Services" body="Full-port security scans of your verified hosts — get alerted the moment a database or admin port is exposed." />
<ModuleCard emoji="✍️" title="Autoblog" body="Research, draft, illustrate, and publish long-form SEO posts to your CMS on a schedule — ~$1 each." />
<ModuleCard emoji="🎯" title="Lead Generation" body="Find the companies, the people and the addresses from any directory or search — then send grounded cold email from your own mailbox." href="/lead-generation" />
<ModuleCard emoji="🔗" title="Link Exchange" body="Verified backlink matching with a real ledger — quality links without four-figure marketplace prices." />
<ModuleCard emoji="📊" title="Analytics Tracker" body="A drop-in tracker for AI referrals, bot crawls, human traffic, pages, and geo — see who (and what) visits." />
<ModuleCard emoji="📣" title="Social Posting" body="Draft and schedule social posts and feed autoposts alongside your content pipeline." />
Expand Down Expand Up @@ -422,14 +423,38 @@ function FeatureCard({ title, body }: { title: string; body: string }) {
);
}

function ModuleCard({ emoji, title, body }: { emoji: string; title: string; body: string }) {
return (
<div className="card flex flex-col p-5">
function ModuleCard({
emoji,
title,
body,
href,
}: {
emoji: string;
title: string;
body: string;
/** Set only for modules that have a page of their own to go on to. */
href?: string;
}) {
const inner = (
<>
<div className="text-2xl" aria-hidden>
{emoji}
</div>
<h3 className="mt-2 font-semibold">{title}</h3>
<p className="mt-1 text-sm text-[var(--color-muted)]">{body}</p>
</div>
{href && <p className="mt-3 text-sm font-medium">Learn more →</p>}
</>
);
// A card without somewhere to go stays a card. Wrapping every one in a link
// to nowhere would make eight of the nine look clickable and do nothing.
return href ? (
<Link
href={href}
className="card flex flex-col p-5 transition hover:border-[var(--color-fg)]"
>
{inner}
</Link>
) : (
<div className="card flex flex-col p-5">{inner}</div>
);
}
46 changes: 46 additions & 0 deletions app/(marketing)/pricing/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import {
CREDIT_PACKS,
CREDIT_RACK_CENTS,
ENGINES,
LEAD_RUN_CREDITS,
OUTREACH_CREDITS,
SCAN_CREDITS,
discountPct,
dollars,
perCreditCents,
Expand Down Expand Up @@ -58,6 +61,49 @@ export default function PricingPage() {
})}
</div>

{/* Credits buy more than scans, and a page that only prices scans makes
every other module look like it is either free or unavailable. */}
<section className="mt-14">
<h2 className="text-center text-2xl font-bold">What else credits buy</h2>
<p className="mx-auto mt-2 max-w-2xl text-center text-sm text-[var(--color-muted)]">
One balance across the whole platform. No module has its own plan.
</p>
<div className="mx-auto mt-6 grid max-w-4xl gap-4 sm:grid-cols-3">
<div className="card p-5">
<div className="text-xs uppercase tracking-wider text-[var(--color-muted)]">
{LEAD_RUN_CREDITS} credits
</div>
<h3 className="mt-1 font-bold">Lead generation run</h3>
<p className="mt-2 text-sm text-[var(--color-muted)]">
One pass of a campaign: discovery, contact lookup, drafting and sending. A run with
nothing to do is not charged.{" "}
<Link href="/lead-generation" className="underline">
How it works
</Link>
</p>
</div>
<div className="card p-5">
<div className="text-xs uppercase tracking-wider text-[var(--color-muted)]">
{OUTREACH_CREDITS} credit
</div>
<h3 className="mt-1 font-bold">Outreach send</h3>
<p className="mt-2 text-sm text-[var(--color-muted)]">
One email, SMS recipient or social post sent by hand, outside a campaign run.
</p>
</div>
<div className="card p-5">
<div className="text-xs uppercase tracking-wider text-[var(--color-muted)]">
{SCAN_CREDITS} credits
</div>
<h3 className="mt-1 font-bold">Autoblog post</h3>
<p className="mt-2 text-sm text-[var(--color-muted)]">
A researched, illustrated, published long-form article — the same price as an
AI-model scan.
</p>
</div>
</div>
</section>

<div className="mt-12 grid gap-4 md:grid-cols-2 lg:grid-cols-4">
{CREDIT_PACKS.map((p) => {
const off = discountPct(p);
Expand Down
Loading
Loading