Scores how satisfied each client sounds in the emails you log every week and keeps a client health dashboard tab up to date.

UsesClaudeGoogle Sheets

Score client health from your email log

Every Monday at 08:00, the client emails you have logged on a spreadsheet tab are read, and each new one is summarised and scored by AI from 1 (about to leave) to 10 (delighted) for how satisfied the client sounds. The result goes into a client health tab: one row per client with their latest score, a status of Healthy, Watch or At risk, the trend since their previous email, the summary and the reasons.

The accounts that need a call this week are the ones marked At risk. Each email is scored once, so re-running never rescores old mail, and the first run starts from recent emails rather than a year of history. A row with no client or no message is reported and skipped, a reply with no readable score leaves the client's row untouched until next week, and a health tab with an unrecognised header row is never written into.

Your email log needs 'Client' and 'Message' columns, with 'Date' and 'Subject' used when present, and new emails added as rows at the bottom. The health tab is created for you if it does not exist yet.

ScheduleRun every Monday at 08:00.Run every Monday at 08:00.
ValuesSet spreadsheetUrl to blank.Set spreadsheetUrl to blank.
ValuesSet emailsTabName to Emails.Set emailsTabName to Emails.
ValuesSet healthTabName to Client health.Set healthTabName to Client health.
ValuesSet maxEmailsPerRun to 60.Set maxEmailsPerRun to 60.
ValuesSet maxMessageChars to 6000.Set maxMessageChars to 6000.
ValuesSet maxRememberedEmails to 300.Set maxRememberedEmails to 300.
Stored ValuesGet the stored value 'client_health_scored'. Save it as scoredStore.Get the stored value 'client_health_scored'. Save it as scoredStore.
ValuesSet remembered to an empty list.Set remembered to an empty list.
Decisions & LoopsIf scoredStore.found is yesIf scoredStore.found is yes
Yes
Decisions & LoopsIf a step below fails, save the error as memoryErrorIf a step below fails, save the error as memoryError
Try
ListsTake scoredStore.value from position 0 on and save it as remembered.Take scoredStore.value from position 0 on and save it as remembered.
On error
Decisions & LoopsStop and report a problem: The stored value 'client_health_scored' holds something that is not a list, so the workflow cannot tell which emails it has already scored. Delete that stored value from the workflow's settings to start again from the most recent emails, then run it again.Stop and report a problem: The stored value 'client_health_scored' holds something that is not a list, so the workflow cannot tell which emails it has already scored. Delete that stored value from the workflow's settings to start again from the most recent emails, then run it again.
No
No steps
ValuesSet emailsRead to nothing.Set emailsRead to nothing.
Decisions & LoopsIf a step below fails, save the error as emailsReadErrorIf a step below fails, save the error as emailsReadError
Try
Google SheetsGoogle SheetsRead the rows of spreadsheetUrl. Save it as emailsRead.Read the rows of spreadsheetUrl. Save it as emailsRead.
On error
Decisions & LoopsStop and report a problem: Could not read the '@emailsTabName' tab: emailsReadError.message. Check the tab name and that the Google Sheets connection can open the spreadsheet.Stop and report a problem: Could not read the '@emailsTabName' tab: emailsReadError.message. Check the tab name and that the Google Sheets connection can open the spreadsheet.
Decisions & LoopsIf emailsRead is emptyIf emailsRead is empty
Yes
Decisions & LoopsStop and report a problem: Reading the '@emailsTabName' tab returned nothing. Check the tab name and the Google Sheets connection, then run it again.Stop and report a problem: Reading the '@emailsTabName' tab returned nothing. Check the tab name and the Google Sheets connection, then run it again.
No
No steps
ValuesSet emailHeaders to emailsRead.headers.Set emailHeaders to emailsRead.headers.
ValuesSet emailRows to emailsRead.rows.Set emailRows to emailsRead.rows.
Decisions & LoopsIf emailRows is emptyIf emailRows is empty
Yes
Decisions & LoopsFinish the run: The '@emailsTabName' tab has no emails logged yet, so there is nothing to score. The dashboard in '@healthTabName' is unchanged.Finish the run: The '@emailsTabName' tab has no emails logged yet, so there is nothing to score. The dashboard in '@healthTabName' is unchanged.
No
No steps
Decisions & LoopsIf emailHeaders doesn't contain Client or emailHeaders doesn't contain…If emailHeaders doesn't contain Client or emailHeaders doesn't contain…
Yes
Decisions & LoopsStop and report a problem: The '@emailsTabName' tab needs Client and Message columns in its header row, spelled exactly like that (Date and Subject columns are optional). Its header row currently reads: emailHeaders joined by , .Stop and report a problem: The '@emailsTabName' tab needs Client and Message columns in its header row, spelled exactly like that (Date and Subject columns are optional). Its header row currently reads: emailHeaders joined by , .
No
No steps
ValuesSet logOrderDays to 0.Set logOrderDays to 0.
Decisions & LoopsIf the length of emailRows is greater than 1If the length of emailRows is greater than 1
Yes
Decisions & LoopsIf a step below fails, save the error as logDateErrorIf a step below fails, save the error as logDateError
Try
Dates & TimesCount the days from emailRows[0].Date to emailRows[-1].Date and save it as logOrderDays.Count the days from emailRows[0].Date to emailRows[-1].Date and save it as logOrderDays.
On error
ValuesChange logOrderDays to 0.Change logOrderDays to 0.
No
No steps
Decisions & LoopsIf logOrderDays is less than 0If logOrderDays is less than 0
Yes
Decisions & LoopsStop and report a problem: The '@emailsTabName' tab looks newest-first: its first email is dated emailRows[0].Date and its last emailRows[-1].Date. This workflow takes new emails from the bottom of the log, so a newest-first log would hide new mail from it. Sort the tab by Date, oldest first, and add new emails as rows at the bottom, then run it again.Stop and report a problem: The '@emailsTabName' tab looks newest-first: its first email is dated emailRows[0].Date and its last emailRows[-1].Date. This workflow takes new emails from the bottom of the log, so a newest-first log would hide new mail from it. Sort the tab by Date, oldest first, and add new emails as rows at the bottom, then run it again.
No
No steps
ValuesSet healthRead to nothing.Set healthRead to nothing.
Decisions & LoopsIf a step below fails, save the error as healthReadErrorIf a step below fails, save the error as healthReadError
Try
Google SheetsGoogle SheetsRead the rows of spreadsheetUrl. Save it as healthRead.Read the rows of spreadsheetUrl. Save it as healthRead.
On error
Decisions & LoopsIf a step below fails, save the error as createTabErrorIf a step below fails, save the error as createTabError
Try
Google SheetsGoogle SheetsCreate the tab healthTabName in spreadsheetUrl.Create the tab healthTabName in spreadsheetUrl.
ValuesChange healthRead to {headers:[],rows:[]}.Change healthRead to {headers:[],rows:[]}.
On error
Decisions & LoopsStop and report a problem: Could not read or create the '@healthTabName' tab: createTabError.message. Add an empty tab with that name to the spreadsheet, then run it again.Stop and report a problem: Could not read or create the '@healthTabName' tab: createTabError.message. Add an empty tab with that name to the spreadsheet, then run it again.
ValuesSet healthHeaders to healthRead.headers.Set healthHeaders to healthRead.headers.
ValuesSet expectedHeaders to [Client, Health score, Status, Trend, Summary, Why, Last email, Emails scored, Updated at].Set expectedHeaders to [Client, Health score, Status, Trend, Summary, Why, Last email, Emails scored, Updated at].
ValuesSet expectedHeaderLine to Client|Health score|Status|Trend|Summary|Why|Last email|Emails scored|Updated at.Set expectedHeaderLine to Client|Health score|Status|Trend|Summary|Why|Last email|Emails scored|Updated at.
TextJoin the items from index 0 up to 9 of healthHeaders with | and save it as actualHeaderLine.Join the items from index 0 up to 9 of healthHeaders with | and save it as actualHeaderLine.
ValuesSet mustWriteHeaders to false.Set mustWriteHeaders to false.
Decisions & LoopsIf healthHeaders is emptyIf healthHeaders is empty
Yes
ValuesChange mustWriteHeaders to true.Change mustWriteHeaders to true.
Else if @actualHeaderLine is not @expectedHeaderLine
Decisions & LoopsIf expectedHeaderLine| starts with actualHeaderLine| (ignoring case)If expectedHeaderLine| starts with actualHeaderLine| (ignoring case)
Yes
ValuesChange mustWriteHeaders to true.Change mustWriteHeaders to true.
No
Decisions & LoopsStop and report a problem: The '@healthTabName' tab has a header row this workflow does not recognise (actualHeaderLine), so it will not write into it. Its first nine columns need to be, in this order: expectedHeaders joined by , . Point 'healthTabName' at an empty tab and the workflow writes them for you.Stop and report a problem: The '@healthTabName' tab has a header row this workflow does not recognise (actualHeaderLine), so it will not write into it. Its first nine columns need to be, in this order: expectedHeaders joined by , . Point 'healthTabName' at an empty tab and the workflow writes them for you.
Otherwise
No steps
Decisions & LoopsIf mustWriteHeaders is yesIf mustWriteHeaders is yes
Yes
Google SheetsGoogle SheetsOverwrite row 1 of healthTabName in spreadsheetUrl.Overwrite row 1 of healthTabName in spreadsheetUrl.
No
No steps
ValuesSet sheetKeys to an empty list.Set sheetKeys to an empty list.
Decisions & LoopsFor each logged in emailRows:For each logged in emailRows:
ValuesSet loggedKey to blank.Set loggedKey to blank.
ValuesSet loggedClient to blank.Set loggedClient to blank.
ValuesSet loggedMessage to blank.Set loggedMessage to blank.
ValuesSet loggedDate to blank.Set loggedDate to blank.
Decisions & LoopsIf logged.Client is yesIf logged.Client is yes
Yes
TextTrim logged.Client and save it as loggedClient.Trim logged.Client and save it as loggedClient.
No
No steps
Decisions & LoopsIf logged.Message is yesIf logged.Message is yes
Yes
TextTrim logged.Message and save it as loggedMessage.Trim logged.Message and save it as loggedMessage.
No
No steps
Decisions & LoopsIf logged.Date is yesIf logged.Date is yes
Yes
TextTrim logged.Date and save it as loggedDate.Trim logged.Date and save it as loggedDate.
No
No steps
Decisions & LoopsIf loggedClient isn't empty and loggedMessage isn't emptyIf loggedClient isn't empty and loggedMessage isn't empty
Yes
ValuesChange loggedKey to loggedClient in lowercase truncated to 40 characters|loggedDate truncated to 24 characters|loggedMessage in lowercase truncated to 40 characters.Change loggedKey to loggedClient in lowercase truncated to 40 characters|loggedDate truncated to 24 characters|loggedMessage in lowercase truncated to 40 characters.
No
No steps
ListsAdd loggedKey to the end of sheetKeys and save it as sheetKeys.Add loggedKey to the end of sheetKeys and save it as sheetKeys.
ValuesSet startPos to 0.Set startPos to 0.
ValuesSet startingFresh to the length of remembered is 0.Set startingFresh to the length of remembered is 0.
ValuesSet runNotes to an empty list.Set runNotes to an empty list.
Decisions & LoopsIf startingFresh is noIf startingFresh is no
Yes
ValuesSet anchorPos to -1.Set anchorPos to -1.
Decisions & LoopsFor each rememberedKey in remembered:For each rememberedKey in remembered:
Lists Find the position of rememberedKey in sheetKeys and save it as foundAt. Find the position of rememberedKey in sheetKeys and save it as foundAt.
Decisions & Loops If foundAt is greater than or equal to 0 If foundAt is greater than or equal to 0
Yes
ValuesChange anchorPos to foundAt.Change anchorPos to foundAt.
Decisions & LoopsStop repeating.Stop repeating.
No
No steps
Decisions & LoopsIf anchorPos is less than 0If anchorPos is less than 0
Yes
ListsAdd None of the the length of remembered emails this workflow remembers scoring are in the '@emailsTabName' tab any more, so it treated the tab as a new log. to the end of runNotes and save it as runNotes.Add None of the the length of remembered emails this workflow remembers scoring are in the '@emailsTabName' tab any more, so it treated the tab as a new log. to the end of runNotes and save it as runNotes.
ValuesChange remembered to an empty list.Change remembered to an empty list.
ValuesChange startingFresh to true.Change startingFresh to true.
No
ValuesChange startPos to anchorPos.Change startPos to anchorPos.
No
No steps
Decisions & LoopsIf startingFresh is yesIf startingFresh is yes
Yes
MathChange startPos to the length of emailRows minus maxEmailsPerRun.Change startPos to the length of emailRows minus maxEmailsPerRun.
Decisions & LoopsIf startPos is less than 0If startPos is less than 0
Yes
ValuesChange startPos to 0.Change startPos to 0.
No
No steps
Decisions & LoopsIf startPos is greater than 0If startPos is greater than 0
Yes
ListsAdd First run: started from the most recent maxEmailsPerRun emails in the tab; the startPos older rows above them will not be scored. to the end of runNotes and save it as runNotes.Add First run: started from the most recent maxEmailsPerRun emails in the tab; the startPos older rows above them will not be scored. to the end of runNotes and save it as runNotes.
No
No steps
No
No steps
ValuesSet newEmails to 0.Set newEmails to 0.
ValuesSet scanPos to 0.Set scanPos to 0.
Decisions & LoopsFor each scanned in emailRows:For each scanned in emailRows:
Decisions & LoopsIf scanPos is greater than or equal to startPosIf scanPos is greater than or equal to startPos
Yes
ListsSet scanKey to sheetKeys[@scanPos].Set scanKey to sheetKeys[@scanPos].
Decisions & LoopsIf scanKey isn't empty and remembered doesn't contain scanKeyIf scanKey isn't empty and remembered doesn't contain scanKey
Yes
ValuesAdd 1 to newEmails.Add 1 to newEmails.
No
No steps
No
No steps
ValuesAdd 1 to scanPos.Add 1 to scanPos.
MathSet surplus to newEmails minus maxEmailsPerRun.Set surplus to newEmails minus maxEmailsPerRun.
Decisions & LoopsIf surplus is less than 0If surplus is less than 0
Yes
ValuesChange surplus to 0.Change surplus to 0.
No
No steps
Decisions & LoopsIf surplus is greater than 0If surplus is greater than 0
Yes
ListsAdd newEmails emails were new but 'maxEmailsPerRun' is maxEmailsPerRun, so only the newest maxEmailsPerRun were scored. The surplus older ones will not be scored; raise 'maxEmailsPerRun' if you log more than that between runs. to the end of runNotes and save it as runNotes.Add newEmails emails were new but 'maxEmailsPerRun' is maxEmailsPerRun, so only the newest maxEmailsPerRun were scored. The surplus older ones will not be scored; raise 'maxEmailsPerRun' if you log more than that between runs. to the end of runNotes and save it as runNotes.
No
No steps
ValuesSet scoredThisRun to 0.Set scoredThisRun to 0.
ValuesSet clientsScored to an empty list.Set clientsScored to an empty list.
ValuesSet skippedSurplus to 0.Set skippedSurplus to 0.
ValuesSet loopPos to 0.Set loopPos to 0.
Decisions & LoopsFor each email in emailRows:For each email in emailRows:
ValuesSet emailPos to loopPos.Set emailPos to loopPos.
ValuesAdd 1 to loopPos.Add 1 to loopPos.
Decisions & LoopsIf emailPos is less than startPosIf emailPos is less than startPos
Yes
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
ListsSet emailKey to sheetKeys[@emailPos].Set emailKey to sheetKeys[@emailPos].
Decisions & LoopsIf emailKey is emptyIf emailKey is empty
Yes
ValuesShow in the output: Skipped row email.row_number in '@emailsTabName': it has no Client or no Message, and both are needed to score it.Show in the output: Skipped row email.row_number in '@emailsTabName': it has no Client or no Message, and both are needed to score it.
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
Decisions & LoopsIf remembered contains emailKeyIf remembered contains emailKey
Yes
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
Decisions & LoopsIf skippedSurplus is less than surplusIf skippedSurplus is less than surplus
Yes
ValuesAdd 1 to skippedSurplus.Add 1 to skippedSurplus.
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
TextTrim email.Client and save it as client.Trim email.Client and save it as client.
TextMake client lowercase and save it as clientKey.Make client lowercase and save it as clientKey.
TextTrim email.Message and save it as message.Trim email.Message and save it as message.
ValuesSet emailDate to blank.Set emailDate to blank.
ValuesSet emailSubject to blank.Set emailSubject to blank.
Decisions & LoopsIf email.Date is yesIf email.Date is yes
Yes
TextTrim email.Date and save it as emailDate.Trim email.Date and save it as emailDate.
No
No steps
Decisions & LoopsIf email.Subject is yesIf email.Subject is yes
Yes
TextTrim email.Subject and save it as emailSubject.Trim email.Subject and save it as emailSubject.
No
No steps
ValuesSet messageForModel to message.Set messageForModel to message.
Decisions & LoopsIf the length of message is greater than maxMessageCharsIf the length of message is greater than maxMessageChars
Yes
ValuesChange messageForModel to message truncated to maxMessageChars charactersChange messageForModel to message truncated to maxMessageChars characters [Message cut off here.].
No
No steps
ValuesSet rowLabel to the email from client (row email.row_number).Set rowLabel to the email from client (row email.row_number).
Decisions & LoopsIf a step below fails, save the error as emailErrorIf a step below fails, save the error as emailError
Try
Google SheetsGoogle SheetsRead the rows of spreadsheetUrl. Save it as healthNow.Read the rows of spreadsheetUrl. Save it as healthNow.
ValuesSet existingRow to nothing.Set existingRow to nothing.
Decisions & LoopsFor each healthRow in healthNow.rows:For each healthRow in healthNow.rows:
Decisions & Loops If healthRow.Client is yes If healthRow.Client is yes
Yes
Decisions & LoopsIf healthRow.Client in lowercase is clientKeyIf healthRow.Client in lowercase is clientKey
Yes
ValuesChange existingRow to healthRow.Change existingRow to healthRow.
Decisions & LoopsStop repeating.Stop repeating.
No
No steps
No
No steps
ClaudeClaudeAsk Claude: 'Summarise one email a client sent us, for the account manager who looks after them and has not read it. Write two or three plain sentences: what the client wrote about, anything they asked for or complained about, and anything that is waiting on a reply from us. Use only what the email says and do not guess at anything it leaves out. No heading, bullet points or preamble.Ask Claude: 'Summarise one email a client sent us, for the account manager who looks after them and has not read it. Write two or three plain sentences: what the client wrote about, anything they asked for or complained about, and anything that is waiting on a reply from us. Use only what the email says and do not guess at anything it leaves out. No heading, bullet points or preamble. Client: client Date: emailDate Subject: emailSubject Email: messageForModel'. Save it as summaryAnswer. Maximum response length: 300
ValuesSet summary to blank.Set summary to blank.
Decisions & LoopsIf summaryAnswer.response_text is yesIf summaryAnswer.response_text is yes
Yes
TextTrim summaryAnswer.response_text and save it as summary.Trim summaryAnswer.response_text and save it as summary.
No
No steps
Decisions & LoopsIf summary is emptyIf summary is empty
Yes
ValuesShow in the output: No summary came back for rowLabel; it will be tried again on the next run.Show in the output: No summary came back for rowLabel; it will be tried again on the next run.
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
ClaudeClaudeAsk Claude: 'You are judging how satisfied a client sounds from one email they sent us. Score their satisfaction from 1 to 10 using the tone of the email and what it asks for: 1 is a client who is angry or about to leave, 4 is one who is unhappy or losing patience, 7 is a routine email with no complaint in it, 10 is a client who is delighted with us.Ask Claude: 'You are judging how satisfied a client sounds from one email they sent us. Score their satisfaction from 1 to 10 using the tone of the email and what it asks for: 1 is a client who is angry or about to leave, 4 is one who is unhappy or losing patience, 7 is a routine email with no complaint in it, 10 is a client who is delighted with us. Reply in exactly this shape and nothing else: SCORE: a whole number from 1 to 10 WHY: one or two sentences naming the words, tone or requests the score rests on Rules you must follow: - The first line must be SCORE: followed by the number and nothing else. - Judge only this email. Do not assume anything about the relationship that the email does not say. - Do not soften. An email that reads as frustrated, disappointed or curt scores low even when it is polite, and an unanswered complaint or a threat to leave scores 3 or lower. - A routine, neutral email with no complaint scores 7. Score above 7 only when the client says something positive about us or our work. - If this is not really from the client (an automatic reply, a newsletter, an out-of-office notice), score 7 and say so in WHY. Client: client Subject: emailSubject Summary: summary Email: messageForModel'. Save it as scoreAnswer. Maximum response length: 200
ValuesSet verdict to blank.Set verdict to blank.
Decisions & LoopsIf scoreAnswer.response_text is yesIf scoreAnswer.response_text is yes
Yes
TextTrim scoreAnswer.response_text and save it as verdict.Trim scoreAnswer.response_text and save it as verdict.
No
No steps
ValuesSet scoreLine to blank.Set scoreLine to blank.
ValuesSet why to blank.Set why to blank.
Decisions & LoopsFor each verdictLine in verdict split by a new line:For each verdictLine in verdict split by a new line:
Text Trim verdictLine and save it as lineText. Trim verdictLine and save it as lineText.
Decisions & Loops If lineText starts with SCORE: (ignoring case) and scoreLine is empty If lineText starts with SCORE: (ignoring case) and scoreLine is empty
Yes
ValuesChange scoreLine to lineText.Change scoreLine to lineText.
Else if @lineText starts with "WHY:" (ignoring case)
TextTrim lineText with WHY: replaced by (ignoring case) and save it as why.Trim lineText with WHY: replaced by (ignoring case) and save it as why.
Otherwise
No steps
TextExtract the first number from scoreLine and save it as scoreRaw.Extract the first number from scoreLine and save it as scoreRaw.
Decisions & LoopsIf scoreRaw is blankIf scoreRaw is blank
Yes
ValuesShow in the output: Could not read a score for rowLabel: the reply began '@verdict truncated to 120 characters'. The client's row is unchanged; it will be tried again on the next run.Show in the output: Could not read a score for rowLabel: the reply began '@verdict truncated to 120 characters'. The client's row is unchanged; it will be tried again on the next run.
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
Decisions & LoopsIf scoreRaw is less than 1 or scoreRaw is greater than 10If scoreRaw is less than 1 or scoreRaw is greater than 10
Yes
ValuesShow in the output: The score for rowLabel came back as scoreRaw, which is not between 1 and 10. The client's row is unchanged; it will be tried again on the next run.Show in the output: The score for rowLabel came back as scoreRaw, which is not between 1 and 10. The client's row is unchanged; it will be tried again on the next run.
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
MathRound scoreRaw to the nearest integer and save it as score.Round scoreRaw to the nearest integer and save it as score.
Decisions & LoopsIf why is emptyIf why is empty
Yes
ValuesChange why to verdict.Change why to verdict.
No
No steps
ValuesSet status to Watch.Set status to Watch.
Decisions & LoopsIf score is greater than or equal to 7If score is greater than or equal to 7
Yes
ValuesChange status to Healthy.Change status to Healthy.
No
No steps
Decisions & LoopsIf score is less than or equal to 3If score is less than or equal to 3
Yes
ValuesChange status to At risk.Change status to At risk.
No
No steps
ValuesSet previousScore to blank.Set previousScore to blank.
ValuesSet scoredBefore to 0.Set scoredBefore to 0.
ValuesSet clientLabel to client.Set clientLabel to client.
Decisions & LoopsIf existingRow isn't emptyIf existingRow isn't empty
Yes
TextExtract the first number from existingRow["Health score"] and save it as previousScore.Extract the first number from existingRow["Health score"] and save it as previousScore.
TextExtract the first number from existingRow["Emails scored"] and save it as countBefore.Extract the first number from existingRow["Emails scored"] and save it as countBefore.
Decisions & LoopsIf countBefore isn't blankIf countBefore isn't blank
Yes
ValuesChange scoredBefore to countBefore.Change scoredBefore to countBefore.
No
No steps
Decisions & LoopsIf existingRow.Client is yesIf existingRow.Client is yes
Yes
TextTrim existingRow.Client and save it as clientLabel.Trim existingRow.Client and save it as clientLabel.
No
No steps
No
No steps
ValuesSet trend to New.Set trend to New.
Decisions & LoopsIf previousScore isn't blankIf previousScore isn't blank
Yes
ValuesChange trend to Steady.Change trend to Steady.
Decisions & LoopsIf score is greater than previousScoreIf score is greater than previousScore
Yes
ValuesChange trend to Improving.Change trend to Improving.
No
No steps
Decisions & LoopsIf score is less than previousScoreIf score is less than previousScore
Yes
ValuesChange trend to Declining.Change trend to Declining.
No
No steps
No
No steps
ValuesSet lastEmail to emailDate.Set lastEmail to emailDate.
Decisions & LoopsIf lastEmail is emptyIf lastEmail is empty
Yes
Dates & TimesGet today's date and save it as lastEmail.Get today's date and save it as lastEmail.
No
No steps
ValuesSet rowValues to [clientLabel, score, status, trend, summary, why, lastEmail, scoredBefore plus 1, the current date and time].Set rowValues to [clientLabel, score, status, trend, summary, why, lastEmail, scoredBefore plus 1, the current date and time].
Decisions & LoopsIf existingRow isn't emptyIf existingRow isn't empty
Yes
Google SheetsGoogle SheetsOverwrite row existingRow of healthTabName in spreadsheetUrl.Overwrite row existingRow of healthTabName in spreadsheetUrl.
No
Google SheetsGoogle SheetsAdd a row to healthTabName in spreadsheetUrl.Add a row to healthTabName in spreadsheetUrl.
ListsAdd emailKey to the end of remembered and save it as remembered.Add emailKey to the end of remembered and save it as remembered.
Decisions & LoopsIf the length of remembered is greater than maxRememberedEmailsIf the length of remembered is greater than maxRememberedEmails
Yes
ListsTake remembered from position the length of remembered minus maxRememberedEmails on and save it as remembered.Take remembered from position the length of remembered minus maxRememberedEmails on and save it as remembered.
No
No steps
Stored ValuesSet the stored value 'client_health_scored' to remembered.Set the stored value 'client_health_scored' to remembered.
ValuesAdd 1 to scoredThisRun.Add 1 to scoredThisRun.
Decisions & LoopsIf clientsScored doesn't contain clientKeyIf clientsScored doesn't contain clientKey
Yes
ListsAdd clientKey to the end of clientsScored and save it as clientsScored.Add clientKey to the end of clientsScored and save it as clientsScored.
No
No steps
On error
ValuesShow in the output: Could not score rowLabel: emailError.message. It will be tried again on the next run.Show in the output: Could not score rowLabel: emailError.message. It will be tried again on the next run.
ValuesSet atRiskClients to an empty list.Set atRiskClients to an empty list.
ValuesSet dashboardNote to blank.Set dashboardNote to blank.
Decisions & LoopsIf a step below fails, save the error as summaryReadErrorIf a step below fails, save the error as summaryReadError
Try
Google SheetsGoogle SheetsRead the rows of spreadsheetUrl. Save it as dashboardNow.Read the rows of spreadsheetUrl. Save it as dashboardNow.
Decisions & LoopsFor each dashboardRow in dashboardNow.rows:For each dashboardRow in dashboardNow.rows:
Decisions & Loops If dashboardRow.Status is At risk and dashboardRow.Client is yes If dashboardRow.Status is At risk and dashboardRow.Client is yes
Yes
ListsAdd dashboardRow.Client to the end of atRiskClients and save it as atRiskClients.Add dashboardRow.Client to the end of atRiskClients and save it as atRiskClients.
No
No steps
Decisions & LoopsIf atRiskClients isn't emptyIf atRiskClients isn't empty
Yes
ValuesChange dashboardNote to At risk: atRiskClients joined by , .Change dashboardNote to At risk: atRiskClients joined by , .
No
ValuesChange dashboardNote to No client is marked At risk..Change dashboardNote to No client is marked At risk..
On error
ValuesChange dashboardNote to The dashboard could not be read back for this summary: summaryReadError.message.Change dashboardNote to The dashboard could not be read back for this summary: summaryReadError.message.
ValuesSet notesText to blank.Set notesText to blank.
Decisions & LoopsIf runNotes isn't emptyIf runNotes isn't empty
Yes
ValuesChange notesText to runNotes joined by a space.Change notesText to runNotes joined by a space.
No
No steps
MathSet attempted to newEmails minus surplus.Set attempted to newEmails minus surplus.
Decisions & LoopsIf scoredThisRun is 0If scoredThisRun is 0
Yes
Decisions & LoopsIf newEmails is 0If newEmails is 0
Yes
Decisions & LoopsFinish the run: No new emails to score in '@emailsTabName' since the last run; the dashboard in '@healthTabName' is unchanged.@dashboardNote@notesText.Finish the run: No new emails to score in '@emailsTabName' since the last run; the dashboard in '@healthTabName' is unchanged.@dashboardNote@notesText.
No
Decisions & LoopsStop and report a problem: Tried to score attempted new email(s) in '@emailsTabName' and none succeeded; see the messages above. The dashboard in '@healthTabName' is unchanged.@dashboardNote@notesText.Stop and report a problem: Tried to score attempted new email(s) in '@emailsTabName' and none succeeded; see the messages above. The dashboard in '@healthTabName' is unchanged.@dashboardNote@notesText.
No
No steps
Decisions & LoopsFinish the run: Scored scoredThisRun email(s) for the length of clientsScored client(s) in '@healthTabName'.@dashboardNote@notesText.Finish the run: Scored scoredThisRun email(s) for the length of clientsScored client(s) in '@healthTabName'.@dashboardNote@notesText.