← Builds
Your own data

Turn a vertical database into a 3-touch outbound engine

Earlier campaigns sourced accounting firms and real estate brokers with Clay. For the owners campaign we pulled a licensed real estate operator database into Tightrope's own Postgres and cut it down to one verified owner per firm. Each owner got LinkedIn, then email, then a call from the founder. Sequence data syncs back into the same database, so a scheduled job knows who finished without replying and puts a call task in the CRM.

Works ifA vertical database already lists your buyers with contacts, and a founder will make the closing calls.

Built for · Tightrope · Seed-stage startup · San Francisco
Thesis DrivenNeonClaudeClayHubSpotHeyReachInstantlyTrigger.devVercel
Read the case study Get the prompt for your coding agent

The system

~/builds/tightrope-outbound-system
01Source

Clay

Find Companies

Accounting firms by industry and size.

Clay

Clay

Find People

Commercial real estate brokers.

Clay

Licensed database

Real estate operators

Firms, owners and contacts, via API.

Thesis Driven
Into Postgres, one schema per sourcededup, then join at core by email and domainNeon
Cut the cohorttitles classified by Claude, emails verified twiceClaude
02Outreach

1

LinkedIn

Blank connect, then up to 3 chats

HeyReach

2

Email

Value email, then a bump

Instantly

3

Call

Task for the founder if no reply

HubSpot
Sequence data synced back to Postgresevery 3 hours, dashboard on SQL viewsTrigger.dev
Warm owners reach the founder's call queue

The data model

~/builds/tightrope-outbound-system/erd

thesis

thesis.gps
  • pkidbigint
  • row_hashtext
  • fkcompany_iduuid
  • gp_nametext
  • websitetext
  • themestext
  • units_ownedtext

Operator firms. row_hash = md5 of the full raw record, the dedup key.

thesis.principals
  • pkidbigint
  • row_hashtext
  • fkgp_idbigint
  • fkcontact_iduuid
  • emailtext
  • titletext
  • linkedin_urltext

Decision-makers. Linked to their firm by name match at load.

thesis.signals
  • pkidbigint
  • row_hashtext
  • fkcompany_iduuid
  • gp_nametext
  • signal_typetext
  • article_urltext
  • article_datetimestamptz

News mentions per firm (acquisition, asset sale). Mirrored, not used to trigger outreach.

core

core.companies
  • pkiduuid
  • nametext
  • domaintext
  • linkedin_urltext
  • created_attimestamptz
  • updated_attimestamptz

Identity anchor. Unique on domain. Holds worked records only; the vendor database is joined by domain until a record enters a campaign.

core.contacts
  • pkiduuid
  • fkcompany_iduuid
  • first_nametext
  • last_nametext
  • emailtext
  • titletext
  • linkedin_urltext

Unique on lower(email).

hubspot

hubspot.contacts
  • pkhs_object_idtext
  • fkcontact_iduuid
  • fkcompany_iduuid
  • hs_company_idtext
  • emailtext
  • lead_statustext
  • utm_campaigntext

CRM mirror, PK = HubSpot object id, full record in payload jsonb.

hubspot.deals
  • pkhs_object_idtext
  • fkcompany_iduuid
  • deal_nametext
  • deal_stagetext
  • pipelinetext
  • amountnumeric
  • close_datetimestamptz

instantly

instantly.campaigns
  • pkidtext
  • nametext
  • statusint
  • timestamp_createdtimestamptz
  • payloadjsonb
instantly.leads
  • pkidtext
  • fkcampaign_idtext
  • fkcontact_iduuid
  • fkcompany_iduuid
  • emailtext
  • statustext
  • custom_variablesjsonb

Sequence status per lead; the call-task job reads completion and reply count from here.

heyreach

heyreach.campaigns
  • pkidbigint
  • nametext
  • statustext
  • total_usersint
  • users_finishedint
  • users_failedint
heyreach.conversations
  • pkidtext
  • fkcampaign_idbigint
  • correspondent_profile_urltext
  • last_message_attimestamptz
  • last_message_sendertext
  • total_messagesint
  • lead_repliedboolean

One row per LinkedIn inbox thread. lead_replied is the LinkedIn equivalent of an email reply. No FK into core yet.

ops

ops.call_tasks
  • pkcontact_emailtext
  • hs_contact_idtext
  • hs_company_idtext
  • campaign_idtext
  • hs_task_idtext
  • statustext
  • created_attimestamptz

Idempotency ledger: one call task per person, ever. Soft links to hubspot.contacts and instantly.campaigns by id, no FK constraint.

The signal

Owner-level fit from a vertical operator database, then sequence status: a lead who finishes the emails without replying queues a founder call.

Why it predicts a purchase

The buyer was the owner-operator: they spend their own capital, so savings land on them, while a third-party manager bills by the hour. Trust was the constraint, so every owner got the full sequence instead of a cold-email-only tier.

How it works

01

Mirror the database

We pulled the full operator database through its API into Postgres, in its own schema. It has no record IDs and different people share emails, so the dedup key is a hash of the whole raw row. The 60k listed contacts came down to about 41k real people.

02

Cut one owner per firm

Claude subagents classified every job title, because a regex let vice presidents in through the word 'president'. The cohort kept owner titles with an email, a rental theme and a unit band, one owner per firm, firms with a LinkedIn page ranked by portfolio size. Only emails that passed two verifiers went out: 685.

03

Run LinkedIn, then email

Contacts, companies and phone numbers went into HubSpot. HeyReach sends a blank connection request and up to three chats, then hands leads to Instantly for a value email and a bump. The handoff drops custom fields, so a scheduled job patches the asset type into each email lead and the first email waits a day for it.

04

Close on a call

Every three hours Trigger.dev mirrors Instantly and HeyReach into Postgres. A lead who finished the emails without replying gets a HubSpot call task for the founder, with the firm, asset type and prior touches in the body. A ledger table keeps it to one task per person.

Build notes

  • The dashboard first read email only and showed the owners campaign as nearly dead, because the campaign ran mostly on LinkedIn. Mirroring HeyReach into the same database fixed the read.
  • A tile labeled 'Meetings booked' was renamed 'Opportunities'. The number was a manual flag in the sequencer, not a confirmed meeting.
  • The vendor database never gets bulk-loaded into the core tables. A record is promoted when it enters a campaign, so core stays the worked pipeline.

Questions

Why not run everything out of the CRM?

The CRM is where the founder works deals and makes calls. Its native integrations with the sequencers flatten campaign, step and variant, so each tool is mirrored into Postgres on its own and the dashboard reads SQL views there. The CRM gets the campaign contacts and the call tasks.

When does the founder pick up the phone?

After the LinkedIn touches and the email sequence. A call task is only created when a lead finished the emails without replying and did not unsubscribe, and the task lists the touches that already happened so the call opens warm.

The next step

Let's get started.