Drafts each planned blog topic with AI, emails it to your reviewer, and records their decision in your sheet.

UsesGoogle SheetsOpenAI

Draft blog posts and email them for approval

Once a day, the topics on your content plan that have not been through review yet are written up as blog posts by AI in your house style, and each full draft is emailed to your reviewer to approve or reject, from the email itself. The run waits for their answer, so nothing counts as done until a person has looked at it.

Each topic ends up as a draft in the reviewer's inbox and a row in a 'Draft Approvals' tab in the same spreadsheet: the topic, whether it was approved or changes were requested, the reviewer's notes and the date. Each topic is logged as awaiting review before the reviewer is asked and that log is checked again before each request, so a topic is never drafted twice, and a topic nobody answers is recorded as 'No answer' rather than resent. Your plan tab is only ever read, a topic whose draft fails is picked up next time, and nothing is published anywhere.

You need a plan tab with a 'Topic' column, optionally an 'Angle' column for extra direction, the reviewer's email address and a short description of your audience and house style.

ScheduleRun every day.Run every day.
ValuesSet planSpreadsheetUrl to blank.Set planSpreadsheetUrl to blank.
ValuesSet planTabName to blank.Set planTabName to blank.
ValuesSet reviewerEmail to blank.Set reviewerEmail to blank.
ValuesSet audienceAndStyle to blank.Set audienceAndStyle to blank.
ValuesSet approvalLogTabName to Draft Approvals.Set approvalLogTabName to Draft Approvals.
ValuesSet maxDraftsPerRun to 3.Set maxDraftsPerRun to 3.
Decisions & LoopsIf a step below fails, save the error as logTabErrorIf a step below fails, save the error as logTabError
Try
Google SheetsGoogle SheetsCreate the tab approvalLogTabName in planSpreadsheetUrl.Create the tab approvalLogTabName in planSpreadsheetUrl.
On error
ValuesShow in the output: Using the existing '@approvalLogTabName' tab: logTabError as text.Show in the output: Using the existing '@approvalLogTabName' tab: logTabError as text.
Google SheetsGoogle SheetsRead the rows of planSpreadsheetUrl. Save it as approvalLog.Read the rows of planSpreadsheetUrl. Save it as approvalLog.
Decisions & LoopsIf approvalLog.headers is emptyIf approvalLog.headers is empty
Yes
Google SheetsGoogle SheetsAdd a row to approvalLogTabName in planSpreadsheetUrl.Add a row to approvalLogTabName in planSpreadsheetUrl.
No
No steps
ListsStart an empty list and save it as reviewedTopics.Start an empty list and save it as reviewedTopics.
Decisions & LoopsFor each loggedTopic in approvalLog.rows:For each loggedTopic in approvalLog.rows:
Decisions & LoopsIf a step below fails, save the error as loggedTopicErrorIf a step below fails, save the error as loggedTopicError
Try
ListsAdd loggedTopic.Topic in lowercase to the end of reviewedTopics and save it as reviewedTopics.Add loggedTopic.Topic in lowercase to the end of reviewedTopics and save it as reviewedTopics.
On error
ValuesShow in the output: Ignoring a row in '@approvalLogTabName' that has no Topic.Show in the output: Ignoring a row in '@approvalLogTabName' that has no Topic.
Google SheetsGoogle SheetsRead the rows of planSpreadsheetUrl. Save it as plan.Read the rows of planSpreadsheetUrl. Save it as plan.
ValuesSet draftsThisRun to 0.Set draftsThisRun to 0.
Decisions & LoopsFor each plannedTopic in plan.rows:For each plannedTopic in plan.rows:
Decisions & LoopsIf draftsThisRun is greater than or equal to maxDraftsPerRunIf draftsThisRun is greater than or equal to maxDraftsPerRun
Yes
ValuesShow in the output: Reached this run's limit of drafts. The rest of the plan is picked up on the next run..Show in the output: Reached this run's limit of drafts. The rest of the plan is picked up on the next run..
Decisions & LoopsStop repeating.Stop repeating.
No
No steps
ValuesSet topic to blank.Set topic to blank.
Decisions & LoopsIf a step below fails, save the error as topicErrorIf a step below fails, save the error as topicError
Try
TextTrim plannedTopic.Topic and save it as topic.Trim plannedTopic.Topic and save it as topic.
On error
ValuesShow in the output: Skipping a row in '@planTabName' that has no Topic.Show in the output: Skipping a row in '@planTabName' that has no Topic.
Decisions & LoopsIf topic is emptyIf topic is empty
Yes
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
Decisions & LoopsIf reviewedTopics contains topic in lowercaseIf reviewedTopics contains topic in lowercase
Yes
ValuesShow in the output: Already sent for approval, skipping: topic.Show in the output: Already sent for approval, skipping: topic.
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
ValuesSet angle to blank.Set angle to blank.
Decisions & LoopsIf a step below fails, save the error as angleErrorIf a step below fails, save the error as angleError
Try
TextTrim plannedTopic.Angle and save it as angle.Trim plannedTopic.Angle and save it as angle.
On error
ValuesChange angle to blank.Change angle to blank.
ValuesSet brief to Topic: topic.Set brief to Topic: topic.
Decisions & LoopsIf angle isn't emptyIf angle isn't empty
Yes
ValuesChange brief to briefChange brief to brief Angle to take: angle.
No
No steps
ValuesSet prompt to You are writing one post for a company blog.Set prompt to You are writing one post for a company blog. Audience and house style: audienceAndStyle brief Write the finished post as plain text an editor can read in an email. Open with a short introduction, put each section heading on its own line, and close with a brief conclusion. Aim for 600-900 words, use concrete examples, and do not invent statistics, quotes, customer names or links. Return only the post itself - no title line and no commentary before or after it.
ValuesSet draft to blank.Set draft to blank.
Decisions & LoopsIf a step below fails, save the error as writingErrorIf a step below fails, save the error as writingError
Try
TextTrim the response text from the OpenAI โ€œAsk OpenAIโ€ step and save it as draft.Trim the response text from the OpenAI โ€œAsk OpenAIโ€ step and save it as draft.
On error
ValuesShow in the output: Could not draft '@topic': writingError as text.Show in the output: Could not draft '@topic': writingError as text.
Decisions & LoopsIf draft is emptyIf draft is empty
Yes
ValuesShow in the output: No draft text came back for '@topic'. It stays on the plan for the next run.Show in the output: No draft text came back for '@topic'. It stays on the plan for the next run.
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
ValuesSet loggedNow to nothing.Set loggedNow to nothing.
Decisions & LoopsIf a step below fails, save the error as logReadErrorIf a step below fails, save the error as logReadError
Try
Google SheetsGoogle SheetsRead column 'A' of approvalLogTabName in planSpreadsheetUrl. Save it as loggedNow.Read column 'A' of approvalLogTabName in planSpreadsheetUrl. Save it as loggedNow.
On error
ValuesShow in the output: Could not read the '@approvalLogTabName' tab, so '@topic' stays on the plan for the next run: logReadError as text.Show in the output: Could not read the '@approvalLogTabName' tab, so '@topic' stays on the plan for the next run: logReadError as text.
Decisions & LoopsSkip to the next item.Skip to the next item.
Decisions & LoopsIf loggedNow.values contains topicIf loggedNow.values contains topic
Yes
ValuesShow in the output: Already waiting on another run's review, skipping: topic.Show in the output: Already waiting on another run's review, skipping: topic.
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
Decisions & LoopsIf a step below fails, save the error as claimErrorIf a step below fails, save the error as claimError
Try
Google SheetsGoogle SheetsAdd a row to approvalLogTabName in planSpreadsheetUrl.Add a row to approvalLogTabName in planSpreadsheetUrl.
On error
ValuesShow in the output: Could not log '@topic' as awaiting review, so it stays on the plan for the next run: claimError as text.Show in the output: Could not log '@topic' as awaiting review, so it stays on the plan for the next run: claimError as text.
Decisions & LoopsSkip to the next item.Skip to the next item.
ValuesSet approvalStatus to No answer.Set approvalStatus to No answer.
ValuesSet reviewerNotes to blank.Set reviewerNotes to blank.
Decisions & LoopsIf a step below fails, save the error as approvalErrorIf a step below fails, save the error as approvalError
Try
ApprovalsRequest approval: Approve the draft for @topic? Save it as review.Request approval: Approve the draft for @topic? Save it as review. Who to ask: reviewerEmailApprove to record the draft as approved. Reject it, with a comment saying what you want changed, and it is recorded as changes requested. Nothing is published either way. draft
Decisions & LoopsIf review.approved is yesIf review.approved is yes
Yes
ValuesChange approvalStatus to Approved.Change approvalStatus to Approved.
Else if @review.timed_out is no
ValuesChange approvalStatus to Changes requested.Change approvalStatus to Changes requested.
Otherwise
No steps
TextTrim review.comment and save it as reviewerNotes.Trim review.comment and save it as reviewerNotes.
On error
ValuesShow in the output: The review of '@topic' could not be sent to reviewerEmail: approvalError as text.Show in the output: The review of '@topic' could not be sent to reviewerEmail: approvalError as text.
ValuesChange approvalStatus to Not sent for review.Change approvalStatus to Not sent for review.
ValuesChange reviewerNotes to approvalError.message.Change reviewerNotes to approvalError.message.
Decisions & LoopsIf a step below fails, save the error as logErrorIf a step below fails, save the error as logError
Try
Google SheetsGoogle SheetsAdd a row to approvalLogTabName in planSpreadsheetUrl.Add a row to approvalLogTabName in planSpreadsheetUrl.
On error
ValuesShow in the output: Could not record the outcome for '@topic': logError as text.Show in the output: Could not record the outcome for '@topic': logError as text.
Decisions & LoopsIf approvalStatus is Not sent for reviewIf approvalStatus is Not sent for review
Yes
Decisions & LoopsStop and report a problem: The review of '@topic' could not be sent to reviewerEmail, so the run stopped rather than use up more topics. Check 'reviewerEmail', then delete '@topic' from the '@approvalLogTabName' tab to have it drafted again.Stop and report a problem: The review of '@topic' could not be sent to reviewerEmail, so the run stopped rather than use up more topics. Check 'reviewerEmail', then delete '@topic' from the '@approvalLogTabName' tab to have it drafted again.
No
No steps
ValuesAdd 1 to draftsThisRun.Add 1 to draftsThisRun.