← Back to blog

Build a Social Media Scheduler From a Folder With Codex

·social media schedulersocial media content calendarCodex scheduled taskspost to X and BlueskyAI agent

Build a Social Media Scheduler From a Folder With Codex

Most social media schedulers have the same shape: a dashboard, a queue, a calendar view, and a monthly bill. The queue is useful. The dashboard is the part you have to keep visiting.

This is a build-it-yourself setup, not a scheduler comparison. You need the ChatGPT desktop app, the PublishPort desktop app, Node.js, and a computer that stays on at 9:00. If you want a hosted tool with a calendar UI, this post is not that; it shows how to build the folder-based version.

If your AI can already run commands on your computer, you can keep the queue and skip the dashboard. A folder of dated text files is enough. The date in the file name is the schedule; the platform line says where the post goes; a Codex scheduled task does the publishing at 9:00, through the accounts already signed in on your machine.

When you are done you will have:

The build takes about 15 minutes of clicking once the two desktop apps are installed, the three accounts are signed in, and Node.js is present. Budget 30 to 40 minutes end to end, including the downloads and one app restart. The first automatic post goes out at the next 9:00.

Who this fits: people who plan posts in batches, already have X, Bluesky and LinkedIn accounts, and want the exact words they approved to go out without opening three composers. A two-person team can keep the calendar in a shared folder and let one machine publish; log.md becomes the shared record of what went out.

What it does not do: it does not write your posts, it does not publish when the computer is off, and it does not give each platform its own time of day. A sleeping laptop means the post silently does not go out. One daily run publishes everything dated that day. If a post must go out at 14:00, this is the wrong shape; the Variations section has the workaround.

The recipe

Open the folder in Codex and paste this sentence. It installs PublishPort, creates the folder structure, publishes a sample post with you watching, and then creates the 9:00 task.

Read https://www.publishport.app/skill.md and follow it to set up PublishPort for me, then turn this folder into my social media scheduler: create calendar/, posted/ and log.md; each calendar file is named YYYY-MM-DD-slug.md, starts with a line like platforms: x, bluesky, linkedin, then a line with three hyphens, then the exact post text; ask me for one short post under 280 characters, save it as today's sample file, show me the exact text, and after I approve publish it to the platforms in the file, verify each link, and append a line to log.md for each platform; install the publishport CLI globally, add a rule to ~/.codex/rules/default.rules that allows commands starting with 'publishport' to run outside the sandbox, and tell me when to restart the app; after I restart, verify with publishport capabilities and create a Codex scheduled task that runs every morning at 9:00, publishes every calendar file dated today, checks log.md before each platform so the same file is never posted twice, records each link as soon as it succeeds, leaves a partially published file in calendar/ for the missing platforms, and does nothing when there is no file for today.

The skill file is a short public instruction sheet. It tells the AI how to install itself, ask you for one authorization click, check which accounts are connected, publish, and confirm the post exists. Every AI you authorize appears on your account page, where you can revoke it.

Step 0: Three things ready

1. The ChatGPT desktop app with Codex. Install it from OpenAI’s download page and sign in. Scheduled tasks are managed under Scheduled in the desktop app or on the web; the official docs say web tasks cannot work directly in a folder on your computer, and the Codex CLI and IDE extension do not provide the Scheduled interface. Use the desktop app.

The computer has to stay on, with the app running, when the task is due. A laptop that sleeps at 9:00 will miss the run. If that is a dealbreaker, put the ChatGPT desktop app and PublishPort on an always-on machine and keep the calendar in a synced folder; the commands still go to the sessions on that machine. If the ChatGPT app staying open is the part you object to, another scheduler can call the same publishport CLI; the PublishPort desktop app still has to be running. This post stays on the Codex path because it is the shortest one.

2. The PublishPort desktop app, signed in to X, Bluesky and LinkedIn. Install it from the download page, open Platform accounts, and sign in to each network in the window that opens (walkthrough). You do not create developer apps or API keys. PublishPort publishes through the sessions you already use; the desktop app has to be running so the relay can reach those sessions.

During setup the AI gives you an authorization link and a confirmation code. Open the link, sign in or create a PublishPort account, check that the code matches, and click Authorize. The link lasts 10 minutes; ask the AI for a new one if it expires.

