AI Workflows

Social Media Auto Poster in 2026: Every Way to Auto-Post, from RSS Feeds and CMS Webhooks to Cron Jobs and AI Agents

Every way to auto-post to social media in 2026: RSS-to-social tools and their polling delays, CMS webhooks from Ghost, Webflow, Contentful, and Shopify, a cron job plus an API, and AI agents through MCP. A decision table, three working recipes in Node and Python, and the platform rules that break auto-posters.
September 19, 2026 by Jesse Eisenbart16 min read
Oil painting of an old wooden waterwheel turning in a stream that winds through golden reed beds toward distant mountains

TLDR: A social media auto poster is whatever publishes for you without a click, and there are four kinds. RSS-to-social tools are the oldest and simplest: Zapier and Make poll every 15 minutes on their free plans, Buffer’s feeds are manual by design, and every one of them struggles with Instagram because Instagram needs an image. CMS webhooks are faster and cleaner: Ghost, Webflow, Contentful, Sanity, and Shopify fire a publish event you can catch; WordPress needs a plugin. Cron plus an API gives you full control for the price of a small script on GitHub Actions or a Cloudflare Worker. AI agents through MCP add judgement: drafting per-platform captions and picking media instead of copying the title. This guide has a decision table, one working recipe for each route, and the rules that break auto-posters: Instagram’s JPEG-only images and 100-post daily cap, X’s $0.20 per link post and duplicate-content policy, and Meta’s Pages-only publishing. Checked on September 19, 2026.

What is a social media auto poster, and which kind do you need?

The phrase covers four different systems that share one output, a post that nobody typed into a composer:

Route Trigger Delay Per-platform captions Cost shape Best for
RSS-to-social tool New item in a feed 15 minutes on free tiers, faster paid Templates only Per task or per feed A blog or podcast with a feed and no engineering time
CMS webhook Publish event from the CMS Seconds Whatever your handler writes A small serverless function Sites on Ghost, Webflow, Contentful, Sanity, or Shopify
Cron + API A clock Your interval Full control A cron host plus an API Queues, evergreen reposts, digests, anything scripted
AI agent via MCP A prompt, a schedule, or an event Seconds to minutes Written per platform by the model Model tokens plus an API Content that needs rewriting per network or judgement about what to post

The rest of the article walks through each route with a working recipe, then the platform rules that apply to all four. If you only want the recipes, they use PostZen’s API so one request reaches ten networks, but the pattern is the same with any publishing API.

RSS to social media: how RSS auto-posting tools work and where they break

RSS-to-social is a poller. Every N minutes the tool fetches your feed, compares the items with what it has seen, and turns new ones into posts using a template such as New post: {title} {link}. The variables are the polling interval, the dedupe key, and what happens on Instagram.

Tool Free plan Polling Notes
Zapier (RSS by Zapier) 100 tasks a month, 3 Zaps, 2-step Zaps only 15 minutes on free; faster on paid plans from $19.99 a month Dedupes on GUID or URL; each network is its own action step
Make 1,000 operations a month, 2 scenarios 15 minutes on free; per-minute from the $9 Core plan RSS module plus one module per network
Buffer content feeds 3 feeds free, 50 on paid Not automated Feeds surface items for you to queue by hand; true auto-posting needs Zapier in front
Hootsuite RSS Autopublisher Third-party app in Hootsuite’s directory Not published Up to 25 feeds, keyword filters, attaches article images
SocialPilot RSS No free plan; from $30 a month You set the check frequency Extra feeds are a paid add-on
dlvr.it Free tier with a few feeds and profiles Not published on the pages we could reach The original RSS-to-social tool
Jetpack Social (WordPress) 30 shares a month free, counted per network On publish X was dropped from Jetpack Social in 2023 after X’s API pricing; check the current network list

