Automated LinkedIn Pipeline
Autonomous content scheduler
01 · Executive Summary
Showing up consistently on LinkedIn is a content problem disguised as a discipline problem. Manual posting is the first thing to slip during a busy sprint, and SaaS schedulers charge a subscription to do something a cron job can do for free — while holding your account credentials on their servers.
The Automated LinkedIn Pipeline is a self-hosted agent that lives entirely in a GitHub repository. On a schedule, a GitHub Actions runner spins up, asks the Gemini API to draft a technical post, publishes it, and records what it shipped. No server, no subscription, and credentials never leave GitHub's encrypted secrets store.
Who it's for: developers and technical founders who want a reliable, hands-off publishing cadence without paying for — or trusting — a third-party scheduler.
02 · The Stack
- Language
- Python — the agent, prompt assembly, and publishing client.
- Runtime / CD
- GitHub Actions — scheduled ubuntu-latest runner, zero infra.
- Model
- Gemini API — drafts each post from a queued topic.
- Scheduling
- cron — 0 10 * * 2,4 (Tuesday & Thursday, 10:00 UTC).
- State
- published.json — committed back to the repo for idempotency.
- Secrets
- GitHub Actions encrypted secrets — keys injected at runtime only.
03 · System Architecture Flow
- 1Cron Trigger
GitHub Actions fires on schedule: 0 10 * * 2,4 (Tue & Thu, 10:00 UTC).
- 2Runner Boot
ubuntu-latest checks out the repo and installs Python dependencies.
- 3Topic Select
The agent reads the next queued topic and skips anything in published.json.
- 4Gemini Draft
The Gemini API generates a formatted, on-brand technical post.
- 5Publish
The post is pushed to LinkedIn; the entry is recorded as published.
- 6State Commit
published.json is updated and committed back to the repository.
04 · Deep Technical Breakdown
The cron scheduler: 0 10 * * 2,4
The whole pipeline is triggered by a single workflow schedule. The five-field expression reads field-by-field as: minute 0, hour 10, any day-of-month, any month, on days-of-week 2,4 — Tuesday and Thursday. GitHub evaluates cron in UTC, so this is a dependable twice-weekly 10:00 UTC run with a manual workflow_dispatch escape hatch for ad-hoc posts.
on:
schedule:
- cron: "0 10 * * 2,4" # Tue & Thu, 10:00 UTC
workflow_dispatch: {} # manual run from the Actions tabPython + Gemini integration
The agent selects the next topic, assembles a structured prompt, and calls Gemini for a draft constrained to a house style and length. The model call is the only network dependency for content generation; everything else is local file work on the runner.
State tracking via published.json
Idempotency is the hard part of any scheduler — a retried or duplicated run must never double-post. The agent treats published.json as an append-only ledger: before publishing it checks the ledger, and after a successful post it appends the entry and commits the file back to the repo. Because the state lives in the repository, history is auditable in git log and there is no external database.
published = json.loads(LEDGER.read_text())
topic = next(t for t in topics if t["id"] not in published)
post = gemini.generate(prompt_for(topic))
publish_to_linkedin(post) # post first...
published[topic["id"]] = {"date": today} # ...then record
LEDGER.write_text(json.dumps(published, indent=2))Secure repository secrets
The Gemini key and LinkedIn credentials are stored as GitHub Actions secrets, injected into the job's environment only at run time and masked in logs. They are never committed, never printed, and never reach a third party — the runner is destroyed when the job ends.
- name: Publish
env:
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
LINKEDIN_TOKEN: ${{ secrets.LINKEDIN_TOKEN }}
run: python scripts/publisher.pyExplore the source
The agent, the workflow, and the published.json ledger.