3. Node.js and a folder. The publishing command runs on Node.js. Check node -v; if nothing comes back, install the LTS release from nodejs.org. Then make a folder called social-scheduler somewhere that will not move, such as your Documents folder. You do not need to create the subfolders or the log; the recipe does that.

One account note before you start: this recipe publishes the same text to all three platforms, so it works only while the text fits X’s standard 280-character limit. X’s own posting guide gives 280 characters for a standard post and longer posts as an X Premium feature. Bluesky’s protocol caps a post at 300 graphemes, per its post lexicon, and LinkedIn’s help page says posts can contain up to 3,000 characters. The task checks all three limits before it publishes anything.

Step 1: The file that is the schedule

One file is one post. The file name starts with the date it should go out, then a short slug: 2026-11-10-importer.md. The first line lists the platforms. A line with three hyphens separates the settings from the post. Everything after that line is the exact text.

platforms: x, bluesky, linkedin
---
Shipped the new importer today. The part that took longest was not the code, it was deciding what to do with half-valid rows. Answer: show them, don't drop them.

At 9:00 on 10 November, the task picks that file up because its name starts with 2026-11-10. It publishes the text to X, Bluesky and LinkedIn in that order, checks each link, and writes a line per platform to log.md.

Three details matter:

The same text on all three networks is deliberate. It keeps the run deterministic. Keep a second, longer LinkedIn file for anything that will not fit X; the date is the same, the platform line is just linkedin:

platforms: linkedin
---
A longer version of the same story, with the two details that would not fit in the short post.

Two files with today’s date means two posts that day. That is also how you handle an X-only follow-up or a Bluesky-only question without teaching the scheduled task to improvise.

The command behaviour in this section was checked against publishport-opencli 1.0.53.

A note on Bluesky links. The current Bluesky command sends the text without rich-text facets, so a URL can appear as plain text rather than a tappable link. Check your first post. If clickable links matter on Bluesky, keep that version self-contained and put the link in the X and LinkedIn files instead. The Bluesky rich-text docs explain why links are stored as facets rather than detected from the string.

Step 2: Paste the recipe and watch the first post

Open the social-scheduler folder in Codex as your workspace. It does not need to be a Git repository; OpenAI’s docs note that scheduled tasks in non-version-controlled projects run directly in the project directory, which is what this recipe wants. Paste the recipe sentence.

Here is what happens next:

  1. It installs the PublishPort skill, runs the login, and checks the machine. You get the authorization link and code from Step 0; if a platform is missing, sign in under Platform accounts in the PublishPort app and ask it to re-check.
  2. It creates calendar/, posted/, and log.md, then asks you for one short post and saves it as today’s sample file. It shows you the exact text. You approve; it publishes to the three platforms one at a time, verifies each post, writes the link to log.md as each one succeeds, and moves the sample file to posted/ when all three links are logged.
  3. It installs the publishport CLI, writes the network rule from Step 3, and tells you to restart the ChatGPT desktop app. After you restart and tell it you are back, it runs publishport capabilities and creates the daily 9:00 task in Scheduled.

A first run can end with links for X and Bluesky and a LinkedIn line that says dry_run. That is the LinkedIn command’s default: it checks the signed-in session and publishes nothing until it is told to send. The task prompt in Step 4 says “not a dry run” for that reason.

Open all three links. This is the moment to catch the things a scheduled run cannot ask you about: line breaks, link display, and whether each command actually published instead of stopping at a validation step.

If one platform is missing or fails, do not ask the AI to republish all three. The log already has the links that worked. Sign in to the missing account and ask it to publish only the missing platform. This is the habit that keeps the whole setup from turning into duplicate posts.

Step 3: Confirm the network rule

The recipe already asked Codex to write the rule. This step is the thirty-second check.

Scheduled tasks run unattended with your default sandbox settings, and the default workspace-write sandbox blocks network access, per OpenAI’s sandboxing docs. Every publish needs the network. A task that works in a supervised chat will fail at 9:00 without a rule.

OpenAI’s answer is rules: allow one command prefix to run outside the sandbox instead of opening everything up. Once the app is back, ask Codex to run publishport capabilities. A device list or a login prompt means the command got through. A network error means the rule did not load; check the file and restart again, or write the rule by hand.

