Sales Guy
Sales Guy — your AI SDR
Section titled “Sales Guy — your AI SDR”Sales Guy runs full B2B outbound cadences from inside your LinkWorld workspace. You give it an Ideal Customer Profile (ICP) and a list of leads; it researches each company, drafts personalized email touches from your own M365 mailbox, paces sends to protect your domain reputation, triages replies, and pushes qualified prospects into your CRM. No separate seat, no CSV export to a third-party SDR tool, no copy-paste between systems.
What this app does
Section titled “What this app does”- Multi-touch email cadences — a configurable 5-touch sequence (intro → follow-up → value-proof → case-study → breakup) runs on a daily heartbeat. Each touch is personalized from the lead’s enrichment data; no spray-and-pray.
- ICP-Chat campaign setup — you describe your target buyer in
natural language; the
sdragent walks you through the missing criteria and creates the campaign. - Three lead-source paths — pasted CSV, manual company list (with
per-lead enrichment via the
researcheragent), or bulk search via ProxyCurl when configured. - Inbox-aware reply triage — every inbound email that matches a cadence thread gets auto-classified (positive / negative / OOO / info-request / other). Positive replies are drafted as approval- pending records you eyeball before sending; opt-outs hard-suppress.
- Domain-reputation guards — per-user daily cap (default 20) +
hourly burst limit (3) + tenant-configured business-hours window.
send_touch returns
deferred=Trueinstead of sending if any cap fires. - Cross-app handoff to Pipeline — qualified leads land as Pipeline contacts; deal stages auto-react to reply verdicts.
Getting started
Section titled “Getting started”- Connect M365 in Settings → Integrations. Sales Guy uses your
real Outlook mailbox to send touches + read replies — the
mail.read,mail.send,calendar.read,calendar.writescopes prompt at activation time. - Activate Sales Guy from the Marketplace. Grant the requested scopes when the consent dialog appears.
- (Optional) Add API keys via the Settings tab:
proxycurl_key— needed for LinkedIn profile enrichment + bulk lead search. Without it, CSV-paste and manual paths still work.hunter_key— verifies email deliverability before send. Without it, sends go out without a deliverability pre-check.
- Tune the send caps in the same Settings tab if 20/day isn’t right for you. Defaults protect a cold-warmed sender; raise only after you’ve checked the domain’s reputation in Microsoft’s Sender Score / Google Postmaster.
- Install Pipeline (recommended) so qualified leads + deal stages land in a CRM you can actually look at. The bilateral cross-app wire auto-approves at activation time when both apps are installed.
Day-to-day usage
Section titled “Day-to-day usage”Starting a campaign (ICP-Chat)
Section titled “Starting a campaign (ICP-Chat)”Open the Sales Guy chat panel (right-side chrome) and describe your target:
“Let’s start a campaign — DACH B2B SaaS companies, 50–200 employees, decision-maker is the Head of Sales or VP Sales”
The sdr agent enters ICP-Chat mode: it asks 1-2 follow-up
questions at a time (geography, exclusions, trigger signals like
recent funding or hires), summarizes back when ready, and asks for
confirmation. Reply “yes” or “create it”:
“yes, name it DACH SaaS VPs”
→ create_campaign runs, the agent returns the campaign id and asks
how you want to load leads. Status is draft until leads exist.
Importing leads — path 1: CSV-paste
Section titled “Importing leads — path 1: CSV-paste”The simplest path. Paste rows directly into chat, any CSV-ish format the LLM can read works:
email,name,company,role[email protected],Alice Schmidt,Scaleup GmbH,VP Sales[email protected],Bob Lehner,Growth AG,Head of Sales[email protected],Carla Mayer,Boostly SE,Sales Director“import these into DACH SaaS VPs”
→ import_leads_to_campaign dedupes on (campaign_id, email),
returns {created: N, skipped: M, total: T}. Idempotent — pasting
the same rows twice safely no-ops the duplicates.
Importing leads — path 2: Manual companies + researcher
Section titled “Importing leads — path 2: Manual companies + researcher”When you have target companies but no contact list, name them:
“I want to target Personio, HelloFresh, and Celonis. Find me their Head of Sales.”
The sdr delegates to the researcher agent (per-lead enrichment
via ProxyCurl + Hunter). Researcher returns 3-5 facts per company +
2-3 per persona + one personalized opening hook. sdr then calls
add_lead for each.
Importing leads — path 3: ProxyCurl bulk search
Section titled “Importing leads — path 3: ProxyCurl bulk search”When configured, you can describe an ICP and let ProxyCurl find
matching profiles in bulk. (Wired via enrich_lead per-profile
today; leads.search platform skill for true bulk is on the
roadmap — Issue #78.)
Sending the first touches
Section titled “Sending the first touches”Once a campaign has leads, you can ask explicitly:
“send the first touch to [email protected]”
→ send_touch runs the guard chain: suppression check → business
hours → daily cap → hourly cap → email_send from your M365 mailbox
→ records the touch + emits touch_sent. Returns {sent: true, used: N, cap: 20, ...} or {deferred: true, reason: ...}.
For the autonomous run, just let the daily cadence heartbeat do its thing — fires at 09:00 (tenant-local UTC), walks every active campaign + lead, decides per-lead whether to send today.
Triaging replies
Section titled “Triaging replies”When someone replies to a cadence touch, the platform routes the
inbound to Sales Guy’s on_inbound handler. The agent:
- Matches the inbound to a prior touch via
channel_thread_id - Classifies the reply:
positive/negative/ooo/info_request/other - Acts per verdict:
- positive → drafts a meeting-request reply, queues it as
draft_replywithstatus=pending_approval. You see it in the Inbox tab. - negative → emits
opt_out_recorded, suppresses in Pipeline, does NOT auto-reply. - ooo → snoozes the cadence by 7 days.
- info_request / other → logs the reply; next heartbeat decides what to do.
- positive → drafts a meeting-request reply, queues it as
You approve / discard drafts manually — Sales Guy never auto-sends risky content.
Cross-app handoff to Pipeline
Section titled “Cross-app handoff to Pipeline”When sdr qualifies a lead (after positive reply or manual
qualification), it calls register_qualified_lead. That fires three
things:
- Posts the contact to Pipeline via the cross-app RPC contract
(
contact_create: email=..., name=..., company=...). Pipeline returns the contact_id. - Emits
lead_qualifiedfor any other subscribed apps (reporting, analytics, your own webhook bot). - Returns the Pipeline contact_id so subsequent
send_touchcalls can carry it ascontact_id.
The full contract is in docs/strategy/sales-guy-pipeline-contract.md.
Sales Guy degrades gracefully when Pipeline isn’t installed —
pipeline_rpc returns a soft forbidden, the lead stays
Sales-Guy-local, and the local suppression list still works.
What to expect
Section titled “What to expect”- Cadence ticks daily at 09:00 UTC — adjust the
business_hoursinstall_setting if your tenant is in a non-UTC zone (full timezone support lands in a later milestone). - 20 sends/user/day default cap — raise via the
daily_send_cap_per_userinstall_setting only after warming the sending domain. Domain-reputation damage takes weeks to recover. - 3 sends/hour burst cap — fingerprint protection, not configurable today.
- Every personalized body must reference a verifiable fact — the
sdragent refuses generic flattery; this is a hard rule in its prompt, not a policy you toggle. - Risky outbound (quotes, contracts, meeting confirms) never
auto-sends — even if
sdrdrafts it, it lands aspending_approvaland you sign off in the Inbox tab. - All app-private state lives in
ctx.kv— campaigns, leads, touches, replies, drafts, suppression. Tenant-scoped, isolated from other tenants on the same instance.
Troubleshooting
Section titled “Troubleshooting”“The agent didn’t import my CSV” — paste should be inside the same chat turn that says “import these”. If you paste first and ask later, the prior turn’s context is what the agent acts on; try again with both in one message.
“send_touch keeps returning deferred” — check the reason:
outside_business_hours→ yourbusiness_hourswindow blocks current UTC timedaily_cap_reached→ wait until tomorrow OR raise the caphourly_cap_reached→ wait 1 hoursuppressed_local→ the email is on your suppression list; remove it viadiscard_draft_replyflow if intended
“My Pipeline contact didn’t appear” — check that Pipeline is
installed AND active on your tenant, AND that the cross-app wire
shows “approved” in Workspace Control. Without the wire, pipeline_rpc
returns forbidden and the lead stays Sales-Guy-local — Sales Guy’s
own records aren’t lost, just not synced.
“Replies aren’t being triaged” — verify M365 is connected with
mail.read granted, and the tenant has opted into
app_lifecycle_fanout so inbound emails reach Sales Guy’s
on_inbound handler (Workspace Control → Channels). Without
fanout, the platform routes inbound to the default Steward agent and
Sales Guy never sees the reply.
“Domain reputation is dropping” — Sales Guy can’t fix bad
infrastructure. Verify SPF/DKIM/DMARC, switch to a warmed secondary
sending domain (see Sending at scale
for the playbook), and reduce daily_send_cap_per_user until
deliverability recovers.
Reference
Section titled “Reference”This section is auto-generated from the app’s manifest. It updates whenever the app publishes a new version.
Required scopes
Section titled “Required scopes”mail.readmail.sendcalendar.readcalendar.writetasks.create
Install settings
Section titled “Install settings”| Key | Label | Type | Required |
|---|---|---|---|
proxycurl_key | ProxyCurl API key | secret | no |
hunter_key | Hunter.io API key | secret | no |
daily_send_cap_per_user | Max emails per user per day | number | no |
business_hours | Sending window (tenant-local) | select_or_custom | no |
Agents
Section titled “Agents”- Sales Guy (default)
- Lead Researcher
list_app_records— Read-only view into the app’s private ctx.kv records. record_typecreate_campaign— Create a new outbound campaign with an ICP description + filterimport_leads_to_campaign— Batch import N leads to a campaign in one call. Passleadsas aadd_lead— Manually add ONE lead to a campaign. Idempotent onsend_touch— Execute ONE outbound email touch. Enforces daily/hourly (scopes:mail.send)register_qualified_lead— Push a qualified lead to the Pipeline app (idempotent on email).approve_draft_reply— Approve a drafted reply (from on_inbound) and send it via (scopes:mail.send)discard_draft_reply— Discard a drafted reply without sending. Marks status=‘discarded’
Emitted events
Section titled “Emitted events”lead_qualifiedmeeting_bookedopt_out_recordedtouch_sent