Multi-channel outreach
Multi-channel outreach runs a durable sequence of touches against one prospect across email and LinkedIn: an intro email, a LinkedIn connect note, a nudge, and a follow-up DM, spaced out over quiet windows. The moment the prospect replies on either channel, every remaining touch cancels. Every outbound message is approval-gated before it sends, and every touch is logged to the prospect’s CRM page, so a sequence never sends something you didn’t see coming and never leaves a trail you have to reconstruct later.
The outreach.sequence workflow
Section titled “The outreach.sequence workflow”Outreach is a registered durable workflow, seeded as the agents/outreach agent definition. You invoke it like any agent, from the slash menu or via agents.invoke, giving it a prospect page, an email address, and (optionally) an IM handle.
The sequence, in order:
-
Intro email
Sends the intro through
mail.send. If denied, the sequence logs the denial and stops here; nothing else runs. -
Quiet window, watching for a reply
Sleeps for
delay(default 2 days), polling everypollEvery(default 1 hour) for a reply on the intro’s mail thread or an inbound message on the IM conversation. A suspended wait costs nothing while it sleeps, since the workflow engine persists its state and doesn’t hold a worker. -
LinkedIn connect + note
If no reply arrived and an IM conversation is configured, sends a connect note through
im.send. Skipped entirely for an email-only sequence (noimConversationId). -
Quiet window again
Same reply watch as after the intro.
-
Nudge email
Sends a follow-up on the same mail thread as the intro (
Re: <subject>), so it reads as one conversation rather than a cold restart. -
Quiet window again
Same reply watch.
-
Follow-up DM
A final LinkedIn message, if an IM conversation is configured.
If a reply lands on either channel at any point, the sequence stops immediately: it logs the cancellation and ends the run rather than continuing to the next touch. An outbound send that gets denied at its approval gate has the same effect, it cancels everything after it.
Reply detection
Section titled “Reply detection”The workflow watches two independent signals:
- A mail reply in the intro’s thread. The workflow parses the thread id out of the
mail.sendsuccess text and polls for a non-own message in that thread; the account’s ownSENTmessages are excluded so a copy of your own send never looks like a reply. - An inbound IM message in the tracked conversation.
Either one is enough to cancel the rest of the sequence, which is the point of running one sequence across two channels: reaching a prospect somewhere they’ll actually respond, without double-messaging them once they do.
Sending through the IM connector
Section titled “Sending through the IM connector”The im.send({app, conversationId, body}) tool is gated (requires: ['im.send']) and claim-guarded per call, so the same touch never sends twice on a workflow replay. It routes through Subspace’s instant messaging connector layer:
- Slack and Telegram post directly through their APIs.
- WhatsApp, Messenger, and LinkedIn have no first-party send API available to a self-hosted server, so
im.sendqueues a browser job that the browser extension delivers from your own logged-in session the next time it’s active.
imApp defaults to linkedin. Omitting imConversationId altogether skips both IM touches and turns the sequence into an email-only follow-up.
Every send is approval-gated
Section titled “Every send is approval-gated”Each of the four touches routes through the normal tool approval machinery: mail.send and im.send both need explicit approval before they leave your outbox. A pending touch shows up as an approval card in the task queue with the tool name and the drafted content; approving lets that one touch go out, denying it cancels every remaining step in the sequence, it does not just skip that one touch and continue.
Logged to the CRM page
Section titled “Logged to the CRM page”Every touch, the cancellation (if any), and the sequence’s completion append as outreach - ... lines directly on the prospect’s CRM page, using kb.append. Opening a prospect’s page after a sequence has run gives you the full timeline: what was sent, when, whether they replied, and how the sequence ended, without cross-referencing agent run history.
Sequence state in the task queue
Section titled “Sequence state in the task queue”A running sequence is visible in the task queue’s Scheduled runs section, showing the prospect and the sequence’s current status (for example, awaiting approval on the next touch, or sleeping through a quiet window). You can dismiss a pending approval from there to cleanly cancel a sequence early: denying the next queued send records the cancellation and stops the workflow without sending anything further.
Bulk import: CSV and Excel into CRM pages
Section titled “Bulk import: CSV and Excel into CRM pages”Before you can run outreach at any scale you need prospects as CRM pages. The CRM’s bulk import reads CSV, XLS, or XLSX workbooks (via SheetJS) and turns rows into deduplicated person and company pages, up to 5,000 rows per workbook and a 15 MB upload limit.
Column headers are matched case-insensitively against a set of aliases, so a workbook exported from almost any CRM or spreadsheet tool works without reformatting first:
| Field | Recognized headers |
|---|---|
| name | name, full name, contact, person, contact name (or first name + last name) |
email, email address, e mail, work email |
|
| company | company, account, organization, organisation, company name |
| domain | domain, company domain, website domain |
| role | role, title, job title, position |
linkedin, linkedin url, linkedin handle, profile url |
|
| stage | stage, status, pipeline stage |
| kind | kind, type, record type (set to company for a company-only row) |
Every worksheet in the workbook is read; a row with a company name but no person name and no email is imported as a company page on its own. Import runs through identity resolution: an exact email match reuses the existing CRM identity and updates it, and only a genuinely new email creates a new person page. A malformed row (for example, an invalid email) is reported back per sheet and row number without rolling back the rest of the import, so one bad row in a 500-row workbook doesn’t block the other 499.
Import happens from the CRM page (/p/crm): drop a file on the import control and the summary reports how many people and companies were created versus updated. From the resulting list you can select prospects and launch outreach sequences for them directly, one durable outreach.sequence run per selected person, using the CRM person’s stored email and (if present) LinkedIn handle.