Multi-channel outreach
Multi-channel outreach is provided by the bundled outreach plugin. It 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.
The bundled outreach plugin
Section titled “The bundled outreach plugin”The plugin ships the agents/outreach agent definition and a version-pinned workflow exposed through the outreach/sequence alias. The bundled plugin sweep installs and enables both on fresh homes and when an existing home first receives the extraction. You invoke the definition 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 workflow itself lives in the plugin; the host still owns the durable engine, approval gates, effect claims, and the public mail and IM tools. A run pins the installed plugin version when it starts, so a later plugin upgrade does not change the sequence already in flight.
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. -
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 logs the cancellation and ends the run immediately. 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 records a run-start timestamp, then polls two public, read-only tools during each quiet window:
mail.threadState({threadId})reports the latest inbound and outbound messages in the intro’s mail thread. The thread id comes frommail.send’s typed result data, and the account’s ownSENTmessages are excluded from inbound state.im.threadState({app, conversationId})reports the newest stored message in the tracked conversation aslastInbound. Outbound connector sends do not land in the IM store, so a stored message is the counterparty talking.
An inbound timestamp strictly newer than the run-start timestamp cancels the rest of the sequence, so older thread history cannot stop a new run and the prospect is not double-messaged once they respond.
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, not just that one touch.
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. The prospect’s page carries the full timeline: what was sent, when, whether they replied, and how the sequence ended, with no agent run history to cross-reference.
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.
Disable or re-enable outreach
Section titled “Disable or re-enable outreach”plugins.disable {name: 'outreach'} leaves the installed version, the agents/outreach page, CRM touch logs, and other user data in place. It removes the plugin’s current workflow alias, so new outreach invocations fail cleanly once the plugin-state event has been processed. Already-started runs keep their pinned workflow bundle available and can finish or resume.
plugins.enable {name: 'outreach'} verifies the plugin’s dependencies and restores the alias and agent in place. No reinstall or data migration is needed.
Bulk import: CSV and Excel into CRM pages
Section titled “Bulk import: CSV and Excel into CRM pages”Outreach at scale needs 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.
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.