LinkedIn Message Writer
The agent writes each personalized message; the bundled scripts deterministically
lint character limits and export the tool CSV. Research uses the
linkedin-profile-post-scraper capability (Apify, or its keyless Playwright cookie degrade).
When to use
- "Write LinkedIn messages" / "personalized connection requests / InMails / DMs for these leads."
- Given LinkedIn URLs + a goal/angle, produce per-lead messages.
- The drafting half of
linkedin-outreach; downstream of linkedin-commenter-extractor,
linkedin-influencer-discovery, kol-discovery.
How it works (agent steps)
- Intake leads (LinkedIn URLs),
message_type, account_tier (free/premium — needed
for connection requests), goal, angle, tone, export tool.
- Research each lead — run
linkedin-profile-post-scraper on each URL for profile +
recent posts (Apify if APIFY_API_TOKEN set, else web-automation + cookie, else write
from user-supplied context only). Skip post-scrape for very large lists; personalize on
headline alone.
- Write the message for the chosen type, leading with the signal/relevance, within the
per-type limit:
- connection_request: 200 (free) / 300 (premium)
- inmail: subject 200 + body 1900
- dm / message_request: 8000
- post_comment / comment_reply: 1250 (must add value — never "Great post!")
- Lint the drafts (see below); rewrite — never truncate — anything over limit.
- (sequence) Order CR -> value-first DM -> social-proof DM -> breakup with timing.
- Export the tool CSV.
How to run
Lint drafted messages (Python 3 stdlib, no install):
python3 ${SKILL_DIR}/scripts/linkedin_lint.py --input ${WORKSPACE}/messages.json
Message JSON — one object or a list:
{"linkedin_url":"https://www.linkedin.com/in/x","name":"Ada","message_type":"connection_request",
"account_tier":"free","message":"..."}
Export to a tool-ready CSV:
python3 ${SKILL_DIR}/scripts/export_csv.py --input ${WORKSPACE}/messages.json \
--tool phantombuster --output ${WORKSPACE}/linkedin_outreach.csv
--tool: generic | dripify | expandi | botdog | phantombuster. The linter exits
1 on any over-limit message (rewrite, don't truncate).
Outputs
- Per-lead messages
{linkedin_url, name, company, title, message(s), char_count, personalization_notes}, validated against type limits.
- A tool-ready CSV (per-tool column mapping) to workspace / channel attachment.
Credentials / env
env.required: none. The writing is done by the host agent; the linter + exporter are
stdlib Python. No paid service is applicable to the drafting/lint/export steps themselves.
env.optional: APIFY_API_TOKEN — only for the delegated research step via
linkedin-profile-post-scraper: if set → that skill's cookieless Apify actor; else → its
keyless Playwright li_at-cookie degrade, or write from user-supplied context only (no
fresh research). The fallback lives in the delegated skill, not here.
Notes & edge cases
- The connection-request limit is the gatekeeper — strictly 200 (free) / 300 (premium);
rewrite to fit. Comments must add value, never filler.
- This skill produces the CSV — it does not send. Sending is
linkedin-outreach /
phantombuster.
- Research depth scales cost (Apify ~credits per profile/post); for huge lists, skip the
post scrape and personalize on headline only.
1---2name: linkedin-message-writer3description: Research LinkedIn leads (profile + recent posts) and write personalized messages for any LinkedIn message type — connection request, InMail, DM, message request, post comment, or comment reply — respecting each type's character limit, then export tool-ready CSVs. Use given a list of LinkedIn URLs and a goal/angle. The drafting half of linkedin-outreach.4---56# LinkedIn Message Writer78The **agent writes** each personalized message; the bundled scripts deterministically9**lint character limits** and **export the tool CSV**. Research uses the10`linkedin-profile-post-scraper` capability (Apify, or its keyless Playwright cookie degrade).1112## When to use1314- "Write LinkedIn messages" / "personalized connection requests / InMails / DMs for these leads."15- Given LinkedIn URLs + a goal/angle, produce per-lead messages.16- The drafting half of `linkedin-outreach`; downstream of `linkedin-commenter-extractor`,17 `linkedin-influencer-discovery`, `kol-discovery`.1819## How it works (agent steps)20211. **Intake** leads (LinkedIn URLs), `message_type`, `account_tier` (free/premium — needed22 for connection requests), goal, angle, tone, export tool.232. **Research each lead** — run `linkedin-profile-post-scraper` on each URL for profile +24 recent posts (Apify if `APIFY_API_TOKEN` set, else web-automation + cookie, else write25 from user-supplied context only). Skip post-scrape for very large lists; personalize on26 headline alone.273. **Write the message** for the chosen type, leading with the signal/relevance, within the28 per-type limit:29 - connection_request: **200 (free) / 300 (premium)**30 - inmail: subject **200** + body **1900**31 - dm / message_request: **8000**32 - post_comment / comment_reply: **1250** (must add value — never "Great post!")334. **Lint** the drafts (see below); rewrite — never truncate — anything over limit.345. (sequence) Order CR -> value-first DM -> social-proof DM -> breakup with timing.356. **Export** the tool CSV.3637## How to run3839Lint drafted messages (Python 3 stdlib, no install):4041```bash42python3 ${SKILL_DIR}/scripts/linkedin_lint.py --input ${WORKSPACE}/messages.json43```4445Message JSON — one object or a list:4647```json48{"linkedin_url":"https://www.linkedin.com/in/x","name":"Ada","message_type":"connection_request",49 "account_tier":"free","message":"..."}50```5152Export to a tool-ready CSV:5354```bash55python3 ${SKILL_DIR}/scripts/export_csv.py --input ${WORKSPACE}/messages.json \56 --tool phantombuster --output ${WORKSPACE}/linkedin_outreach.csv57```5859`--tool`: `generic` | `dripify` | `expandi` | `botdog` | `phantombuster`. The linter exits60**1** on any over-limit message (rewrite, don't truncate).6162## Outputs6364- Per-lead messages `{linkedin_url, name, company, title, message(s), char_count,65 personalization_notes}`, validated against type limits.66- A tool-ready CSV (per-tool column mapping) to workspace / channel attachment.6768## Credentials / env6970- `env.required`: none. The writing is done by the host agent; the linter + exporter are71 stdlib Python. **No paid service is applicable to the drafting/lint/export steps themselves.**72- `env.optional`: `APIFY_API_TOKEN` — only for the *delegated* research step via73 `linkedin-profile-post-scraper`: **if set → that skill's cookieless Apify actor; else → its74 keyless Playwright `li_at`-cookie degrade, or write from user-supplied context only** (no75 fresh research). The fallback lives in the delegated skill, not here.7677## Notes & edge cases7879- The connection-request limit is the gatekeeper — strictly 200 (free) / 300 (premium);80 rewrite to fit. Comments must add value, never filler.81- This skill produces the CSV — it does **not** send. Sending is `linkedin-outreach` /82 phantombuster.83- Research depth scales cost (Apify ~credits per profile/post); for huge lists, skip the84 post scrape and personalize on headline only.