The breakages are the same everywhere:

  • Latency. A 15-minute poll means your “breaking” post is 15 minutes late on average and 29 in the worst case. Webhooks fix this.
  • Dedupe. RSS 2.0 makes guid optional, and some CMSs regenerate it on edit; a tool that keys on GUID reposts an edited article, and one that keys on URL misses a moved one. Atom’s id is required and must never change, which is one reason to prefer an Atom feed if your CMS offers one.
  • Instagram. Instagram’s publishing API needs an image or video on every post and accepts only JPEG images. A feed item without an enclosure or og:image cannot become an Instagram post, and a PNG has to be converted first. Most RSS tools either skip Instagram or fail silently on it.
  • X’s bill. Since February 2026, X charges per API post: $0.015 for a post and $0.20 for a post containing a link. An RSS feed that posts every article with its URL costs $0.20 a post before any tool subscription, which is why several tools quietly dropped X from free plans; our X API pricing guide has the full rate card.
  • Identical copies. The same title and link on six networks is the identical-copy problem covered in our cross-posting guide: dead links in Instagram captions, thin posts on LinkedIn, hashtags where they do not belong.

RSS tools are right for a blog that publishes a few times a week to X, LinkedIn, Facebook, and Bluesky, where a 15-minute delay and a templated caption are fine. Past that, the next three routes take over.

Recipe 1: an RSS poller in Node that posts into a queue

This poller runs on a schedule, fetches the feed with conditional requests so unchanged feeds cost nothing, dedupes on Atom ID or RSS GUID with a URL fallback, and creates one PostZen post per new item into the profile’s queue so posts land in your predefined slots rather than the minute the cron fires. Instagram is included only when the item has an image.

// poller.mjs — run every 15 minutes (GitHub Actions, Cloudflare Cron, EventBridge)
import Parser from 'rss-parser';
import { readFile, writeFile } from 'node:fs/promises';

const FEED = 'https://example.com/blog/feed.xml';
const STATE = './state.json'; // { etag, lastModified, seen: [] }
const POSTZEN = 'https://api.postzen.dev/v1';
const PROFILE = process.env.POSTZEN_PROFILE_ID;
const TARGETS = {
  twitter: 'acc_x', linkedin: 'acc_li', bluesky: 'acc_bsky', facebook: 'acc_fb', instagram: 'acc_ig',
};

const state = JSON.parse(await readFile(STATE, 'utf8').catch(() => '{"seen":[]}'));
const seen = new Set(state.seen);

const res = await fetch(FEED, {
  headers: {
    ...(state.etag ? { 'If-None-Match': state.etag } : {}),
    ...(state.lastModified ? { 'If-Modified-Since': state.lastModified } : {}),
  },
});
if (res.status === 304) process.exit(0); // nothing changed, nothing spent

const feed = await new Parser({ customFields: { item: ['enclosure', ['media:content', 'media']] } })
  .parseString(await res.text());

