For agents: the plain-markdown version of this recipe is at /recipes/reach-out.md.

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.