Only commands that start with publishport run outside the sandbox. Everything else stays restricted. Do not add npx to the rule: that would allow a much wider set of packages to run outside the sandbox, and this workflow does not need it.

If Codex could not write the rule, do it by hand. There are two separate actions.

First, in a terminal, install the command:

npm install -g publishport

Second, open ~/.codex/rules/default.rules in a text editor and append this Codex rule. The block is a rule file, not Python:

prefix_rule(
    pattern = ["publishport"],
    decision = "allow",
    justification = "PublishPort publishing needs network access",
)

The file may already contain other rules. Add this one; do not replace the file. Then quit and reopen the ChatGPT desktop app.

The skill file covers setup and publishing behaviour; publishport --help lists the top-level commands, and publishport guide <platform> --device <d> (get <d> from publishport capabilities) returns that platform’s live schema, including the twitter post and linkedin create calls used here. You do not need a Codex-specific wrapper to call them.

Step 4: Check the 9:00 task

Open Scheduled in the ChatGPT desktop app sidebar. You should see a daily 9:00 task attached to the social-scheduler folder.

If it is not there, create a standalone scheduled task, choose the folder as the project, set it to daily at 9:00, and paste this prompt:

This is a pre-approved social media scheduling task. Publish without asking me to confirm.

1. Read log.md. Create it if it is missing.
2. List calendar/*.md. Select the files whose names start with today's date (YYYY-MM-DD).
3. If there are none, stop and reply "No posts scheduled for today."
4. For each selected file, read the platforms line and the text after the three-hyphen line.
5. Before publishing anything, check the text against every platform listed: X 280 characters, Bluesky 300 graphemes, LinkedIn 3,000 characters. If it is too long for any of them, skip the whole file and report which limit failed. Do not shorten or rewrite it.
6. For each platform, check log.md first. If a line already exists for this file and platform, skip it. If a platform is signed out, skip that platform and report it; do not stop the others.
7. Publish the exact text to each remaining platform, one command per platform. For X use the twitter site's post command. For LinkedIn use create with send enabled, not a dry run. If a command errors or times out, do not run it again. Check the account first and retry once only if the post is really absent. Never publish the same file to the same platform twice.
8. Verify each post exists and append one line to log.md for each success: date, file name, platform, link.
9. Move the file to posted/ only when log.md has a line for every platform listed in the file. Otherwise leave it in calendar/ so the next run can publish only the missing platforms.
10. Reply with a one-line summary of what published and what did not.

Each scheduled run starts cold from the saved prompt, and PublishPort’s instructions otherwise tell the AI to show a person the content before the first publish of a session. Nobody is watching at 9:00, so the “pre-approved” line is what keeps it moving.

The rules in the prompt are the part worth copying even if you build this somewhere else. They make the run safe to repeat: the log is checked before every platform, the file only moves when every platform is logged, and a failed command is never blindly retried.

Step 5: Watch the first automatic run

When the task first runs, open Scheduled and read the run. You want four things:

If one platform failed, the file stays in calendar/ and the log records the platforms that worked. Sign in to the failed account, fix the cause, and run the task again. The log makes it publish only the missing platform.

If nothing ran at all, check the three conditions in Step 0: the computer was on, the ChatGPT desktop app was running, and the social-scheduler folder was still on disk. Local scheduled tasks do not run on a sleeping machine, and they cannot find a folder you moved.

Your weekly rhythm

The saving is the three copy-paste steps and the “did it actually go out?” check that disappear on a posting day. The writing still takes the same time.

If something doesn’t run

The first two entries are the ones that stop a new setup. The rest can wait until you need them.

Variations

Where to take it next

If you are still deciding how far ahead to plan, what batch scheduling actually fixes covers the trade-offs; this recipe is the execution layer once you have decided.

Once the calendar is running, the next useful job is the follow-up: the replies on X, the comments under a LinkedIn post, the questions in your inbox. AI auto-replies to DMs and comments covers how to decide what the AI answers and what it hands back to you.

The other job worth adding is a weekly read of the results. A weekly social media analytics review Codex writes for you turns the links in log.md into a short note about what worked, so next Sunday’s calendar starts from evidence instead of guesswork.

Where does your queue live now: a dashboard, a notes app, or a folder? The folder is the one you can open, search, and back up without exporting anything. The first automatic post is the proof that the folder, the rule, and the accounts are all doing their part.