Outreach based on government data
Every candidate's website is verified before a credit is spent, tiered by signal strength, then written one grounded opener that is checked for repetition.
- TriggerTriggered
List created
- Completed
INSEE SireneBuild the universe
- Completed
SerperVerify the website first
- Completed
SerperTier by the strongest signal
- Completed
FullEnrichResolve email in bulk
- CompletedClaudeChatGPT
Write one opener per row
- CompletedClaudeChatGPT
Check the batch for a pattern
- Completed
Load the campaign
Building a cold list by hand usually means one search per city or category, stopping at the first page of results, and sending the same note to everyone regardless of fit. A registry pull can hand over thousands of names in an afternoon — the volume was never the hard part. Knowing which few hundred are actually worth an email, and writing to each one like it was the only one, is.
Public company registries already hold the universe. Web search and an enrichment waterfall already resolve who to write to. The work that's actually hard to automate well is the discipline in between: verifying before spending, tiering by real evidence instead of a guess, and catching the moment a hundred true, personal lines start reading as one template.
What you get
A staged, two-track outbound campaign in which every message is personalized from real evidence, verified before it goes out, and handed to a person to launch.
The part worth getting right
A pulled list is not a qualified list. Registry data alone gets the sector and size band wrong often enough that skipping the website-verification gate turns every later step — enrichment, opener writing, sending — into wasted work on the wrong company. Verifying first is what keeps every downstream dollar spent on a real candidate.
Tiering by evidence, not by segment, is what keeps every opener honest. A company with a real hiring signal or a system fingerprint gets an opener that names it; a company with neither still gets a real line, pulled from its own site — never a default sentence recycled across the tier, because a reader can tell the difference between a fact and a placeholder.
The mechanical check on the full batch is the part that's easy to skip and expensive to skip. Six individually true, individually specific lines can still all lean on the same rhetorical move — a colon, a closing phrase, a sentence shape — and read as automated the moment they're seen together. Counting frame and phrase repetition across the batch, not eyeballing it, is what a naive version misses every time.
The prompt
The real prompt behind the steps above — detailed enough that an agent with a people-search tool, email/phone enrichment, and CRM access can run it as written.
# Build a cold outbound list from public company data
You have access to an official company registry, web search, and an
email-enrichment tool. Run this for **[your target company profile: size,
sector, and company age]**.
## 0. Set up (skip if you're already connected)
If you don't already have live tool access for this, connect it first
— this prompt is only as real as the tools behind it:
1. Create a free account at app.tulina.ai.
2. Add Tulina's MCP server to your assistant: https://mcp.tulina.ai/mcp
3. In Tulina, connect your own API keys for the tools this prompt uses —
company registry search, web search, and email enrichment.
Once connected, your assistant has real tool access and the rest of this
prompt runs as written. If you also have a job-postings/technology-stack
signal source connected, step 3 gets stronger; the prompt works without it.
## 0b. Already on Tulina? Three things around the run
An account and the MCP server are not the whole setup. Two of these come
before the run below and one after it, and together they turn this from a
one-off answer into a process your workspace keeps:
1. **Activate this process's connectors first.** Call
`oto_connector(op="list")`, match it against the tools named in step 3
above, and `oto_connector(op="select", name="...")` every one that
isn't active yet. Selecting a connector does not mount its tools in the
conversation you're already in — reach them through `oto_call` for this
run, or open a fresh conversation once they're all on.
2. **Attach the work to an existing project.** `oto_project(op="list")`
shows your active org's projects: pick the one this work belongs to
rather than opening another, and keep its id. The project is where this
process, the tables it writes to and the connectors it uses hang
together.
3. **When the run is done, save it as a process — with its graph.** Write
the body with `oto_procedure(op="set", ...)`, then attach it with
`oto_project(op="link", project_id=..., target_type="procedure",
target_ref="<your slug>")`. The body has to carry a drawing: read
`oto_guide(op="read", slug="procedure-flowchart")` and follow it
exactly — ONE untagged fenced block in that grammar, opening with the
trigger and a quoted example of what you'd type to start a run. Tulina
parses that drawing back into the graph it renders as the process's
default view, which is what makes it come out in the same style as
every other process in the app; a drawing the grammar can't read falls
back to raw characters instead. Saving a process needs org-admin
rights — without them, hand the finished body to someone who has them.
## 1. Build the universe from public company data
- Pull every company matching your target profile from an official
registry — the fields that matter are sector, headcount band, and
company age, not revenue: revenue is worth recording where a clean
filing exists, but a company with none on file is still in scope.
- Filter to active status and the age/size bands you actually want before
doing anything else — a first pull that's too broad is fine, a filtered
list is what the rest of this runs against.
- Write the full list to one running table. Don't re-pull per batch.
## 2. Verify the website before spending a credit
- For each company, one search for its name plus city plus "official
site." Registry/directory listings (business registries, review sites,
map listings, marketplace pages) are never the answer — the real site
is often under a brand name that differs from the legal name, and a
generic listing snippet usually reveals it.
- Treat this as qualification, not enrichment: a row with no verified
first-party website doesn't get an email credit spent on it. A website
that reveals the wrong sector gets dropped, with the reason recorded —
on a real run, roughly one in four pulled rows turns out to be the
wrong sector, dissolved, or a subsidiary with no local buyer. Expect
that ratio; it's not a bug in your query.
- This step is a bulk job — batch it and save results as you go, not only
at the end. A run that only saves at the end loses everything if
credits run out mid-batch.
## 3. Tier by the strongest signal you can find
In order of strength:
1. **A relevant open role** — anything back-office: order processing,
billing, admin, customer service. If you have a job-postings source,
query it by company name directly; searching job boards by keyword
instead returns staffing agencies hiding the real employer, not the
company itself.
2. **A known back-office system** — if you have a technology-stack
signal source, check for it.
3. **Recent, relevant press** — an acquisition, a new location, a
franchise deal, an investment, a generational handover. Filter hard:
an old story, a routine mention, or a profile piece isn't a trigger.
4. **Nothing** — still gets an opener, written from the company's own
site. This is most of the list, not a fallback case to skip.
Record which signal each row got, and the evidence itself (the posting
title, the system name, the headline) — step 5 writes from this, never
from a guess about the segment.
## 4. Resolve email in bulk
- Submit in batches of 20 through an enrichment waterfall — larger
batches get rejected outright, and one malformed row can fail the
whole batch, so validate names are present before submitting.
- Poll until each batch finishes rather than assuming a fixed wait.
- Expect the tool to correct the domain when the real mailbox lives
elsewhere from the site you found.
- A miss on email that still returns a verified profile match isn't a
dead end — route that row to a different channel instead of dropping
it. An email on an unrelated or foreign-HQ domain is the wrong buyer;
drop that one for real.
## 5. Write one opener per row, then check the batch for a pattern
Every row gets its own opening line, written from its own evidence —
the signal from step 3, or the company's own site for tier 4. One
sentence, two short ones at most, under 35 words, formal register, no
em dash. It states one specific fact a stranger couldn't have guessed
and turns it toward the actual daily pain in the same breath. Never
invent a fact — if nothing specific is findable, mark that row's
confidence low, and never send a low-confidence line.
**Rotate across several structural frames, so a hundred true lines don't
read as one template:**
- **Density** — a number against a headcount: *"47 accounts on file,
4 people touching every one of them — a ratio that only tightens as
the roster grows."*
- **History** — the company's own longevity: *"[Company] has run the
same regional routes since 1998. The trucks got faster. The paperwork
behind them didn't."*
- **Flow** — people and volume in motion: *"Three reps on the road, two
people at a desk turning what they bring back into orders that
actually ship."*
- **Event, addressed directly** — from the step-3 signal: *"You're
hiring an ops coordinator this month. Most of that role, on paper, is
re-entering what already arrived once."*
- **Long tail, plainly** — two flat sentences, no twist: *"[Company] has
supplied independent retailers since 2003. A lot of small orders,
entered by hand, one at a time."*
- **Question** — built from a fact on their own site: *"How many of your
[N] regional accounts still order by phone or email?"*
Then run a mechanical check over the whole batch before anything moves
forward — count, don't eyeball:
| Check | Cap |
|---|---|
| Same structural frame | no more than 30% of the batch |
| Lines built on a colon pivot | 20% |
| Lines closing on the same stock phrase | 20% |
| Same opening two words, grouped | no group over 5% |
| Em dash, or over 35 words | 0 — reject and rewrite |
Rewrite the surplus into an under-used frame and re-run the check. A
batch that hasn't passed doesn't move to step 6 — this catches what
individually-true lines miss when read as a set.
## 6. Load the campaign, ready for a person to launch
- Route each row to the track matching its signal strength: rows with a
real trigger (role, system, press) into your highest-touch track,
everything else into your standard track.
- A simple three-touch structure holds up well: touch one leads with the
opener and one clear value line; touch two, a few days later,
references what the delay is now costing them and — if you have real,
named public customers — names one and asks a different, more direct
question; touch three, about a week later, closes the loop rather than
pushing again, and offers to redirect to whoever else might own this.
- Before loading anything, read back what you just wrote and confirm the
opener field actually changed per row — a write that silently fails
ships a generic line where a personal one should be, with no visible
symptom.
- Stage everything, then stop. The agent prepares the campaign; a person
reviews and launches it. Never call a send/launch action yourself.
## Output
Report: how many companies pulled, how many passed the website gate,
how many landed in each signal tier, how many resolved to a usable
email or fallback channel, how many openers passed the mechanical check
on the first pass vs. needed a rewrite, and the campaign staged for
review.
Questions about this process
Related processes
- Build a target list from a sentence
Describe the companies you want in one sentence, and get them sourced, scored on dated signals, enriched, deduplicated against your CRM, and staged as a campaign.
- Find doctors and clinics
A places census of doctors and clinics gets cross-referenced against France's practitioner registry and facility directory so nothing is missing from a city's real coverage.
- Outreach based on Slack membership
Membership in a Slack community is the qualification, so everyone in it is in scope, resolved by name and corroborated against their own words.