Shipping the cross‑poster
Description and use
An automated pipeline in GitHub Actions publishes scheduled posts to LinkedIn and dev.to, updates frontmatter, records a ledger entry, and commits the changes, eliminating manual friction.
Problem
Manual cross‑posting to LinkedIn and dev.to becomes tedious after a few posts, causing writers to stop publishing.
Outcome
The pipeline runs hourly, completes in ~21 s on a cold runner, and has successfully published posts to both platforms without manual intervention.
Workflow
- Read schedule.yml and distribution‑ledger.jsonl
- Determine if a scheduled post is due and not already published
- Invoke the appropriate publisher (devto.mjs or linkedin.mjs)
- On success: 1) Append entry to ledger, 2) Mutate frontmatter with status, publishedUrl, postedAt, 3) Commit and push with [skip ci]
Evidence
Cross‑posting manually dies at the third post. Open LinkedIn, strip the markdown, re‑check character count, paste. Open dev.to, remember the canonical URL field, paste the body, slugify the tags, confirm. Each post is fifteen minutes of friction between "I wrote something" and "the people who follow me on those platforms can read it."
The site already deploys on Vercel and the code already lives on GitHub, so the pipeline had to live inside the repo.
Three hundred lines of orchestrator (scripts/publish-due.mjs) read content/blog/schedule.yml, cross‑reference .distribution‑ledger.jsonl, and when a scheduled post is due and not already published, hand it to the matching publisher – lib/distribution/devto.mjs or lib/distribution/linkedin.mjs.
On success, three things happen in a fixed order: 1) Append to the ledger, 2) Mutate the post's frontmatter, 3) Commit and push with [skip ci] so Vercel still rebuilds but the cron origin is filterable in git log
Hourly on 15 * * * *. Twenty‑one seconds end‑to‑end on a cold GitHub Actions runner.
- Record date: 2026-10-01T06:08:13.822Z