# Reach out to a person or company

A step-by-step recipe for an AI agent using the BaseBara connector. Follow it when the user wants to write to, message, call, follow up with, or get in front of a specific prospect or account, on whichever channel fits, whether or not they say the word "outreach".

Tools used: `find_contacts`, `get_contacts`, `verify_emails`, `get_linkedin_messages`, `send_linkedin_connection_request`, `send_linkedin_message`, `create_contacts`, `log_contact_event`, `list_contact_events`.

## Use when

- The user names a company, a person, or a handful of accounts and wants a message to them.
- The user asks "how should I approach X" or "write me something for Y".
- The user wants a LinkedIn DM, a cold email, a call opener, or a short follow-up sequence for one target.

Not for: more than a handful of people or sends that span several days (call `run_outreach_campaign`, which builds on this one), or continuing a conversation that has already started on any channel (call `follow_up`).

## The channels

BaseBara reveals three ways to reach a person and sends on one of them:

| Channel | What BaseBara does | Who sends | Logged as |
| --- | --- | --- | --- |
| LinkedIn | Reveals `linkedin_url`, reads the thread, sends the connection request or DM, paces the sends | BaseBara, after the user approves the text | `contacted`, channel `linkedin`, by the server itself |
| Email | Reveals the work email and checks it is deliverable (`verify_emails`) | A mail connector in the same chat (Gmail, Outlook, a CRM's mail tool) if the user has one, after approval; otherwise the user, from their own client | `contacted`, channel `email`, when the connector confirms the send or the user says it went out |
| Phone | Reveals the business phone | The user, or a dialer or SMS connector if one is in the chat | `contacted`, channel `phone`, when the user says they called or the connector confirms |

**Other connectors.** BaseBara only sends on LinkedIn, but the user may have connected other tools to the same chat: a mailbox, a calendar, a CRM, a dialer. Before any email or phone step, look at the tools you have. If one can read or send on that channel, use it in place of asking the user: search the mailbox for a prior thread with the revealed address, send the approved email from it, look up the thread before a follow-up. The rules do not change. The same approval before every send, one channel per message, and the touch still logged in BaseBara so the team sees it. If no such tool is in the chat, the user sends by hand and tells you when it went out.

The craft is the same on every channel. Only the length, the opener, and the send step change.

## Inputs to collect

Before any tool call, restate the brief in one line:

- **Who**: the company and, if known, the person or the role ("the VP of Sales at Acme").
- **Why now**: the hook. A trigger event, a shared connection, something they published, a company initiative, or a documented persona pain. If the user has none, ask once. Never invent a signal.
- **Channel**: LinkedIn, email, or phone, if the user has a preference. If not, pick it in step 2 from what the contact has and what the ask needs, and say which you picked.
- **The ask**: the smallest real thing that could happen this week. A short list to run, one account to check, a sample to review. Offering concrete work beats asking for time.

## Steps

### 1. Find the person

Call `find_contacts` scoped to the company. Pass `company_name` or `company_domain`, and narrow with `authority_levels` (Staff, Manager, Director, VP, C-Team) or `title_contains`. Only filter by channel when the user has already chosen one: `has_linkedin=true` for LinkedIn, `require_work_email=true` for email, `require_phone=true` for phone. Otherwise leave them off, so the row shows what the person has and the channel can be picked from it.

- If the company resolves as `ambiguous`, show the candidates and re-call with the chosen `company_id`. Never pick for the user.
- If the user named a person, add `first_name` and `last_name` to the company-scoped call.
- Show the results table and let the user confirm the target. One person per message.

### 2. Reveal the contact and pick the channel

Call `get_contacts` with the chosen `contact_id`. This returns the work email, business phone, and `linkedin_url`. The user must be signed in. The connector prompts them if not.

Check `last_event` on the result (the `find_contacts` row carries it too). If a teammate already revealed or contacted this person, say so before drafting, and call `list_contact_events` with the `contact_id` for the history in the teammate's own words. The user may want to coordinate rather than double-contact.

Then settle the channel, in this order:

1. The user named one: use it.
2. Only one of the three was revealed: use that one.
3. There is a live LinkedIn thread or an existing connection (step 3 tells you): LinkedIn.
4. The message needs a proof point, a link, or more than a few sentences: email.
5. A late-stage economic buyer, or the user already has a warm path: phone.
6. Otherwise LinkedIn when the user is connected, email when not.

**If the pick is email, check the address before drafting.** The `get_contacts` row carries `email_verification` (the last live check: `deliverable`, `risky`, `undeliverable`, `bounced`, `unknown`, or null when never checked) and `email_verified_at`. Call `verify_emails` with the address when the check is null, not `deliverable`, or older than about 30 days. It is free and takes a few seconds. Then:

- `deliverable`: go ahead.
- `risky`: the server accepts mail for any address, so a bounce is possible. Say so and let the user choose email or another channel.
- `undeliverable` or `bounced`: do not use the address. Pick another channel the person has, or `find_contacts` for a colleague.
- `unknown` with a null `verified_at`: the check did not finish. Retry once; if still unknown, treat as `risky`.

State the pick in one line with the reason. The user can override it.

### 3. Check for an existing thread

Whenever the contact has a `linkedin_url`, call `get_linkedin_messages` with it, whichever channel the user is leaning toward. A live LinkedIn thread changes the message on every channel. It tells you whether the two are connected and shows any prior messages.

- Messages exist: stop and call `follow_up`. A cold opener into a live thread is the worst outcome.
- Not connected, no `invite_pending`: a LinkedIn touch means a connection request first, then the DM after acceptance.
- Connected: a DM can go straight out.

BaseBara cannot read the user's email or call history. If a mail connector is in the chat, search it for the revealed address before drafting; a thread there means `follow_up`, not this one. If there is no such connector, ask once whether they have written to or spoken with this person before. If they have, call `follow_up`.

### 4. Draft

Write one message, in the language the user sells in, pitched at the reader's altitude. C-level hears risk and strategy, VP hears performance, Director hears operational pain.

- **Opener, email**: the implication of the hook, in the reader's frame. One sentence. Never open with the event itself.
- **Opener, LinkedIn**: who you are and what you want, in the first line. A DM has no room for warm-up.
- **Opener, phone**: name, company, and the reason for the call in two sentences, then the ask. Write it as the user will say it, not as prose. A voicemail is under 40 words and ends with the user's name and number.
- **Body**: at most one researched detail, reacted to rather than displayed. At most one proof point, attributed and sized to their company. On LinkedIn and on the phone, drop the proof point and the numbers.
- **Close**: one ask.
- **Length**: email 60 to 110 words, follow-ups shorter. LinkedIn DM well under 500 characters. Connection-request note under 200 characters, or none at all. Call opener under 60 words.

Run the final gate on every message. A failed check means rewrite, not tweak:

1. Delete the opener. Does the message still make sense? If not, the personalization was decoration.
2. Does the close say "worth"? Rewrite it. Ask for the thing itself.
3. Does sentence one name a funding round, launch, or hire? Rewrite. Open with what it means for them.
4. Could this go to anyone else this week? Then it is a blast in costume.
5. Is the proof a number or a name? "Teams like yours" is not proof.
6. Read it as the recipient with a full inbox. Any reason to reply beyond politeness?

### 5. Show, then send

Show the user the exact text with the reasoning in two lines: who, why now, which hook, which channel. Wait for approval. Never send text the user has not seen.

- **LinkedIn, connected**: `send_linkedin_message` with the `linkedin_url` and the approved text.
- **LinkedIn, not connected**: `send_linkedin_connection_request`, with a note only if the user wants one. Tell the user the DM goes once the person accepts. Check with `get_linkedin_messages` later before sending it.
- **Email, mail connector in the chat**: send the approved text from it to the revealed address, as a new message with the subject the user approved. BaseBara does not send email itself.
- **Email, no connector**: hand the user the revealed address and the draft.
- **Phone**: hand the user the revealed number and the opener. BaseBara does not place calls. Send an SMS through a connector only when the user asks for a text instead of a call.

A successful LinkedIn send is saved and logged by the server itself: the answer's `contact` block says `event_logged` true, and the `contacted` event, with the message or the request's note as its text, is already on the person. Do not log it again. Only when `event_logged` is false does the answer carry a `next_step`; follow it without asking (`create_contacts` with the `basebara_contact_id`, then `log_contact_event`), since the user already approved the send. When the person later accepts a request, the server logs `connected` on its own the next time their profile is read; `follow_up` finds those.

For email and phone, log `contacted` with channel `email` or `phone` once the send is real: when the mail connector confirms it went out, or when the user says they sent it or made the call. Never when you hand over a draft. Log it in BaseBara even when another tool did the sending; that is where the team looks. If the call turned into a real conversation, log `meeting` instead, with the gist in the user's words. If the person is not saved yet, `log_contact_event` answers `not_in_my_contacts`: call `create_contacts` with the `basebara_contact_id` and re-send the event, without asking.

Both LinkedIn tools are paced to protect the user's account. A pacing refusal (`send_limit_reached`, `already_messaged`, `duplicate_message`, `rate_limited`) is final for now. Report the reason and the wait. Do not retry, do not switch the same message to another channel to get around it, and never queue a list of people to send one by one against the clock.

### 6. Plan the follow-ups

Default cadence when the user has none: 3 to 4 touches on roughly days 0, 3, 8, 14. Every follow-up is shorter than the last, stands alone, and brings a new angle or gets skipped. The last touch is a 25 to 50 word close with the door open and no new pitch.

**Rotate channels on silence.** If the first touch has had no reply after 3 days, the second touch goes out on a different channel the person has: email after a silent DM, LinkedIn after a silent email, phone when both are quiet. A rotation onto email gets the same address check as step 2. A connection request counts as a first touch: leave it pending, do not withdraw it, and send the day-3 touch by email or phone without mentioning the request. Each touch stands alone and never says "I also wrote to you on". Someone with only one channel gets the follow-ups on that one, spaced the same way. If they answer anywhere, the conversation lives on that channel from then on.

So a typical cadence for someone with LinkedIn and a work email is: day 0 DM or connection request, day 3 email, day 8 a short LinkedIn note or a call, day 14 the close on whichever channel drew any signal, or email if none did. This is why step 1 does not filter by channel unless the user insists on one: the row needs to show every way to reach the person.

Draft follow-ups only when the user asks. They send later, after `follow_up` confirms there has been no reply.

## Output shape

```
TARGET     <Name>, <Title> at <Company>   (revealed: email ✓ / phone ✓ / LinkedIn ✓)
HOOK       <one line: why this person, why now>
CHANNEL    <LinkedIn DM — connected / needs connection request | Email — <address> (verified deliverable Oct 3) | Phone — <number>>, because <one clause>
TEAM       <"nobody on your team has reached them" or "contacted by Kev (Sep 12) +1", from last_event>

MESSAGE
<the text, exactly as it will go out, or as the user will say it>

NEXT       Approve to send, or edit. Follow-up planned for day 3 if no reply.
```

## Rules

- Never send a message the user has not approved in this conversation.
- Never invent a signal, a customer, or a stat. Unattributable proof gets cut.
- Never reference tracking-type signals (website visits, profile views, intent data) in the copy.
- Never congratulate as the opener. Infer the consequence instead.
- Reveal only the person the user is about to contact. Do not reveal the whole search result.
- Pick one channel for the first touch and say why. The user can override; the recipe never sends the same message on two channels or through two tools.
- Three days of silence on a first touch, including a connection request, means the next touch goes on another channel the person has. Leave a pending request pending.
- Use the connectors in the chat for email and phone when they exist, with the same approval. BaseBara is still where the touch is logged.
- Never email an address that is `undeliverable` or `bounced`. Verify before the first email when the row's check is missing, not `deliverable`, or older than about 30 days.
- A pacing refusal is final. Report it and stop.
- Log every touch with its channel. A LinkedIn send is saved and logged by the server (follow `next_step` only when the answer says `event_logged` false); an email or a call is logged by you when the user says it happened.
