LinkedIn Outreach Platform Agentic Workflow
Draft Custom Message — LinkedIn Outreach Platform Agentic Workflow
Finds a person's thread via inbox search, clears the compose box, and types caller-supplied message text into it — draft only, never sends
sidebutton install linkedin The mechanical half of outreach: given final message text, put it into the right
compose box and stop. Pairs with linkedin_thread_history and
linkedin_profile_brief (research) or takes text written by hand.
Finds the thread through message search (survives the Focused/Other inbox split),
clears any previous draft via an in-page select-all + delete (LinkedIn's compose is a
React contenteditable — setting textContent directly desyncs its editor state, and
browser.fill does not work on it), then types the text.
This workflow never sends. LinkedIn's compose has press-Enter-to-send enabled by default, so the text is typed without any trailing Enter; a human reviews the box and presses Enter themselves.
Boundary: requires an existing conversation with the person (any 1st-degree contact who ever exchanged a message, including connection notes). For a contact with no thread at all, the profile-page Message overlay renders inside an iframe this schema cannot reach — draft manually there.
Steps
- 1. Navigate to a URL
- url
- https://www.linkedin.com/messaging/?searchTerm={{person_name}}
browser.navigate - 2. Wait
- selector
- main
- timeout
- 12000
browser.wait - 3. Wait
- ms
- 1500
browser.wait - 4. Click an element
- selector
- li[class*="conversation"]:has-text("{{person_name}}")
browser.click - 5. Wait
- selector
- .msg-form__contenteditable
- timeout
- 8000
browser.wait - 6. Click an element
- selector
- .msg-form__contenteditable
browser.click - 7. browser injectJS
- id
- sb-linkedin-clear-compose
- as
- clear_result
- js
- |
browser.injectJS - 8. Type into a field
- selector
- .msg-form__contenteditable
- text
- {{message_text}}
browser.type - 9. Wait
- ms
- 300
browser.wait - 10. control stop
- message
- Draft typed into the thread with {{person_name}} — NOT sent. Review the compose box and press Enter to send. (Compose clear step reported: {{clear_result}})
control.stop
Workflow definition
schema_version: 1
version: "1.0.0"
last_verified: "2026-08-25"
id: linkedin_draft_outreach
title: "Draft Custom Message"
description: "Finds a person's thread via inbox search, clears the compose box, and types caller-supplied message text into it — draft only, never sends"
overview: |
The mechanical half of outreach: given final message text, put it into the right
compose box and stop. Pairs with `linkedin_thread_history` and
`linkedin_profile_brief` (research) or takes text written by hand.
Finds the thread through message search (survives the Focused/Other inbox split),
clears any previous draft via an in-page select-all + delete (LinkedIn's compose is a
React contenteditable — setting textContent directly desyncs its editor state, and
`browser.fill` does not work on it), then types the text.
**This workflow never sends.** LinkedIn's compose has press-Enter-to-send enabled by
default, so the text is typed without any trailing Enter; a human reviews the box and
presses Enter themselves.
Boundary: requires an existing conversation with the person (any 1st-degree contact
who ever exchanged a message, including connection notes). For a contact with no
thread at all, the profile-page Message overlay renders inside an iframe this schema
cannot reach — draft manually there.
category:
level: task
domain: sales
reusable: true
params:
person_name:
type: string
description: Name as shown in the inbox, used for search and thread selection
required: true
message_text:
type: string
description: Final message text to place in the compose box, verbatim
required: true
policies:
allowed_domains:
- "*.linkedin.com"
steps:
- type: browser.navigate
url: https://www.linkedin.com/messaging/?searchTerm={{person_name}}
- type: browser.wait
selector: main
timeout: 12000
- type: browser.wait
ms: 1500
- type: browser.click
selector: 'li[class*="conversation"]:has-text("{{person_name}}")'
- type: browser.wait
selector: .msg-form__contenteditable
timeout: 8000
- type: browser.click
selector: .msg-form__contenteditable
- type: browser.injectJS
id: sb-linkedin-clear-compose
as: clear_result
js: |
(() => {
const box = document.querySelector('.msg-form__contenteditable');
if (!box) return 'compose box not found';
box.focus();
document.execCommand('selectAll', false, null);
document.execCommand('delete', false, null);
return 'cleared';
})();
- type: browser.type
selector: .msg-form__contenteditable
text: "{{message_text}}"
- type: browser.wait
ms: 300
- type: control.stop
message: "Draft typed into the thread with {{person_name}} — NOT sent. Review the compose box and press Enter to send. (Compose clear step reported: {{clear_result}})"