Reminds the rep, then a manager, when a routed lead sits unclaimed, and posts a nurture message whenever a not-yet-ready lead is due one.

UsesClaudeGmailGoogle SheetsSlack

Chase leads nobody has picked up and nurture the not-yet-ready

Every 15 minutes, the 'Ledger' tab that 'Route every new sales enquiry to the right rep' writes is checked for leads nobody has claimed. Once a routed lead passes its reply deadline, its rep is reminded in Slack; 30 minutes later a manager is told, and 30 minutes after that it is offered to anyone who wants it, which is the last reminder. Every reminder says how to claim the lead: type the time in its 'Claimed At' cell. Leads parked for nurture because they were not ready get up to five nurture messages, 0, 4, 8, 15 and 22 days after they enquired, each written by AI from what they originally said and posted in Slack for a person to send.

Each step is written to the lead's Ledger row before its message goes out, so no lead hears the same step twice, and after the last step it is left alone; a filled 'Claimed At' cell stops everything for that lead. Only the cells it changes are written, so nothing a rep types is overwritten. If a row cannot be updated, the AI fails or Slack cannot be reached, the run is marked failed and names the leads.

You need the routing workflow and its spreadsheet in place, Google Sheets, Slack and Anthropic connected (and Gmail if nurture messages should also be saved as drafts), and the spreadsheet link and Slack channel filled in. A manager's Slack member ID is optional.

