Publishes the posts in your spreadsheet queue on the day each one is due, and writes the date and link back so none goes out twice.

UsesGoogle SheetsX

Post your queued content to X on schedule

ScheduleRun every day at 09:00.Run every day at 09:00.
ValuesSet queueSheetUrl to blank.Set queueSheetUrl to blank.
ValuesSet queueTabName to blank.Set queueTabName to blank.
ValuesSet maxPostsThisRun to 5.Set maxPostsThisRun to 5.
Google SheetsGoogle SheetsRead the rows of queueSheetUrl. Save it as queue.Read the rows of queueSheetUrl. Save it as queue.
ValuesSet queueHeaders to queue.headers.Set queueHeaders to queue.headers.
ValuesSet headerLine to queueHeaders joined by ||.Set headerLine to queueHeaders joined by ||.
Decisions & LoopsIf headerLine doesn't start with Posted On|Link|If headerLine doesn't start with Posted On|Link|
Yes
Decisions & LoopsStop and report a problem: The first two columns of your queue tab must be headed 'Posted On' and 'Link', in that order. They are the only two cells this workflow ever writes to, so your own columns stay untouched. Add them, then run it again.Stop and report a problem: The first two columns of your queue tab must be headed 'Posted On' and 'Link', in that order. They are the only two cells this workflow ever writes to, so your own columns stay untouched. Add them, then run it again.
No
No steps
Decisions & LoopsIf queueHeaders doesn't contain PostIf queueHeaders doesn't contain Post
Yes
Decisions & LoopsStop and report a problem: Your queue tab needs a column headed 'Post' holding the text to publish. Add it, then run it again.Stop and report a problem: Your queue tab needs a column headed 'Post' holding the text to publish. Add it, then run it again.
No
No steps
Decisions & LoopsIf queueHeaders doesn't contain Publish DateIf queueHeaders doesn't contain Publish Date
Yes
Decisions & LoopsStop and report a problem: Your queue tab needs a column headed 'Publish Date' holding the day each post should go out, written as 2026-09-01. Add it, then run it again.Stop and report a problem: Your queue tab needs a column headed 'Publish Date' holding the day each post should go out, written as 2026-09-01. Add it, then run it again.
No
No steps
Dates & TimesGet today's date and save it as todaysDate.Get today's date and save it as todaysDate.
XXGet your account. Save it as profile.Get your account. Save it as profile.
ValuesSet authorHandle to i/web.Set authorHandle to i/web.
Decisions & LoopsIf a step below fails, save the error as profileProblemIf a step below fails, save the error as profileProblem
Try
TextTrim profile.user.username and save it as foundHandle.Trim profile.user.username and save it as foundHandle.
Decisions & LoopsIf foundHandle isn't emptyIf foundHandle isn't empty
Yes
ValuesChange authorHandle to foundHandle.Change authorHandle to foundHandle.
No
No steps
On error
ValuesShow in the output: Could not read your X username, so the links written back to the queue use the generic form..Show in the output: Could not read your X username, so the links written back to the queue use the generic form..
ValuesSet postsPublished to 0.Set postsPublished to 0.
ValuesSet published to an empty record.Set published to an empty record.
ValuesSet dueLeft to 0.Set dueLeft to 0.
ValuesSet problems to an empty list.Set problems to an empty list.
Decisions & LoopsFor each row in queue.rows:For each row in queue.rows:
Decisions & LoopsIf a step below fails, save the error as rowProblemIf a step below fails, save the error as rowProblem
Try
ValuesSet rowNumber to row.row_number.Set rowNumber to row.row_number.
TextTrim row["Posted On"] and save it as postedOn.Trim row["Posted On"] and save it as postedOn.
Decisions & LoopsIf postedOn isn't emptyIf postedOn isn't empty
Yes
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
TextTrim row.Post and save it as postText.Trim row.Post and save it as postText.
Decisions & LoopsIf postText is emptyIf postText is empty
Yes
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
TextTrim row["Publish Date"] and save it as publishDate.Trim row["Publish Date"] and save it as publishDate.
Decisions & LoopsIf publishDate is emptyIf publishDate is empty
Yes
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
ValuesSet daysSinceDue to -1.Set daysSinceDue to -1.
Decisions & LoopsIf a step below fails, save the error as dateProblemIf a step below fails, save the error as dateProblem
Try
Dates & TimesCount the days from publishDate to todaysDate and save it as daysSinceDue.Count the days from publishDate to todaysDate and save it as daysSinceDue.
On error
ListsAdd Row rowNumber: the publish date '@publishDate' is not a date, so the post was left in the queue. Write it as 2026-09-01. to the end of problems and save it as problems.Add Row rowNumber: the publish date '@publishDate' is not a date, so the post was left in the queue. Write it as 2026-09-01. to the end of problems and save it as problems.
Decisions & LoopsSkip to the next item.Skip to the next item.
Decisions & LoopsIf daysSinceDue is less than 0If daysSinceDue is less than 0
Yes
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
TextReplace the pattern https?://\S+ with https://t.co/LinkLength in postText and save it as countedText.Replace the pattern https?://\S+ with https://t.co/LinkLength in postText and save it as countedText.
TextCount the characters in countedText and save it as countedLength.Count the characters in countedText and save it as countedLength.
Decisions & LoopsIf countedLength is greater than 280If countedLength is greater than 280
Yes
ListsAdd Row rowNumber: the post is countedLength characters as X counts them (a link counts as 23), over the 280 X accepts, so it was left in the queue. Shorten it. to the end of problems and save it as problems.Add Row rowNumber: the post is countedLength characters as X counts them (a link counts as 23), over the 280 X accepts, so it was left in the queue. Shorten it. to the end of problems and save it as problems.
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
Decisions & LoopsIf postsPublished is greater than or equal to maxPostsThisRunIf postsPublished is greater than or equal to maxPostsThisRun
Yes
ValuesAdd 1 to dueLeft.Add 1 to dueLeft.
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
Google SheetsGoogle SheetsOverwrite row rowNumber of queueTabName in queueSheetUrl.Overwrite row rowNumber of queueTabName in queueSheetUrl.
Decisions & LoopsIf a step below fails, save the error as postProblemIf a step below fails, save the error as postProblem
Try
XXPost: postText. Save it as published.Post: postText. Save it as published.
On error
ListsAdd Row rowNumber: X did not publish it - postProblem.message to the end of problems and save it as problems.Add Row rowNumber: X did not publish it - postProblem.message to the end of problems and save it as problems.
Google SheetsGoogle SheetsOverwrite row rowNumber of queueTabName in queueSheetUrl.Overwrite row rowNumber of queueTabName in queueSheetUrl.
Decisions & LoopsSkip to the next item.Skip to the next item.
ValuesAdd 1 to postsPublished.Add 1 to postsPublished.
ValuesSet postLink to https://x.com/@authorHandle/status/@published.tweet.id.Set postLink to https://x.com/@authorHandle/status/@published.tweet.id.
Decisions & LoopsIf a step below fails, save the error as linkProblemIf a step below fails, save the error as linkProblem
Try
Google SheetsGoogle SheetsOverwrite row rowNumber of queueTabName in queueSheetUrl.Overwrite row rowNumber of queueTabName in queueSheetUrl.
On error
ListsAdd Row rowNumber was published (postLink) but its link could not be written back, so the row still reads 'in progress'. Paste the link into it. Google Sheets said: linkProblem.message to the end of problems and save it as problems.Add Row rowNumber was published (postLink) but its link could not be written back, so the row still reads 'in progress'. Paste the link into it. Google Sheets said: linkProblem.message to the end of problems and save it as problems.
On error
ListsAdd Row row.row_number could not be handled, so nothing was posted from it: rowProblem.message to the end of problems and save it as problems.Add Row row.row_number could not be handled, so nothing was posted from it: rowProblem.message to the end of problems and save it as problems.
ValuesSet summaryLines to an empty list.Set summaryLines to an empty list.
ListsAdd Published postsPublished post(s) from the queue. to the end of summaryLines and save it as summaryLines.Add Published postsPublished post(s) from the queue. to the end of summaryLines and save it as summaryLines.
Decisions & LoopsIf dueLeft is 1If dueLeft is 1
Yes
ListsAdd 1 due post left for tomorrow, because 'maxPostsThisRun' is maxPostsThisRun. to the end of summaryLines and save it as summaryLines.Add 1 due post left for tomorrow, because 'maxPostsThisRun' is maxPostsThisRun. to the end of summaryLines and save it as summaryLines.
Else if @dueLeft is greater than 1
ListsAdd dueLeft due posts left for tomorrow, because 'maxPostsThisRun' is maxPostsThisRun. to the end of summaryLines and save it as summaryLines.Add dueLeft due posts left for tomorrow, because 'maxPostsThisRun' is maxPostsThisRun. to the end of summaryLines and save it as summaryLines.
Otherwise
No steps
TextJoin problems with a new line and save it as problemText.Join problems with a new line and save it as problemText.
Decisions & LoopsIf problems isn't emptyIf problems isn't empty
Yes
ListsAdd These rows need your attention:Add These rows need your attention: problemText to the end of summaryLines and save it as summaryLines.
Decisions & LoopsStop and report a problem: summaryLines joined by a blank line.Stop and report a problem: summaryLines joined by a blank line.
No
No steps
Decisions & LoopsFinish the run: summaryLines joined by a blank line.Finish the run: summaryLines joined by a blank line.