How to post — from draft to measured result

The LinkedIn workspace closes the loop around every post: draft → schedule → publish → measure. This is the operating manual for that loop.

1 · Draft it with /post

Open the Posts space — the publishing hub where drafts live — start a thread (chat panel → +), and type:

/post a short post about <your topic>

The /post skill (Skill/post) walks the draft through the structure that works:

  1. Hook (line 1–2) — one sharp claim, number or tension that earns the "…more" tap. No warm-up.
  2. Body — one idea, developed. Short paragraphs, line breaks, specifics over adjectives.
  3. Takeaway — the one thing to remember, stated plainly.
  4. Call to action — invite a reply, not a like. One CTA, not three.

Keep it ~120–250 words. Never use hashtags — no exceptions. Start from your own data: the Analytics Calendar shows which past themes actually drew reactions, comments and connections — write in a proven vein.

⚠️ Known issue: freshly deployed Skill nodes may answer "Unknown command" until the portal's skill projection refreshes (cache bug, reported). The skill's guidance above works standalone.

2 · Capture the draft as a node

Create a Markdown node under Posts/Drafts/<slug>name = working title, description = one-sentence abstract, body = the full draft. That makes it reviewable, and future drafts will appear on the Calendar's future days once the Post node type lands.

3 · Publish — three routes

A. By hand (default)

Copy the draft into LinkedIn → Post. To time it, use LinkedIn's native scheduler: in the share box click the clock icon, pick date + time, Next, then Schedule. A scheduled post is invisible to everyone until its time — which makes it perfect for testing (see §4).

B. Scheduled via the automated UI flow (the pilot)

The repo ships a Playwright flow that operates LinkedIn's own UI end-to-end:

cd e2e
npm install
npm run login            # one-time: sign in to LinkedIn (+ memex) in the opened browser
npm run schedule-pilot   # schedules a 🧪-marked test post ~3 days out, 9:00 AM

It is double-gated (RUN_LINKEDIN=1 is set by the script; a plain test run never touches linkedin.com) and uses your own session from the one-time login.

Scheduling a REAL post (linkedin-schedule-real.spec.ts) uses the same machinery for an actual publication — text and image, into LinkedIn's native scheduler, held by LinkedIn's infrastructure (survives laptops, sessions and weekends):

cd e2e
# 1. write artifacts/schedule-payload.json — gitignored; post CONTENT never lives in the repo:
#    { "text": "…exact post text…", "image": "artifacts/deliverable.jpg",
#      "date": "8/3/2026", "time": "11:00 AM", "meshNode": "Posts/WhatIsADeliverable" }
# 2. stage the image next to it (e.g. curl the mesh's /static asset into artifacts/)
npm run schedule-real

The flow types the exact text (blank-line rhythm preserved), attaches the image through an intercepted file chooser (no OS dialog), then tries LinkedIn's scheduler dialogs automatically. LinkedIn's dialog chrome is the flaky part — so on any misstep it degrades gracefully: the armed composer stays open, the console prints the remaining clicks (clock icon → date/time → Next → Schedule), and the run keeps watching up to 10 minutes for LinkedIn's confirmation. The content is always machine-exact; only the final navigation may fall back to a human.

Triple-gated: RUN_LINKEDIN_REAL=1 (set by the npm script), the payload file must exist, and the run is headed so every step is visible and abortable. A scheduled post stays invisible and deletable ("View all scheduled posts") until its slot — even a successful run is reversible.

After the post goes live at its slot, set the mesh node to Published (the workflow button) so the timeline reflects reality.

C. Via the LinkedIn API

scripts/linkedin-publish-pilot.py posts through the REST API — immediately (the API has no native scheduling; mesh-side timing arrives with the core scheduler):

export LI_ACCESS_TOKEN=...   # token with w_member_social + openid — keep it out of chats/repos
python3 scripts/linkedin-publish-pilot.py                    # dry-run: shows the exact request
python3 scripts/linkedin-publish-pilot.py --live             # actually publish the test post
python3 scripts/linkedin-publish-pilot.py --delete "<urn>" --live   # remove it again

Without the token the script is a silent no-op; without --live nothing leaves your machine.

4 · Testing safely (the pilot protocol)

  1. The post text is unmistakably a test — 🧪-marked, timestamped, announcing its own removal.
  2. Schedule it in the future (route A or B) so it is never publicly visible.
  3. Verify it exists: LinkedIn → Start a post → clock icon → View all scheduled posts.
  4. Delete it before its time: same list → ⋯ → Delete. (Route C: --delete <urn> --live.)

5 · Measure

After your next data-archive import (scripts/linkedin-ingest.py), the post appears on the Calendar with its attributed 👍 reactions, 💬 comments and 🔗 new connections; Statistics puts it against your baseline. Double down on what resonated.

Reach caveat: per-post impressions and received likes are not in LinkedIn's export — the calendar attributes engagement by time. True reach needs LinkedIn's creator analytics export or the API with analytics scopes.

Roadmap

Reconnecting…
The server was updated. Reloading the page to pick up the latest version.