P230 23:31:30 13 FEB
AUTOMATING LINKEDIN CROSS-POSTING
05 AUG 2026

I’ve been writing posts here for quite a while, and during that time the site has grown to cover a fairly wide mix of topics. I felt it would be nice to share that work with a larger audience, so I decided to implement a CI/CD pipeline for automatically cross-posting to LinkedIn.

Implementation

I originally considered using Zapier for this, but the free tier did not offer the functionality I needed, so I built a small publishing pipeline myself using GitHub Actions.

The action uses the site’s RSS feed to discover published posts in order, then reads metadata from each post to decide whether it should be shared and how the LinkedIn update should be formatted. Since my blog contains some articles that are not relevant for a professional audience, I needed a way to control which posts are shared. I solved this by adding a filter so posts only become eligible if I have marked them with a cross_post: true flag.

I also wanted it to work through the historical backlog of older posts, but gradually. Rather than posting everything at once, the scheduled workflow picks the earliest eligible post that has not already been shared and publishes a single update once a week. To avoid duplicate cross-posts, each post is keyed with a stable syndication_id UUID, and the workflow uses these IDs to record what has already been reposted. This UUID approach means that even if I rename an old post, the workflow will not publish it twice.

The publishing record lives in a CSV table on a dedicated Git branch called metrics. I first used this branch-based storage pattern when adding site size metrics, so it was a natural fit here too. Although this storage approach is not optimised for performance, it is free and sufficient for infrequent reads and writes.

Testing

One problem that came up during testing was formatting. I initially used the RSS description of each post as the LinkedIn content, but Hugo’s RSS output can include rendered HTML and image markup. That produced a messy LinkedIn post rather than a concise summary. The fix was to keep using RSS for ordering, URLs, and publication dates, while taking the postable content directly from the source local front matter description instead.

There were a few other small decisions that made the automation safer to operate. The script only records a post in the CSV after LinkedIn returns a successful response and validates that it is about to post a URL from my own domain. It also supports a dry-run mode so I can preview the exact LinkedIn post content before publishing. I also added unit tests for RSS parsing, front matter filtering, and post formatting. As a guard against publishing something ugly, the workflow requires these tests to pass before it executes the publishing step.

Reflection

The final workflow is a scheduled GitHub Action backed by a Python script. It runs at 5pm on Fridays using GitHub’s timezone-aware cron syntax, reads /posts/index.xml, compares the discovered posts against a record table of previous cross-posts, publishes one concise LinkedIn update, and records the returned LinkedIn post URN after a successful response.

It’s a good balance between ease of use and functionality, with safeguards built in. In the future, I may consider adding an AI step for both writing more detailed post templates and additional quality control checks.

View the project on GitHub