for (const item of feed.items.reverse()) { // oldest first
  const key = item.id ?? item.guid ?? item.link;
  if (!key || seen.has(key)) continue;

  const image = item.enclosure?.url ?? item.media?.$?.url;
  const summary = (item.contentSnippet ?? '').slice(0, 200).trim();
  const short = `${item.title} ${item.link}`;

  const platforms = [
    { platform: 'twitter', accountId: TARGETS.twitter, customContent: short.slice(0, 280) },
    { platform: 'bluesky', accountId: TARGETS.bluesky, customContent: short.slice(0, 300) },
    { platform: 'linkedin', accountId: TARGETS.linkedin },
    { platform: 'facebook', accountId: TARGETS.facebook, settings: { link: item.link } },
  ];
  if (image) {
    platforms.push({ platform: 'instagram', accountId: TARGETS.instagram, customContent: `${item.title}. Link in bio.` });
  }

  const r = await fetch(`${POSTZEN}/posts`, {
    method: 'POST',
    headers: { Authorization: `Bearer ${process.env.POSTZEN_API_KEY}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({
      content: `${item.title}\n\n${summary}\n\n${item.link}`,
      queuedFromProfile: PROFILE, // PostZen picks the next free queue slot
      ...(image ? { mediaItems: [{ url: image }] } : {}),
      platforms,
    }),
  });
  if (!r.ok) { console.error(key, await r.text()); continue; }
  seen.add(key);
}

await writeFile(STATE, JSON.stringify({
  etag: res.headers.get('etag'),
  lastModified: res.headers.get('last-modified'),
  seen: [...seen].slice(-500),
}));

Notes on the choices:

  • rss-parser is the most-downloaded Node feed parser (about 660,000 weekly downloads) but its last release, 3.13.0, is years old. It works; do not expect fixes. Python’s feedparser is actively maintained (6.0.14 shipped July 30, 2026) if you would rather write this in Python.
  • queuedFromProfile hands the post to the profile’s queue and PostZen returns the slot it claimed as scheduledFor. Do not preview the next slot and pass it back yourself; two pollers doing that race for the same slot. The queue posts page explains the slot model.
  • The Instagram target is only added when there is an image, and its caption drops the link because Instagram captions are not clickable. Instagram accepts JPEG images only, so make the feed’s featured image a JPEG or convert it in the poller before sending.
  • The seen list is trimmed to 500 keys; for a high-volume feed put it in a KV store instead of a file.

CMS webhooks: auto-posting the moment an article publishes

A webhook flips the direction: the CMS tells you when something is published, so there is no polling, no delay, and no dedupe problem. Most modern CMSs fire one:

CMS Publish event Notes
Ghost post.published (also post.published.edited, post.scheduled, post.unpublished) Configure in Integrations; JSON body carries the full post
Webflow collection_item_published (also _created, _changed, _deleted) Needs the cms:read scope; payload includes the item’s fields
Contentful ContentManagement.Entry.publish in the X-Contentful-Topic header One webhook covers create, save, publish, unpublish, and delete topics
Sanity Any document change matching a GROQ filter; publishes only by default The projection shapes the payload you receive
Shopify products/create (REST topic; PRODUCTS_CREATE in GraphQL) The pattern for “new drop” posts
WordPress None in core Use a webhooks plugin hooked to publish_post, or Jetpack Social for its supported networks
Substack, Hashnode No outgoing webhooks Substack is RSS only; Hashnode has a GraphQL API you can poll

The handler is small: verify the request, pull title, excerpt, URL, and featured image from the payload, and create the post. Because webhooks retry on non-2xx responses and can fire twice for one publish (Ghost fires post.published and, on the first edit, post.published.edited), key your dedupe on the CMS item ID.

Recipe 2: a Ghost webhook handler that schedules posts

A minimal handler as a single serverless function. It accepts Ghost’s post.published webhook, ignores anything that is not a fresh publish, and schedules the announcement into the queue.

// api/ghost-published.js — deploy on any serverless host and set it as the webhook target
const POSTZEN = 'https://api.postzen.dev/v1';

export default async function handler(req, res) {
  if (req.method !== 'POST') return res.status(405).end();
  if (req.headers['x-webhook-secret'] !== process.env.GHOST_WEBHOOK_SECRET) return res.status(401).end();

  const post = req.body?.post?.current;
  if (!post || post.status !== 'published') return res.status(200).json({ skipped: true });

  // Dedupe on Ghost's post id so retries and published.edited never double-post
  if (await alreadyAnnounced(post.id)) return res.status(200).json({ skipped: 'duplicate' });

  const text = `${post.title}\n\n${post.custom_excerpt ?? post.excerpt ?? ''}\n\n${post.url}`;
  const platforms = [
    { platform: 'linkedin', accountId: process.env.ACC_LINKEDIN },
    { platform: 'twitter', accountId: process.env.ACC_X, customContent: `${post.title} ${post.url}`.slice(0, 280) },
    { platform: 'threads', accountId: process.env.ACC_THREADS, customContent: `${post.title}\n${post.url}`.slice(0, 500) },
  ];
  if (post.feature_image) {
    platforms.push({ platform: 'instagram', accountId: process.env.ACC_IG, customContent: `${post.title}. Link in bio.` });
  }

  const r = await fetch(`${POSTZEN}/posts`, {
    method: 'POST',
    headers: { Authorization: `Bearer ${process.env.POSTZEN_API_KEY}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({
      content: text,
      queuedFromProfile: process.env.POSTZEN_PROFILE_ID,
      ...(post.feature_image ? { mediaItems: [{ url: post.feature_image }] } : {}),
      platforms,
    }),
  });
  if (!r.ok) return res.status(502).json({ error: await r.text() }); // non-2xx makes Ghost retry

  await markAnnounced(post.id);
  return res.status(200).json({ ok: true });
}

The same shape works for Webflow (read payload.fieldData), Contentful (check the topic header, then read fields), and Shopify (a product’s title, handle, and first images[].src). Ghost does not sign webhooks with a header out of the box, so the secret-header check above assumes you appended one in the webhook URL or use a host that supports it; verify signatures wherever the CMS provides them.

Cron plus an API: the auto poster you fully control

When there is no feed and no webhook, or when the content is a list rather than a stream (daily tips, evergreen reposts, a weekly digest), a scheduled script is the whole product. The choices are where the clock runs and what fills the posts.

Scheduler Minimum interval Limits worth knowing Cost
Plain crontab 1 minute You own the host and its uptime The server
GitHub Actions schedule 5 minutes Runs can be delayed or dropped at busy times, especially on the hour; avoid 0 * * * * Free minutes on public repos
Cloudflare Workers Cron Triggers 1 minute 5 triggers per account free, 250 on paid; 30 seconds of CPU for sub-hourly triggers Free tier
Vercel Cron Daily on Hobby, and not to the minute; 1 minute on Pro Tied to deployments Pro from $20 a month
AWS EventBridge Scheduler 1 minute Six-field cron with a time zone $1 per million invocations after the free tier

For most auto-posters a 15-minute or hourly cron is plenty, because the posts should not go out when the cron fires anyway; they should go into a queue with slots at the times your audience is online. That separation is the difference between an auto-poster that looks automated and one that looks like a person.

Recipe 3: a Python cron that drains an evergreen list into the queue

This script keeps a rotation of evergreen posts in a JSON file, picks the least recently used one each run, and adds it to the queue with per-platform variants. Run it once a day and it keeps the queue topped up without ever repeating within the rotation.

# evergreen.py — run daily from cron: 0 9 * * * /usr/bin/python3 /srv/evergreen.py
import json, os, time, requests
from pathlib import Path

POSTZEN = "https://api.postzen.dev/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['POSTZEN_API_KEY']}"}
PROFILE = os.environ["POSTZEN_PROFILE_ID"]
ROTATION = Path("/srv/evergreen.json")  # [{ "id", "text", "x", "link", "image", "lastPostedAt" }]