ScheduleRun every 15 minutes.Run every 15 minutes.
ValuesSet spreadsheetUrl to blank.Set spreadsheetUrl to blank.
ValuesSet slackChannelId to blank.Set slackChannelId to blank.
ValuesSet salesManagerSlackId to blank.Set salesManagerSlackId to blank.
ValuesSet minutesBetweenEscalations to 30.Set minutesBetweenEscalations to 30.
ValuesSet nurtureTouchDaysAfterEnquiry to 0,4,8,15,22.Set nurtureTouchDaysAfterEnquiry to 0,4,8,15,22.
ValuesSet replyMode to slack.Set replyMode to slack.
ValuesSet ledger to {status:,headers:[],rows:[],count:0}.Set ledger to {status:,headers:[],rows:[],count:0}.
Decisions & LoopsIf a step below fails, save the error as ledgerErrorIf a step below fails, save the error as ledgerError
Try
Google SheetsGoogle SheetsRead the rows of spreadsheetUrl. Save it as ledger.Read the rows of spreadsheetUrl. Save it as ledger.
On error
Decisions & LoopsStop and report a problem: Could not read the 'Ledger' tab. This workflow reads the spreadsheet that 'Route every new sales enquiry to the right rep' writes - check that the link on the step called 'Create spreadsheetUrl' is the same one and that it has a tab named exactly 'Ledger'. The error was: ledgerError.message.Stop and report a problem: Could not read the 'Ledger' tab. This workflow reads the spreadsheet that 'Route every new sales enquiry to the right rep' writes - check that the link on the step called 'Create spreadsheetUrl' is the same one and that it has a tab named exactly 'Ledger'. The error was: ledgerError.message.
Decisions & LoopsIf ledger.rows isn't empty and ledger.headers doesn't contain Submission ID or…If ledger.rows isn't empty and ledger.headers doesn't contain Submission ID or…
Yes
Decisions & LoopsStop and report a problem: The 'Ledger' tab is missing columns this workflow needs. Its heading row must include, spelled exactly like this: Submission ID, Decision, Claimed At, SLA Due, Next Touch At, Touches Sent, Processed At. The 'Inbound lead workspace setup' template builds the tab with every heading in place.Stop and report a problem: The 'Ledger' tab is missing columns this workflow needs. Its heading row must include, spelled exactly like this: Submission ID, Decision, Claimed At, SLA Due, Next Touch At, Touches Sent, Processed At. The 'Inbound lead workspace setup' template builds the tab with every heading in place.
No
No steps
TextSplit nurtureTouchDaysAfterEnquiry by , and save it as touchDayList.Split nurtureTouchDaysAfterEnquiry by , and save it as touchDayList.
ValuesSet managerMention to A manager.Set managerMention to A manager.
Decisions & LoopsIf salesManagerSlackId isn't blankIf salesManagerSlackId isn't blank
Yes
ValuesChange managerMention to <@@salesManagerSlackId>.Change managerMention to <@@salesManagerSlackId>.
No
No steps
ValuesSet escalationsThisRun to 0.Set escalationsThisRun to 0.
ValuesSet nurtureTouchesThisRun to 0.Set nurtureTouchesThisRun to 0.
ListsKeep each item in ledger.rows where item["Submission ID"] isn't blank and item["Claimed At"] is blank and… and save it as openLeads.Keep each item in ledger.rows where item["Submission ID"] isn't blank and item["Claimed At"] is blank and… and save it as openLeads.
ValuesSet chaseProblems to an empty list.Set chaseProblems to an empty list.
Decisions & LoopsFor each row in openLeads:For each row in openLeads:
Decisions & LoopsIf a step below fails, save the error as sweepErrorIf a step below fails, save the error as sweepError
Try
TextTrim row["Submission ID"] and save it as submissionId.Trim row["Submission ID"] and save it as submissionId.
Decisions & LoopsIf submissionId is emptyIf submissionId is empty
Yes
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
TextMake row.Decision lowercase and save it as decision.Make row.Decision lowercase and save it as decision.
TextTrim row["Claimed At"] and save it as claimedAt.Trim row["Claimed At"] and save it as claimedAt.
ValuesSet rowNumber to row.row_number.Set rowNumber to row.row_number.
TextTrim row.Email and save it as leadEmail.Trim row.Email and save it as leadEmail.
TextTrim row.Company and save it as leadCompany.Trim row.Company and save it as leadCompany.
TextTrim row["Routed To"] and save it as routedTo.Trim row["Routed To"] and save it as routedTo.
TextTrim row["Routed To Slack ID"] and save it as routedToSlackId.Trim row["Routed To Slack ID"] and save it as routedToSlackId.
ValuesSet routedMention to routedTo.Set routedMention to routedTo.
Decisions & LoopsIf routedToSlackId isn't emptyIf routedToSlackId isn't empty
Yes
ValuesChange routedMention to <@@routedToSlackId>.Change routedMention to <@@routedToSlackId>.
No
No steps
TextTrim row["Processed At"] and save it as processedAt.Trim row["Processed At"] and save it as processedAt.
TextTrim row["Next Touch At"] and save it as nextTouchAt.Trim row["Next Touch At"] and save it as nextTouchAt.
ValuesSet touchesSent to 0.Set touchesSent to 0.
Decisions & LoopsIf row["Touches Sent"] isn't blankIf row["Touches Sent"] isn't blank
Yes
MathChange touchesSent to row["Touches Sent"] minus 0.Change touchesSent to row["Touches Sent"] minus 0.
No
No steps
Decisions & LoopsIf claimedAt isn't emptyIf claimedAt isn't empty
Yes
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
ValuesSet isDue to true.Set isDue to true.
Decisions & LoopsIf nextTouchAt isn't emptyIf nextTouchAt isn't empty
Yes
ValuesChange isDue to the difference between nextTouchAt and the current date and time in minutes is greater than or equal to 0.Change isDue to the difference between nextTouchAt and the current date and time in minutes is greater than or equal to 0.
No
No steps
Decisions & LoopsIf isDue is noIf isDue is no
Yes
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
Decisions & LoopsIf decision is routeIf decision is route
Yes
TextTrim row["SLA Due"] and save it as replyDeadline.Trim row["SLA Due"] and save it as replyDeadline.
ValuesSet minutesOverdue to 0.Set minutesOverdue to 0.
Decisions & LoopsIf replyDeadline isn't emptyIf replyDeadline isn't empty
Yes
Dates & TimesCount the minutes from replyDeadline to the current date and time and save it as minutesOverdue.Count the minutes from replyDeadline to the current date and time and save it as minutesOverdue.
No
No steps
Decisions & LoopsIf minutesOverdue is greater than 0If minutesOverdue is greater than 0
Yes
ValuesSet escalationNote to blank.Set escalationNote to blank.
ValuesSet newReason to QUEUE_UNACCEPTED.Set newReason to QUEUE_UNACCEPTED.
Dates & TimesMove the current date and time by minutesBetweenEscalations minutes and save it as nextCheckAt.Move the current date and time by minutesBetweenEscalations minutes and save it as nextCheckAt.
Dates & TimesMake nextCheckAt readable as an ISO 8601 timestamp and save it as nextCheckText.Make nextCheckAt readable as an ISO 8601 timestamp and save it as nextCheckText.
Decisions & LoopsIf touchesSent is 0If touchesSent is 0
Yes
ValuesChange escalationNote to :hourglass_flowing_sand: routedMention this lead has been waiting minutesOverdue minutes and nobody has picked it up yet.Change escalationNote to :hourglass_flowing_sand: routedMention this lead has been waiting minutesOverdue minutes and nobody has picked it up yet. leadEmail at leadCompany.
Else if @touchesSent is 1
ValuesChange escalationNote to :rotating_light: managerMention escalating: leadEmail at leadCompany plus has now been unclaimed for minutesOverdue plus minutes. It was routed to routedTo.Change escalationNote to :rotating_light: managerMention escalating: leadEmail at leadCompany plus has now been unclaimed for minutesOverdue plus minutes. It was routed to routedTo.
Otherwise
ValuesChange escalationNote to :arrows_counterclockwise: Returning to the pool - leadEmail at leadCompany went unclaimed for minutesOverdue minutes. Anyone who wants it, take it - this is the last reminder about it.Change escalationNote to :arrows_counterclockwise: Returning to the pool - leadEmail at leadCompany went unclaimed for minutesOverdue minutes. Anyone who wants it, take it - this is the last reminder about it.
ValuesChange newReason to RETURNED_TO_POOL.Change newReason to RETURNED_TO_POOL.
ValuesChange nextCheckText to blank.Change nextCheckText to blank.
ValuesAdd 1 to touchesSent.Add 1 to touchesSent.
Google SheetsGoogle SheetsSet Reason Code: newReason; Next Touch At: nextCheckText; Touches Sent: touchesSent on row rowNumber of 'Ledger' in spreadsheetUrl.Set Reason Code: newReason; Next Touch At: nextCheckText; Touches Sent: touchesSent on row rowNumber of 'Ledger' in spreadsheetUrl.
Decisions & LoopsIf a step below fails, save the error as postErrorIf a step below fails, save the error as postError
Try
SlackSlackSend a message to slackChannelId.Send a message to slackChannelId.escalationNote To claim it, type the time (or your name) in its Claimed At cell: row rowNumber of the <spreadsheetUrl|Ledger tab>.
On error
ListsAdd leadEmail (Ledger row rowNumber): the reminder was recorded on the Ledger but could not be posted to Slack, so nobody was told (postError.message). to the end of chaseProblems and save it as chaseProblems.Add leadEmail (Ledger row rowNumber): the reminder was recorded on the Ledger but could not be posted to Slack, so nobody was told (postError.message). to the end of chaseProblems and save it as chaseProblems.
ValuesAdd 1 to escalationsThisRun.Add 1 to escalationsThisRun.
No
No steps
No
No steps
Decisions & LoopsIf decision is nurtureIf decision is nurture
Yes
Decisions & LoopsIf touchesSent is greater than or equal to the length of touchDayListIf touchesSent is greater than or equal to the length of touchDayList
Yes
Google SheetsGoogle SheetsSet Reason Code: NURTURE_EXHAUSTED; Next Touch At: on row rowNumber of 'Ledger' in spreadsheetUrl.Set Reason Code: NURTURE_EXHAUSTED; Next Touch At: on row rowNumber of 'Ledger' in spreadsheetUrl.
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
MathSet touchNumber to touchesSent plus 1.Set touchNumber to touchesSent plus 1.
ValuesSet nurturePrompt to Write nurture touch number touchNumber of the length of touchDayList plus for an inbound lead who was a good fit but had no urgency when they first got in touch.Set nurturePrompt to Write nurture touch number touchNumber of the length of touchDayList plus for an inbound lead who was a good fit but had no urgency when they first got in touch. They are leadEmail at leadCompany. What they originally said: row.Evidence Why we thought they were worth keeping: row.Reasoning Write under 80 words. Send one useful thing - a relevant example, a number, a question worth answering - not a check-in. Never say 'just circling back', 'touching base' or 'following up'. Later touches in the sequence should be shorter and less frequent in tone, not more insistent. Output only the message body.
ClaudeClaudeAsk Claude: nurturePrompt. Save it as nurtureDraft.Ask Claude: nurturePrompt. Save it as nurtureDraft. Maximum response length: 400
ValuesAdd 1 to touchesSent.Add 1 to touchesSent.
ValuesSet nextOffsetDays to 0.Set nextOffsetDays to 0.
Decisions & LoopsIf touchesSent is less than the length of touchDayListIf touchesSent is less than the length of touchDayList
Yes
MathChange nextOffsetDays to touchDayList[@touchesSent] minus 0.Change nextOffsetDays to touchDayList[@touchesSent] minus 0.
No
No steps
ValuesSet anchorDate to processedAt.Set anchorDate to processedAt.
Decisions & LoopsIf anchorDate is emptyIf anchorDate is empty
Yes
Dates & TimesMake the current date and time readable as an ISO 8601 timestamp and save it as anchorDate.Make the current date and time readable as an ISO 8601 timestamp and save it as anchorDate.
No
No steps
Dates & TimesMove anchorDate by nextOffsetDays days and save it as nextTouchDue.Move anchorDate by nextOffsetDays days and save it as nextTouchDue.
Dates & TimesMake nextTouchDue readable as an ISO 8601 timestamp and save it as nextTouchText.Make nextTouchDue readable as an ISO 8601 timestamp and save it as nextTouchText.
Google SheetsGoogle SheetsSet Reason Code: NURTURING; Next Touch At: nextTouchText; Touches Sent: touchesSent on row rowNumber of 'Ledger' in spreadsheetUrl.Set Reason Code: NURTURING; Next Touch At: nextTouchText; Touches Sent: touchesSent on row rowNumber of 'Ledger' in spreadsheetUrl.
Decisions & LoopsIf a step below fails, save the error as postErrorIf a step below fails, save the error as postError
Try
SlackSlackSend a message to slackChannelId.Send a message to slackChannelId.:seedling: *Nurture touch touchNumber of the length of touchDayList due* - leadEmail at leadCompany plus nurtureDraft.response_text
On error
ListsAdd leadEmail (Ledger row rowNumber): the nurture touch was recorded on the Ledger but could not be posted to Slack, so nobody was told (postError.message). to the end of chaseProblems and save it as chaseProblems.Add leadEmail (Ledger row rowNumber): the nurture touch was recorded on the Ledger but could not be posted to Slack, so nobody was told (postError.message). to the end of chaseProblems and save it as chaseProblems.
Decisions & LoopsIf replyMode in lowercase is draft and leadEmail isn't emptyIf replyMode in lowercase is draft and leadEmail isn't empty
Yes
Decisions & LoopsIf a step below fails, save the error as draftErrorIf a step below fails, save the error as draftError
Try
GmailGmailDraft an email to leadEmail.Draft an email to leadEmail. Subject: A thought on your enquirynurtureDraft.response_text
On error
ListsAdd leadEmail (Ledger row rowNumber): nurture touch touchNumber was posted to Slack but the Gmail draft could not be saved, so send it from the Slack post (draftError.message). to the end of chaseProblems and save it as chaseProblems.Add leadEmail (Ledger row rowNumber): nurture touch touchNumber was posted to Slack but the Gmail draft could not be saved, so send it from the Slack post (draftError.message). to the end of chaseProblems and save it as chaseProblems.
No
No steps
ValuesAdd 1 to nurtureTouchesThisRun.Add 1 to nurtureTouchesThisRun.
No
No steps
On error
ListsAdd row.Email (Ledger row row.row_number) was not updated and will be tried again on the next check (sweepError.message). to the end of chaseProblems and save it as chaseProblems.Add row.Email (Ledger row row.row_number) was not updated and will be tried again on the next check (sweepError.message). to the end of chaseProblems and save it as chaseProblems.
Decisions & LoopsIf chaseProblems isn't emptyIf chaseProblems isn't empty
Yes
TextJoin chaseProblems with a new line and save it as problemList.Join chaseProblems with a new line and save it as problemList.
Decisions & LoopsStop and report a problem: Some leads need attention:Stop and report a problem: Some leads need attention: problemList.
No
No steps
Decisions & LoopsIf escalationsThisRun plus nurtureTouchesThisRun is greater than 0If escalationsThisRun plus nurtureTouchesThisRun is greater than 0
Yes
ValuesShow in the output: Checked the Ledger: escalationsThisRun reminder(s) and nurtureTouchesThisRun nurture touch(es) posted to Slack.Show in the output: Checked the Ledger: escalationsThisRun reminder(s) and nurtureTouchesThisRun nurture touch(es) posted to Slack.
No
No steps