Skip to content
← Processes

Message new LinkedIn connections

Each new LinkedIn connection is checked for past conversations, researched on public sources and given a first message and a follow-up, sent once approved.

LinkedIn→Firecrawl→TheirStack
Starts
Daily schedule on weekday mornings
Steps
9
Checks and gates
4
A person decides
1

Trigger · Schedule

Daily schedule on weekday mornings

Each weekday morning the run picks up the connections accepted since the last one.

Claude runs the rest autonomously

  1. 1

    AI checks the account before spending.check

    LinkedIn

    LinkedIn

    The session has to answer alive and the tables have to answer, or the run stops and says which before it spends anything.

  2. 2

    AI lists the newly accepted connections.

    LinkedIn

    LinkedIn

    Every connection not already known becomes one row keyed on its profile URL, with the date it was accepted.

  3. 3

    AI matches each one to its outbound source.

    A connection on one of your outbound lists keeps its source, and one on none of them is personal network and left alone.

  4. 4

    AI closes anyone already talked to.check

    LinkedIn

    LinkedIn

    The whole conversation is read, and any message you ever sent, an exclusion, a colleague already chosen or a recent email stops the row.

  5. only when a blank thread and nothing excludes them
  6. 5

    AI researches the company, then judges the fit.

    LinkedIn

    LinkedIn

    The current role, the company's own pages and its open jobs give a verdict with two or three sourced signals, and a poor fit ends there.

    Firecrawl

    Firecrawl

    TheirStack

    TheirStack

  7. 6

    AI writes the two messages.

    An observation with one question, then a second angle, queued for review with the reason the person was chosen.

  8. Approve, rewrite or drop each pair. A person steps in here.handover

    A person sends, rewrites or drops each pair, and the reasons go into a log the next run reads first.

  9. only when approved to send
  10. 8

    AI re-checks just before sending.check

    LinkedIn

    LinkedIn

    The connection, the copy, a freshly read thread, the current role and the email window are all checked again, and one failure holds the send.

  11. 9

    AI sends, then follows up once.

    LinkedIn

    LinkedIn

    The first message goes out exactly as approved, and the second follows days later only when nobody has replied.

Your outbound sends a few dozen LinkedIn invitations a week, and people accept them. Then one of two things happens. Nothing, because nobody owns the moment after the acceptance and the connection goes cold in a list. Or a generic pitch lands in a thread where someone on your team already wrote to that person months ago, and the person reads it as the automated sequence it is.

The two halves already exist. Your accepted connections, and every conversation you ever had with them, are in your LinkedIn account. What each company does, how it sells and who it is hiring is on its own public pages and in its job postings. The work is the join: for each new connection, read the history first, then the company, and write the one message that could only have gone to them.

What you get

Every connection your outbound brought in gets two messages built on one verified public fact about their company, nobody you have already talked to is pitched as a stranger, and nothing leaves until a person has approved that exact text.

What each new connection is checked against

The run reads your newest accepted connections on LinkedIn and gives each unknown one a row, keyed on the normalized profile URL that every table shares. It then asks where the connection came from: one of your email campaigns' lists, your sourcing tool's list, or neither. Neither means personal network, and the person is left alone. A connection that looks sourced but is on no list waits until the list is reloaded, rather than being guessed into one bucket or the other.

Before any research, the inbox is paged to the end and the person's thread is read in full. A message from your account at any date closes the row. So does an exclusion, a colleague at the same company who was already chosen, or an email from your campaigns to that company in the last week. Only then is the company researched: the current role on the profile, the company's own pages through Firecrawl, and its open jobs through TheirStack.

What makes a first LinkedIn message hold up

Every signal carries the URL it was read from. The verdict on a connection is written as two or three lines of "fact (source: URL)", and a signal without a page actually opened does not exist. That rule is what keeps the first message honest: it opens on one observation about how the company handles the problem you solve, from one of those pages, and asks one question about their workflow that can be answered in a line. If the same message could go unchanged to twenty people with the same title, it is research rather than relevance, and it is rewritten before anyone sees it.

The person stays the person. The profile is read again by its public identifier, and the identifier that comes back has to match the one asked for, because a mismatch is a redirect onto somebody else. The current company is what gets judged, not the one in the campaign file. An empty job search is normal for a small company and never read as "not hiring". The person's own posts can steer the angle, but they are never quoted back at them.