items = json.loads(ROTATION.read_text())
item = min(items, key=lambda i: i.get("lastPostedAt", 0))  # least recently posted

platforms = [
    {"platform": "linkedin", "accountId": os.environ["ACC_LINKEDIN"]},
    {"platform": "twitter", "accountId": os.environ["ACC_X"], "customContent": item["x"][:280]},
    {"platform": "bluesky", "accountId": os.environ["ACC_BSKY"], "customContent": item["x"][:300]},
    {"platform": "facebook", "accountId": os.environ["ACC_FB"], "settings": {"link": item["link"]}},
]
body = {
    "content": f'{item["text"]}\n\n{item["link"]}',
    "queuedFromProfile": PROFILE,
    "platforms": platforms,
}
if item.get("image"):
    body["mediaItems"] = [{"url": item["image"]}]
    platforms.append({"platform": "instagram", "accountId": os.environ["ACC_IG"], "customContent": f'{item["text"]} Link in bio.'})

r = requests.post(f"{POSTZEN}/posts", json=body, headers=HEADERS, timeout=30)
r.raise_for_status()
post = r.json()["post"]
print("queued", item["id"], "for", post["scheduledFor"])

item["lastPostedAt"] = int(time.time())
ROTATION.write_text(json.dumps(items, indent=2))

