How to build an automated content calendar using Redis and cron jobs
You write a blog post on Monday. You check the box. But how are you bringing in traffic the other six days?
In a startup world filled with noise, product bugs, and endless feature requests, it is easy to fall behind on marketing. You go through the motions, ship a quick patch, and tell yourself you will write an SEO article tomorrow. We convince ourselves that we have time. We convince ourselves that users will find us anyway.
But the truth is much more urgent. Your startup runway is not guaranteed. Your product needs consistent traffic to survive.
When I was building my SaaS, I realized that writing articles by hand every week was killing my development speed. I needed a system that runs on schedule without my manual input. I needed a reliable engine to manage content strategy.
In this guide, I will show you step by step how to build an automated content calendar using Redis and simple cron jobs. This solution gives you full control over your scheduling, prevents duplicate runs, and keeps your content publishing automatically.
The problem with simple cron jobs
Most developers start with a basic system. They set up a system cron job or a Node.js process using node-cron. The script fires at 9:00 AM, calls an external API, generates an article, and posts it to a blog.
That works fine for a week. Then, real life happens.
Your server restarts right when the cron job is supposed to fire. The execution gets missed entirely. Or worse, your script hits a API timeout while generating text, hangs indefinitely, and spawns another process an hour later. Suddenly, you publish three duplicate posts in ten minutes.
To build a clean automated content calendar, you need two distinct components:
- A Scheduler: A lightweight process that pushes publishing tasks into a queue at specific times.
- A Worker: A resilient background process that pulls tasks from the queue, handles retries, and publishes the post.
By decoupling the schedule from the execution, your system becomes bulletproof. If your worker crashes, the schedule is not lost. If your generation task takes three minutes, the scheduler does not care.
Setting up Redis for your automated content calendar
Redis is perfect for this task. It is fast, lightweight, and supports data structures that make job queues easy to implement.
For our queue, we will use Redis sorted sets (ZSET). A sorted set allows us to store tasks with a timestamp as their score. This turns Redis into a time-based task broker.
Step 1: Install dependencies
Start a new Node.js project and install the required packages:
npm init -y
npm install ioredis node-cron dotenv @google/genai
Step 2: Initialize the Redis connection
Create a file named redis.js to manage your connection.
// redis.js
const Redis = require('ioredis');
require('dotenv').config();
const redis = new Redis(process.env.REDIS_URL || 'redis://127.0.0.1:6379');
redis.on('connect', () => {
console.log('Connected to Redis');
});
redis.on('error', (err) => {
console.error('Redis connection error:', err);
});
module.exports = redis;
Building the queue and scheduler workflow
Now we need a way to schedule upcoming content. We will push content topics into a Redis sorted set called content_schedule. The score for each item will be the UNIX timestamp (in milliseconds) when the post should be generated and published.
Step 3: Pushing topics into the calendar
Create a script named schedule.js. This script populates your calendar for the next 30 days.
// schedule.js
const redis = require('./redis');
async function scheduleArticles() {
const topics = [
{ id: 'post-1', keyword: 'best database for saas', publishAt: Date.now() + 60000 }, // 1 min from now
{ id: 'post-2', keyword: 'how to lower AWS costs', publishAt: Date.now() + 86400000 }, // 1 day from now
{ id: 'post-3', keyword: 'building a micro saas in 2025', publishAt: Date.now() + 172800000 } // 2 days from now
];
for (const item of topics) {
const payload = JSON.stringify(item);
// ZADD schedule_queue timestamp payload
await redis.zadd('schedule_queue', item.publishAt, payload);
console.log(`Scheduled "${item.keyword}" for timestamp: ${item.publishAt}`);
}
process.exit(0);
}
scheduleArticles();
Step 4: Setting up the cron trigger
Instead of having your cron job do the heavy lifting of writing articles, the cron job will only check Redis every minute to see if any job is ready.
Create a file named scheduler.js:
// scheduler.js
const cron = require('node-cron');
const redis = require('./redis');
// Run every minute
cron.schedule('* * * * *', async () => {
const now = Date.now();
// Fetch jobs that were scheduled for 'now' or earlier
const jobs = await redis.zrangebyscore('schedule_queue', 0, now);
if (jobs.length === 0) {
return;
}
for (const jobString of jobs) {
// Move job from schedule_queue to active_queue to prevent re-processing
const removed = await redis.zrem('schedule_queue', jobString);
if (removed) {
await redis.lpush('active_queue', jobString);
console.log('Moved job to active queue:', JSON.parse(jobString).keyword);
}
}
});
console.log('Scheduler running...');
Integrating Gemini AI content generation
Once a job hits the active_queue, our background worker processes it. This is where ai content automation comes into play.
We will use Google Gemini API for fast, high-quality text generation. This acts as an ai blog writer for saas that drafts full articles on autopilot.
Step 5: Creating the worker script
Create a file named worker.js:
// worker.js
const redis = require('./redis');
const { GoogleGenAI } = require('@google/genai');
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
async function processQueue() {
while (true) {
// BRPOP blocks until a job is available in active_queue (timeout 5 seconds)
const result = await redis.brpop('active_queue', 5);
if (result) {
const jobData = JSON.parse(result[1]);
console.log(`Processing article for keyword: ${jobData.keyword}`);
try {
const articleText = await generateArticle(jobData.keyword);
await publishToCMS(jobData.keyword, articleText);
console.log(`Successfully published: ${jobData.keyword}`);
} catch (error) {
console.error(`Failed to process job ${jobData.keyword}:`, error.message);
// Put the job back in queue or handle retry logic here
}
}
}
}
async function generateArticle(keyword) {
// Utilizing gemini ai content generation
const response = await ai.models.generateContent({
model: 'gemini-2.5-flash',
contents: `Write a clear, engaging 1000-word blog post targeted at developers about: "${keyword}". Use clear H2 headings and practical tips.`,
});
return response.text;
}
async function publishToCMS(title, content) {
// Mock function for CMS publishing (e.g., WordPress, Ghost, Webflow)
console.log(`[CMS] Publishing post: "${title}" (${content.length} characters)`);
return true;
}
processQueue();
Overcoming real-world technical trade-offs
When building an automated seo content pipeline, tutorials often make it look easy. In production, you will hit edge cases. Here are three specific technical details I learned the hard way.
1. Redis atomic operations and race conditions
If you scale your scheduler across multiple server instances or serverless functions, two processes might read the same job from Redis at the exact same millisecond.
Using zrangebyscore followed by zrem is not thread-safe. To fix this, you should use Lua scripts inside Redis to fetch and remove the item in a single atomic operation.
Here is an atomic Lua script snippet for Redis:
local val = redis.call('ZRANGEBYSCORE', KEYS[1], 0, ARGV[1], 'LIMIT', 0, 1)
if #val > 0 then
redis.call('ZREM', KEYS[1], val[1])
return val[1]
end
return nil
2. Gemini API rate limits
When generating long-form articles with gemini ai content generation, you can hit token limit errors (HTTP 429). If your worker tries to process five posts at once, the API will block your requests.
To solve this, implement an exponential backoff in your worker. If the API returns a 429 status code, delay the worker for 30 seconds before re-queuing the job.
3. Memory leaks in long-running Node.js processes
Node.js workers running continuous while(true) loops can slowly leak memory over weeks if garbage collection is not handling string buffers properly. Set up a simple Docker constraint or system service that restarts your worker process once every 24 hours.
Streamlining content ops for indie hackers
Building your own custom pipeline gives you complete control over your code. But maintaining your own server workers, error retry queues, prompt templates, and CMS integrations takes time away from building your primary product.
When running my startup, managing content ops for indie hackers became a full-time job. I spent more time debugging rate limits and formatting markdown than writing actual product features.
I ended up automating this whole flow with a dedicated background tool I built called SleepPublish.
It functions as an ai seo tool for startups and a complete wordpress ai autopilot. It picks low-competition keywords, drafts long-form content with Gemini, formats markdown, and publishes directly to platforms like WordPress, Webflow, Ghost, and Notion without needing self-hosted Redis queues or custom scripts.
Scale your organic traffic automatically
Consistency is everything in SEO. Search engines reward websites that publish high-quality content regularly over months, not sites that drop ten articles in one day and disappear for half a year.
By building an automated content calendar using Redis and cron jobs, you remove manual effort from your growth strategy. You ensure that your marketing pipeline runs smoothly behind the scenes while you focus on shipping features.
Set up your Redis queues, configure your cron tasks, and let your automated pipeline handle the rest.
Try SleepPublish free for 7 days, it plans, writes, and publishes SEO content straight to your CMS: https://sleeppublish.mactrixxr.space
Top comments (0)