Nothing leaves without a decision on that exact text. At most ten pairs wait for review at a time, and a reviewer sends, rewrites with a comment, or drops each one. The reasons go into a decisions log the next run reads before judging or writing anything, and a reason that comes back three times is proposed as a standing rule.

Re-checked at the moment of sending

Approval can be days old by the time a message goes, so the run checks again right before it sends: the connection is accepted, both messages exist, the thread read just now is still blank, the person is still in the role, and no email went to the company in the meantime. A check that cannot be run counts as failed, and the row goes back with the reason. The first message goes out character for character as approved. The second follows at least five business days later, on a different angle, and only if nobody has replied. There is never a third.

The prompt

The real prompt behind the steps above, detailed enough that an agent with the same tools can run it as written. The Run buttons copy the full version, which also carries the steps for connecting those tools.

# Message new LinkedIn connections

You have access to your own LinkedIn session, a web scraper, a job
postings search, and a handful of tables you can read and write. Run
this **[on weekday mornings]** for **[the connections your outbound
invitations brought in]**.


## 1. Check the account before spending

### The tables, and the one key they share

Every match in every table runs on the normalized LinkedIn URL:
`https://www.linkedin.com/in/<public identifier>`, no trailing slash,
no query parameters.

| Table | What it holds |
|---|---|
| The pool | One row per person; its status is the truth. A person enters once, is judged once, and is never messaged twice. |
| The approval queue | The only table the reviewer works in: undecided pairs only, never more than ten. |
| The tracking table | One row per person messaged: step, dates of both messages, reply. Written by the run, never by hand. |
| The source lists | The LinkedIn URLs of people in your email campaigns, with their tier, and of people your sourcing tool found. |
| The exclusions | People, domains and companies you never contact: opt-outs, customers, friends, people who replied to a campaign and their colleagues. |
| The decisions log | One dated line per reviewer decision, with its reasons and evidence. Read before every run. |

- The LinkedIn session must answer connected and alive. A seat can
  stay linked while its session is dead, and this check is free.
- The pool must answer a read. If either check fails, stop and say
  which: a run that cannot read the inbox or write the pool spends
  nothing.
- If the LinkedIn tools are missing altogether, the connector is most
  likely paused rather than missing a key. Report it that way.

## 2. List the newly accepted connections

- Read your connections, newest first, asking only for name,
  headline, public identifier, member id and connection date so each
  page stays small. Keep paging until a whole page is already in the
  pool. Dedupe on the member id, never on the page offset: the cursor
  can return the same person twice.
- Look each normalized URL up in the pool and in the exclusions. A
  known person stops there, with one exception: a known row with no
  connection date is an invitation you queued earlier. When it shows
  up as accepted, stamp the date and the row resumes where it was.
- A new person becomes one row: name, headline, company, URL,
  connection date, status `to_draft`, fit `to_judge`. Nothing else is
  written yet.

## 3. Match each one to its outbound source

Compare the normalized URL, in this order:

1. **Your email campaigns' list.** The provenance is the tier on that
   row. Reload the list whenever a campaign starts: a list that lags
   turns a sourced lead into an organic one.
2. **Your sourcing tool's list**, matched on the public identifier.
3. **Neither: organic.** Add the person to the exclusions with the
   reason "organic" and set the pool row to `rejected`. Your personal
   network is out of scope.

**Never guess a provenance.** A connection that looks like a sourced
lead but is on neither list waits in `to_draft` with "source to
confirm, reload the list".

## 4. Close anyone already talked to

This is the step that protects the relationship. For each open row:

- **Find the thread.** List the inbox page by page with the cursor
  and index the one-to-one threads on the attendee's profile URL,
  normalized, once per run rather than once per person. When the
  attendee names did not resolve, fall back on the attendee's provider
  id. **Only a listing paged to the end proves there is no thread**: a
  partial listing raises no error, it just shows fewer people.
- **Read it in full.** Any message written from your account, however
  old, closes the row as `already_contacted`: tick `thread_verified`
  and note the date of your last message. Campaign files are not
  evidence; the inbox is.
- **Decide who wrote each message** by comparing its sender id with
  your own account's id. Never rely on the message's "is sender" flag:
  it has been seen false on every thread of an account.
