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:
- a
calendar/folder with one file per post, named with the date it should go out, - a
posted/folder for files that reached every platform listed, - a
log.mdfile with one line and one live link per platform, - a daily 9:00 task under Scheduled in the ChatGPT desktop app,
- one post you watched go out to X, Bluesky and LinkedIn before the task took over.
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:
- Platform names are lowercase CLI names.
xis X/Twitter; the publishing command calls the sitetwitter.blueskyandlinkedinare the other two. Keep the commas. - The text is literal. The task does not add hashtags, rewrite the opening, or “improve” the line breaks. What you save is what goes out. That is the point of using a folder instead of a prompt that regenerates the post at 9:00.
- The date is the schedule, not the creation date. A file named
2026-11-10will wait until 10 November. If you create it on the 7th, nothing happens on the 7th.
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:
- 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.
- It creates
calendar/,posted/, andlog.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 tolog.mdas each one succeeds, and moves the sample file toposted/when all three links are logged. - It installs the
publishportCLI, 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 runspublishport capabilitiesand 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:
- one live link per platform,
- one new line per platform in
log.md, - the file moved from
calendar/toposted/, - no second copy of the same post on any account.
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
- Sunday, 20 minutes: write next week’s files in
calendar/. One idea per file, short enough for X’s 280 characters, with the date in the name. Draft with AI if you like, but save the text you approve. - Each weekday morning: read the run summary. If all three links are there, you are done.
- Friday, 5 minutes: read
log.md. Which post got replies? Which platform was quiet? Write that down for next Sunday. - Once a month: prune the archive if you do not need it, and decide whether the three-platform mix still matches where your readers are.
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.
- Nothing happened at 9:00. The computer was asleep, the ChatGPT desktop app was closed, or the folder moved. Check Scheduled for the run record.
- The run reports a network error. The network rule is missing, the app was not restarted, or the task is calling
npxinstead ofpublishport. Fix the rule and edit the prompt. - A file is skipped as too long. The text exceeds 280 characters for X, 300 graphemes for Bluesky, or 3,000 for LinkedIn. Keep a shorter shared file and move the long version to a LinkedIn-only file. The task will not cut the sentence for you.
- One platform is signed out. Sign in again under Platform accounts in the PublishPort app, then run the task. The log stops the platforms that already succeeded from posting again.
- The same post appears twice. The log line was not written, or the task did not read it. Check
log.mdand the first rule in the task prompt. - LinkedIn says
dry_run. The command validated the session but did not publish. Ask Codex to run LinkedIn create with send enabled. - The Bluesky link is not tappable. Keep the Bluesky file self-contained and put the URL in the X and LinkedIn files, or accept a plain URL there.
- The AI says no device is online. The PublishPort desktop app is not running. Start it; it normally starts with your computer.
- The authorization link expired. The link lasts 10 minutes. Ask Codex for a new one.
Variations
- Publish at a different time. Edit the schedule to 8:00 or 10:00. If you need two posting times, use two folders and two tasks; the date filter is per folder.
- Add Threads or Mastodon. Add the platform name to the file’s platform line and ask Codex to read that platform’s guide before the first run. The WorkBuddy cross-posting recipe covers the per-platform limits for Bluesky, Mastodon and Threads.
- Run the scheduler from the cloud. Claude Code’s scheduled tasks run on Anthropic’s cloud, while the PublishPort desktop app still does the publishing on the computer that holds your sessions. Put your PublishPort credential in the task environment’s
PUBLISHPORT_TOKENvariable, as the skill file explains, and allow the environment to reachapi.publishport.app. - Add one image per post. Put an
image:line in the file with an absolute path, then ask Codex to attach it through each platform’s command and test one post before scheduling. Keep the image under 1 MB if Bluesky is in the platform line; the current Bluesky command rejects larger files.
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.
