How I Built a Content Pipeline That Publishes Itself
The current setup for this site: I write an article in HTML. I click one button in a local dashboard. The article goes live on the site, social copy is generated by Claude, branded slide images are rendered, a LinkedIn post is scheduled for 9am, and an Instagram reel is queued for 7pm. Total time from draft to fully distributed: under five minutes, most of it waiting for uploads. Here is what it actually took to build.
The pieces
A Python HTTP server — stdlib only, no Flask — running at localhost:7070. A pipeline module with five components: article manager, social generator, slide renderer, video renderer, and publisher. Claude Code CLI called as a subprocess for social copy. Cloudinary for media hosting. Buffer's GraphQL API for scheduling posts. A £4/month VPS running nginx and a Python cron script that handles scheduled publishing.
No frameworks. No build step. No SaaS dashboard I cannot read the code of. Standard library all the way until a specific capability required an external package — PyYAML for config, Pillow for slide rendering, requests for API calls, cloudinary for uploads. The total dependency list is short enough to memorise.
What Claude is actually doing
The social generator calls Claude Code CLI as a subprocess, passing the article HTML as context with a structured prompt specifying the LinkedIn post format and Instagram reel script structure. Claude writes the copy and saves it to a markdown file in a separate directory. The slide renderer then parses that file, extracts the hook line and on-screen captions, and passes them to a Pillow-based visual generator that produces branded PNG slides at the right dimensions.
This is not a complex AI orchestration system. It is one subprocess call that returns text, and Python that parses it. Most AI pipeline complexity exists to sell platform subscriptions, not because the problem requires it. If you can call a CLI and read a file, you have the fundamentals of this pattern.
The part that took longest
Buffer's GraphQL API. The mutation for creating a post has non-obvious field requirements. The schedulingType input is an enum that only accepts "automatic" or "notification" — not "scheduled", which is what you would guess. For specific-time posting, you use mode: "customScheduled" alongside dueAt as an ISO 8601 DateTime string. The mode field is non-null and required even when you include dueAt. The error messages returned when you get any of this wrong are not descriptive. This took three debugging iterations and direct schema introspection to resolve.
Everything else — the publish script, the VPS cron scheduler, the rsync deploy, the sitemap update — worked roughly as expected from first principles. The Buffer API was the only thing that required sustained debugging rather than just reading the docs.
The actual cost
VPS: £4/month (Hetzner CX11, 2 vCPU, 4GB RAM, 40GB disk — vastly more than needed). Cloudinary: free tier, 25GB bandwidth. Buffer: free tier for scheduled posts across two channels. Claude: already paying for the subscription. Total additional infrastructure cost beyond what I was already paying: £4/month.
The time cost was approximately two weekends: one to build the core pipeline, one to wire up the scheduling and debug the Buffer API. This is the honest number. Most "I built this in a day" accounts omit the debugging.
The order that works
If you want to build something similar, the sequence matters. Automate publishing first. Write the script that takes your content and gets it live on your site. Update the index pages. Handle the RSS feed. This is the foundation — everything else plugs into it, and doing it first means you understand your content format before you try to generate social copy from it.
Add social generation second. One subprocess call to Claude CLI, a structured prompt, file output. Parse the output. Test it with a real article before adding more steps.
Add scheduling last. Buffer's free tier handles LinkedIn and Instagram. Their GraphQL API works once you understand the enum structure. Do not start here — you will not know what you are scheduling until the first two steps are solid.
Most content automation advice comes from people selling the automation platform. This is what a weekend, a free Buffer account, and £4/month actually produces. It runs every day without attention. That is what the goal was.
Frequently Asked Questions
Why call Claude Code CLI as a subprocess instead of using the Anthropic API directly?
The CLI handles authentication, model selection, and context management without requiring any SDK setup or API key handling in the pipeline code itself. For a single structured prompt that returns text, the subprocess approach means less code and no additional dependency — the tradeoff is a slight startup overhead per call, which is acceptable when the task runs once per article publish.
What are the exact Buffer GraphQL fields needed to schedule a post at a specific time?
The mutation requires schedulingType set to the enum value 'automatic', plus a scheduleTime object containing mode: 'customScheduled' and dueAt as an ISO 8601 DateTime string. The mode field is non-null and must be included even when dueAt is present — omitting it returns a vague error that does not identify mode as the missing field. Schema introspection via a direct GraphQL query against Buffer's endpoint is the fastest way to confirm current field requirements.
How does the VPS cron script handle publishing if Buffer is already scheduling the social posts?
Buffer handles the social distribution side — LinkedIn and Instagram — while the VPS cron script handles the actual site publish: copying the HTML file into the served directory, updating the sitemap, and triggering any cache invalidation. These are separate concerns on separate timers, so a post can be written and queued in advance with the site publish and social distribution happening independently at their respective scheduled times.
What does Pillow receive from the Claude-generated markdown file to produce the slide images?
The slide renderer parses the markdown output for two specific elements: the hook line used as the headline text, and the on-screen captions list used for sequential slide content. Pillow then composites these text strings onto a base template at predefined coordinates with brand typography and dimensions sized for the target platform. Keeping Claude's output as structured plain text rather than trying to generate images directly makes the visual rendering step fully deterministic and easy to debug.