- **An unreadable thread is not a blank thread.** An error or a
  timeout leaves the row in `to_draft` for the next run.
- **Exclusions.** The person, their company's domain or their URL on
  the list sets the row to `rejected` with the list's reason.
- **One person per company.** When two open rows share a company,
  keep the one closest to the buying decision and reject the other as
  "colleague, one person per company".
- **One channel at a time.** If anyone at that company received an
  email from your campaigns in the last **[7 days]**, or is still in a
  live email sequence, the row waits with "recent email". Read this
  from **[your email send log]**; if it cannot be read, the row waits
  and the report says why.

A row moves on only with `thread_verified` ticked and no exclusion
touching it.

## 5. Research the company, then judge the fit

Load **[your fit guide]** and the decisions log first. A reason that
recurs in the log counts as a rule until the reviewer contradicts it.

1. **Confirm the person is still there.** Read their profile by
   public identifier and check that the identifier returned is the
   one you asked for: a mismatch is a redirect onto someone else.
   Judge the *current* company and correct the row when the profile
   disagrees. Keep the provider id, which sending needs. A response
   saying some sections were throttled is a rate limit, not missing
   data: retry that person later.
2. **Read the profile's description first.** One matching **[your
   standing no-go profiles]** is an immediate `no_go`, however strong
   the lead looks. So is a contact address on a different domain than
   the stated company.
3. **Gather public sources.** Scrape the company's home, pricing,
   customers, team and careers pages, allowing cached copies of stable
   pages. Search its job postings by company domain with a small
   limit: it bills per company record returned, and an empty result is
   normal on small companies, never proof they are not hiring. The
   person's own LinkedIn activity may steer the angle, but it is never
   quoted.
4. **Write the verdict**: `go`, `weak` or `no_go`, with two or three
   signals written one per line as "fact (source: URL)", a reason, a
   trigger and an angle. **A signal without a URL actually opened does
   not exist.** A `no_go` ends the row as `rejected`.

## 6. Write the two messages

Load **[your copy guide]** and re-read the rewrite comments in the
decisions log first. For each `go` or `weak` row, most recent
connection first:

1. **Message one: an observation and its question.** One verified
   public observation about how this company handles the problem you
   solve, from a URL you opened, then one question about their
   workflow that can be answered in a line. No pitch. **The twenty
   test:** if the message could go unchanged to twenty people with
   the same title, rewrite it.
2. **Message two: a different angle.** One question. Never a reminder
   of message one and never an exit line. For a row returned with a
   rewrite comment, the comment outranks everything else.
3. **Run your copy checklist** on both, item by item. A message that
   fails is rewritten, never proposed.
4. **Queue the pair** in the approval queue: name, company, title,
   URL, both messages and a one-line "why" (source, verdict, the
   observation used), decision empty. Store both messages on the pool
   row and set it to `awaiting_decision`.

**At most [ten] proposals per run, and never more than ten undecided
rows in the queue.** A `weak` row gets a shorter, more careful
message, never a generic one. When no observation holds up, the row
stays `to_draft` with "no solid observation".

## 7. Approve, rewrite or drop each pair

A person opens the approval queue and sets one decision per row:

- `send`: both messages go out exactly as written, character for
  character.
- `rewrite`, with a comment: step 6 rewrites it next run and queues
  it again.
- `no`: the person is never messaged. "Exclude" in the comment adds
  them to the exclusions.

For `no` and `rewrite`, the reviewer ticks the reasons and pastes the
evidence. The next run **collects the decisions first**: `send` sets
the pool row to `approved`, `rewrite` to `to_fix` with the comment,
`no` to `rejected`. Reasons and evidence are copied onto the pool row
and appended as one dated line to the decisions log **before** the
row leaves the queue. When the same reason shows up three times, the
report proposes it as a rule for the fit or copy guide, and the
reviewer confirms it.

Make the pool table enforce this where your tables allow it: message
one and `thread_verified` required once a row is `approved` or
`sent`, and a status lifecycle that cannot jump from `to_draft`
straight to `sent`.

## 8. Re-check just before sending

For each `approved` row, in this order. One failure and nothing
sends: the row goes to `to_fix` with the reason, and the report names
the check.