Two habits keep a script like this out of trouble. Keep each post’s variants distinct across platforms and across time; X’s automation rules prohibit duplicative or substantially similar posts, and a rotation that reposts the identical text every three weeks is closer to that line than one with a few variants per item. And write the state after the API call succeeds, not before, so a failed request retries next run instead of being skipped.

For hundreds of posts at once, skip the loop and send a CSV to the bulk endpoint with a dry run first; the bulk scheduling page covers the format and the per-row report.

AI agents as auto posters: MCP, prompts, and what changes

The three routes above copy content from one place to another. An agent can also decide: read the article and write a LinkedIn post that is not the title, pick which of five images suits Instagram, skip a post that is an internal announcement, or answer “what should go out this week” from your analytics.

The plumbing in 2026 is MCP, the Model Context Protocol, an open standard for connecting AI applications to external systems. Claude, ChatGPT, Cursor, and VS Code support MCP servers, and PostZen exposes its API as one at https://mcp.postzen.dev/mcp, with tools for profiles, accounts, media, posts, queues, analytics, inbox, and comment automations. Zapier and Make both ship MCP servers too, so an agent can reach the RSS and CMS integrations of the first two routes through them.

Recipe 4: an agent that turns a new article into per-platform posts

Connect the server once. In Claude Code:

claude mcp add --transport http postzen https://mcp.postzen.dev/mcp
# then in a session: /mcp → postzen → Authenticate

Then the recipe is a prompt, run by hand, from a scheduled agent, or triggered by the CMS webhook from recipe 2 with the article URL substituted in:

Read https://example.com/blog/new-article. Using my PostZen profile “Company blog”, write a LinkedIn post of about 120 words that leads with the most useful finding and ends with the link, an X post under 280 characters with the link, a Threads post under 500 characters without the link, and an Instagram caption that says “link in bio” and uses the article’s featured image. Add all four to the profile’s queue. Skip Instagram if the image is not at least 1080 pixels wide. Show me the four captions and the scheduled times before you create anything.

The agent lists the accounts, fetches the page, drafts, asks for confirmation, uploads the image through the media tool, and creates one post with four targets in the queue. The “show me before you create” line is worth keeping in any automated prompt; drop it only once you have watched the output for a while.

Three things change when the poster is an agent:

  • Per-platform copy stops being a template. The identical-copy problem from the RSS section goes away, because the model writes for each network.
  • Judgement is a feature and a risk. An agent can skip the internal-only post; it can also hallucinate a claim about your article. Keep the human-confirm step for anything public until you trust the pattern, and give it read tools for analytics so its judgement is grounded.
  • The queue matters more. An agent that runs at 3 a.m. should fill slots, not publish at 3 a.m. Queues do that by default.

The agents page and the Claude Code MCP setup cover the integration, and the MCP docs list every tool.

Platform rules that break auto posters

Every route above ends in the same ten platform APIs, and each has rules that a naive auto-poster trips:

Platform Rule an auto-poster hits Consequence
Instagram Image or video required; JPEG only; 100 API posts per account per 24 hours Text-only items fail; PNGs need conversion; a feed burst hits the cap
X Pay per post since February 2026: $0.015 a post, $0.20 with a link; no duplicative posts across your accounts; no automated trending-topic posts; automated accounts should carry the automated-account label Cost per link post; suspension risk for copypasta
Facebook Pages only; personal profiles and Groups cannot be posted to by API An auto-poster for a personal profile is not possible
LinkedIn Link previews come from your page’s Open Graph tags; 3,000-character limit Missing og:image means a bare link card
Bluesky 300 graphemes; links need facets to be clickable; bots should self-label bot Plain-text URLs unless the API adds facets
Threads 500 characters Long templates truncate
TikTok, YouTube, Pinterest Video (or image for Pinterest) required; TikTok needs a privacy level, YouTube a title, Pinterest a board Not reachable from a text feed at all

