Fully automating our social media posting was supposed to free up real time each week — pull the latest blog post, generate a caption, post it at a fixed optimal time, done. It did save time, and within about a month, engagement on our posts had dropped noticeably enough that a teammate flagged it before we'd even looked at the numbers ourselves: "did something change with the posts, they feel kind of off lately." She was right, and the actual cause wasn't a single obvious mistake — it was several small automation-driven patterns compounding into something that read as robotic even though no individual post was wrong on its own.
The original fully-automated pipeline
on_new_blog_post_published ->
caption = generate_caption(post.title, post.excerpt)
short_link = create_short_link(post.url)
schedule_post(caption + " " + short_link, time: "10:00 AM")
Straightforward, and each individual piece worked correctly — the caption generator produced grammatically fine captions, the link shortener worked, the scheduler posted reliably at 10am every time. Nothing here has an obvious bug. The problem was the aggregate pattern across many posts over time, which no single post's review would have caught, because each individual post looked fine in isolation.
Pattern 1: identical timing, every single time
Every post going out at exactly 10:00 AM, without exception, is a pattern a platform's own algorithm and a human scrolling feed both eventually notice, even if neither can articulate exactly why a feed full of posts all landing at the same round-numbered time each day starts to feel mechanical. Real accounts posting manually have natural variation in timing — sometimes 9:47, sometimes 11:15 — that a rigid fixed schedule doesn't have by construction.
// Before: fixed time, every post
schedule_post(content, time: "10:00 AM")
// After: randomized within a sensible window
const postTime = randomTimeBetween("09:30", "11:00");
schedule_post(content, time: postTime);
A small, almost trivial-sounding change, and it was the single easiest fix to ship — randomizing the exact posting minute within a reasonable window using the same kind of seeded randomness covered hands-on in our random number generator, just applied here to picking a natural-feeling post time instead of a number for a person to read directly.
Pattern 2: identical caption structure, every single time
The caption generator's prompt always produced the same shape: a hook sentence, a one-line summary, then the link. Reading ten consecutive posts back to back made the template obvious in a way no single post did on its own — "New post: [title]. [one-sentence summary]. Read more: [link]" with only the bracketed parts changing, post after post after post.
// Before: one fixed caption template, every time
caption = `New post: ${title}. ${summary}. Read more: ${shortLink}`;
// After: several genuinely different structures, picked per post
const templates = [
() => `${hookQuestion}\n\n${summary}\n\n${shortLink}`,
() => `${title}\n\nHere's what we found: ${keyInsight}\n\n${shortLink}`,
() => `${summary}\n\nFull breakdown: ${shortLink}`,
() => `We just published something on ${topic}. ${hookQuestion}\n\n${shortLink}`,
];
const caption = templates[randomIndex(templates.length)]();
Four genuinely different structural shapes, picked at random per post, immediately broke the pattern that made ten consecutive posts read as obviously templated when viewed together. No individual post changed dramatically in quality — the fix wasn't "write better captions," it was "stop writing the exact same caption shape every single time."
Pattern 3: no acknowledgment of anything happening outside the blog
The fully-automated pipeline only ever posted about new blog content — nothing reacting to a relevant industry conversation, nothing referencing something timely, nothing that wasn't a direct, mechanical output of "a new post exists." Real accounts that feel human post about things beyond their own content on a predictable schedule; an account that only ever announces its own new posts, forever, reads as a content-distribution pipe rather than an account with anything resembling a voice or a point of view.
The fix here wasn't more automation — it was explicitly less. We kept the blog-post-announcement automation for the baseline cadence, and added a manual, human-written post roughly twice a week for anything timely or reactive, deliberately not automated, specifically because the whole value of that category of post is that it isn't a mechanical output of a trigger firing on a schedule.
Checking caption length against each platform's actual limits
A smaller but real issue surfaced while building the varied templates: some of the new template structures occasionally exceeded a target platform's effective character limit for optimal display before truncation, which none of the original fixed-template posts had ever risked since that one template had been sized conservatively from the start. Running each generated caption through a word counter as a validation step before scheduling — checking character count specifically, not just word count — catches this before a post ships truncated mid-sentence on a platform with a tighter limit than the one the caption was drafted with in mind.
function validateCaptionLength(caption, platform) {
const limits = { twitter: 280, linkedin: 3000, mastodon: 500 };
if (caption.length > limits[platform]) {
throw new Error(`Caption exceeds ${platform} limit: ${caption.length}/${limits[platform]}`);
}
}
What engagement actually looked like after the fixes
We don't have a rigorously controlled before/after — too many other variables changed over the same period to isolate automation format as the sole cause with real statistical confidence. What we can say honestly: engagement metrics recovered to roughly their pre-automation baseline within about six weeks of shipping the timing randomization and caption variation, and the manual reactive posts specifically tended to outperform the automated announcement posts on average, which reinforced that the "add real human posts back in" piece of the fix mattered as much as the two purely mechanical randomization fixes did.
A related failure mode worth knowing about in advance
This is a close relative of a different automated-content bug covered in automating a weekly report with AI — both are cases where an automated pipeline produced output that was technically correct at the level of any single instance, and only revealed itself as a real problem once you looked at the pattern across many instances rather than any one output in isolation. A single robotic-feeling caption is a non-issue; forty of them in a row, in the exact same shape, is a pattern an audience notices even when no individual post is identifiably wrong on its own terms.
What we kept fully automated, deliberately
- The core "new post published, announce it" trigger stayed automated — it's genuinely routine, time-sensitive, and doesn't benefit meaningfully from a human writing it fresh each time, provided the output has enough real variation.
- Link shortening stayed automated via the same URL shortener the pipeline always used — nothing about the robotic-feeling problem was actually about the links themselves, so there was no reason to touch that piece.
- Timing and caption structure got randomized, not eliminated — automation stayed, the rigid uniformity within that automation didn't.
- Reactive, timely posts became explicitly manual, on purpose, because that specific category's entire value depends on not being a scheduled, predictable output.
The actual lesson wasn't "automation makes things feel robotic" — it's that automation faithfully repeats whatever pattern you give it, indefinitely, and a pattern that's perfectly fine once becomes obviously mechanical the fortieth time a human scrolls past it in a feed. The fix was never less automation. It was building enough deliberate variation into what got automated that the repetition stopped being visible.
How we now review a template before it ships, to catch the pattern earlier
The actual process change that came out of this, beyond the specific code fixes, was adding a deliberate review step before any new caption template or automation rule ships: generating ten sample outputs from the proposed template back to back and reading them consecutively, the same way an actual audience eventually would, rather than reviewing a single example in isolation the way it's naturally tempting to do when you're the one writing the template. A single sample almost never reveals a repetition problem, precisely because repetition is a property of the pattern across many instances, not a property visible in any one instance on its own — the exact same blind spot that let the original 10:00 AM fixed-time pattern ship without anyone noticing it in review, since nobody had looked at ten consecutive scheduled times side by side before it went live.
A smaller detail that mattered more than expected: varying which platform gets which format
Beyond timing and caption structure, we also noticed the same exact caption, verbatim, cross-posted identically to every connected platform — Twitter, LinkedIn, Mastodon — which reads as obviously automated to anyone who happens to follow the account on more than one platform and notices the identical wording appearing everywhere at once. The fix mirrors the caption-template fix: platform-specific framing pulled from the same underlying content, rather than one caption blasted unchanged everywhere.
const platformCaption = {
twitter: shortHookVersion,
linkedin: longerContextVersion,
mastodon: mediumVersionWithHashtags,
};
Nothing here is complex — it's the same underlying insight as the caption-template fix, applied to a second dimension (platform) rather than just varying content within a single platform's feed.
Deciding how much variation is actually enough
An honest open question we don't have a fully settled answer to: four caption template shapes and a randomized posting window measurably fixed the specific problem that was flagged, but there's no obvious formula for how much variation is "enough" versus how much starts becoming its own kind of inconsistency that reads as scattered rather than natural. Our rough internal rule ended up being to add a new template variant only once an existing one had been used often enough that a careful, repeat follower could plausibly start recognizing it as a pattern — which in practice has meant revisiting the template library every couple of months rather than trying to solve for infinite variation up front, on a rough guess that most real accounts don't need dozens of distinct shapes, just enough that the automation isn't visibly the same four things forever without ever adding anything new to the rotation.
No comments yet.
Be the first visitor to add a thoughtful comment on this article.