1. **The connection is accepted.** If not, it is a pending
   invitation: the row stays `approved` and is listed under "awaiting
   acceptance". This is the only check that is not a failure.
2. **The copy exists**, both messages.
3. **The thread is blank, read just now.** Never trust step 4, which
   may be days old. An unreadable thread postpones the send.
4. **The person is still in the role.** A changed company sends the
   row to `to_fix` with "no longer there, re-judge on the new
   company". A role check older than **[7 days]** no longer counts.
5. **The email window is clear**, read again now.

**A check that cannot be run counts as a failed check.**

## 9. Send, then follow up once

**Message one**, in the order the decisions were made: send the
approved text unchanged, in the existing thread if there is one,
otherwise to the provider id from step 5. Set the pool row to `sent`
with the date and create the tracking row.

**Message two**, for `sent` rows at least **[5 business days]** old
with no second message: read the thread first. If the person
replied, message two never goes and the conversation belongs to a
person from here on. Otherwise send it exactly as approved and move
the tracking row on.

**Pacing:** at most **[15 messages per run]**, both messages
combined, on business days and within **[your business hours]**. The
rest wait for the next run.

**Tracking**, every run: a reply moves the tracking row to `replied`
with a one-line summary, and colleagues still open in the pool are
rejected as "colleague of someone who replied". A negative reply or
an opt-out puts the person and their company in the exclusions.
Silence after message two: nothing more. Two messages, never three;
after **[10 business days]** the row closes as `ended_no_reply`.

## Rules

- One sender on the account: while this runs, nothing else sends
  messages or invitations from it.
- Nothing sends without a `send` decision on that exact text.
- This process sends no invitations. It only works on connections
  already accepted.
- The inbox is the truth.
- Only a person lifts an exclusion.
- A second run on the same day is a no-op: the shared URL key, the
  thread re-read before every send, the send dates and the terminal
  statuses see to it.

## Output

Report: connections added and their sources; rows closed as already
contacted or excluded; verdicts; pairs queued; decisions collected,
with their reasons; rows blocked by a pre-send check, naming the
check; rows awaiting acceptance; first and second messages sent;
replies received; rows waiting past the caps or on a provenance; any
thread that could not be read. Nothing estimated.

Why run it with Tulina

A prompt helps one person. A process moves a team.

Pasted into a chat, the prompt above works once, for whoever pasted it. In Tulina, "Message new LinkedIn connections" becomes a process the team shares, that keeps its history, runs on its own, and stays inside the same security perimeter as the tools it calls.

  • The whole team works on the same one

    PersonalTeamOrganisation

    A process lives where it belongs: yours, your team's or the whole organisation's. Whoever runs it can improve it, and everyone starts from the best version instead of explaining the job again.

  • Every edit is a version

    v1v2v3

    Each change is a new version with the previous one kept, so a bad edit is undone by restoring an earlier one. Every run records the version it followed, and what a run teaches goes back into the process.

  • It runs without you

    schedulewebhookon demand

    A hosted agent runs the process on Tulina's own infrastructure, on a schedule or when an event arrives, and keeps going when you close the chat. Nothing to host. Every run is tracked to its outcome.

  • Inside the same security perimeter

    vaultrolesrun log

    Credentials stay in the vault, never in the prompt. Roles decide who may run what, every call is logged, and a step a person has to approve waits for that person.

Questions about messaging new LinkedIn connections

One verified public observation about how their company handles the problem you solve, taken from a page you actually opened, and one question about their workflow that can be answered in a line. No pitch. If the same message could go unchanged to twenty people with the same title, it gets rewritten.

It can research and write it, and here it stops before sending. Each new connection's profile, company site and open jobs are read, a verdict and its sources are written down, and a first message and a follow-up wait in an approval queue. Only the exact text a person marked send goes out.

By reading the inbox, not the campaign files. Every new connection's thread is found by paging the inbox to the end and read in full, and any message ever sent from your account closes the person as already contacted. The thread is read again right before sending, since the first check may be days old.

One, here. The second message goes at least five business days after the first, on a different angle, and only if the person has not replied. After that the sequence ends, and a reply at any point hands the conversation to a person.

Other processes that run mainly on LinkedIn. The tools they share with this one are lit.

See Tulina in a 30-minute demo.

Book a demo