Two of these deserve a sentence each. Meta’s Pages-only rule is absolute: there has been no API for posting to a personal Facebook profile since 2018, so an “auto post to my Facebook” request means a Page. And X’s per-post fee turns every architecture decision into a cost decision: an RSS feed with 20 link posts a day costs about $4 a day on X alone, which is why the recipes above shorten X copy and why some teams post links only on LinkedIn, Facebook, and Bluesky.

None of the routes changes reach. Whether a post came from a feed, a webhook, a cron job, or an agent is not a ranking signal on any platform, as our evidence review on scheduled posts covers; what changes reach is the timing and the copy, which is the argument for queues and per-platform variants throughout this guide.

Which social media auto poster should you build?

  • A blog with a feed and no code: an RSS tool on a free plan for X, LinkedIn, Facebook, and Bluesky. Accept the 15-minute delay and skip Instagram.
  • A site on a modern CMS: a webhook handler like recipe 2 into a queue. Seconds of delay, no dedupe problem, an image for Instagram when the article has one.
  • A list of content rather than a stream: a cron script like recipe 3 into a queue, with variants per platform and per repeat.
  • Content that needs rewriting per network: an agent with MCP tools and a confirm step, fed by any of the above.

All four end in one request to a social media API that fans out to ten platforms and reports each target separately, which is the part you should not build yourself. The auto poster is the small script or prompt in front of it.

Frequently asked questions

What is a social media auto poster?

Any system that publishes to social accounts without a person pressing the button each time. The four common forms are an RSS-to-social tool that watches a feed, a CMS webhook that fires when an article is published, a cron job that posts from a queue or a list on a schedule, and an AI agent that drafts and publishes through an API or MCP tools.

How do I automatically post my blog to social media?

Either watch the blog's RSS feed and post each new item, or have the CMS call a webhook when a post is published. Ghost, Webflow, Contentful, Sanity, and Shopify all fire publish events natively; WordPress needs a plugin. The handler then creates a social post through a scheduler API with the title, a summary, the link, and the article's image, ideally into a queue so posts land at good times rather than the second they publish.

Can you auto-post to Instagram from an RSS feed?

Only if every item has an image. Instagram's publishing API rejects text-only posts and accepts JPEG images only, so an RSS auto-poster has to pull the article's featured image (or a generated card), convert it to JPEG, and skip items without one. Meta also caps API publishing at 100 posts per account per rolling 24 hours.

Is auto-posting allowed on X?

Yes, within X's automation rules: no duplicative or near-identical posts across accounts you control, no automated posting about trending topics, and automated accounts should carry X's automated-account label. Since February 2026 every API post costs money on X's pay-per-use pricing, $0.015 per post and $0.20 when the post contains a link, which changes the economics of an RSS feed that posts every article.

What is the best free way to auto-post to social media?

For a low-volume blog, Zapier's or Make's free plan with an RSS trigger works, with 15-minute polling and monthly caps of 100 tasks or 1,000 operations. For anything larger, a small script on a free cron (GitHub Actions every five minutes, or a Cloudflare Worker cron trigger) calling a social media API with a free tier costs nothing and removes the per-task ceiling.

Can an AI agent post to social media automatically?

Yes. Through MCP, clients such as Claude, ChatGPT, Cursor, and VS Code can call a social media API's tools directly, so an agent can read a feed or a page, draft per-platform captions, upload media, and schedule the posts. Combine it with a queue so the agent fills slots rather than posting the moment it runs.