Writes a personalised welcome email from each new client intake row, asks you to approve it, then sends it or drafts it.

UsesClaudeGmailGoogle Sheets

Welcome new clients from their intake form

Every hour, your client intake sheet is read and a personalised welcome email is written for each new client from what they told you on the form. Each email comes to you as an approval request with the address it would go to: approve it and it goes out under your name; reject it, or leave it, and it is saved as a Gmail draft for you to edit.

Nothing reaches a client without a person approving it, and nobody is welcomed twice, even when a new run starts while another is still waiting for your answer. A row missing a name, email address or notes is skipped rather than guessed at, and the intake sheet itself is only ever read. If an approved email cannot be sent, it is saved as a draft where possible and the run is marked as failed with the client's name and the reason; if the intake tab's columns are not named Name, Email and Notes, the run stops and tells you which columns it found.

You need Google Sheets, Gmail and Anthropic connected, an intake tab with 'Name', 'Email' and 'Notes' columns, a second tab headed 'Email', 'Name', 'Outcome' and 'Welcomed at', and a short description of your firm, what it does and how you like to sound.

ScheduleRun every hour.Run every hour.
ValuesSet intakeSheetUrl to blank.Set intakeSheetUrl to blank.
ValuesSet intakeTabName to blank.Set intakeTabName to blank.
ValuesSet welcomedTabName to blank.Set welcomedTabName to blank.
ValuesSet firmName to blank.Set firmName to blank.
ValuesSet firmContext to blank.Set firmContext to blank.
ValuesSet maxClientsPerRun to 10.Set maxClientsPerRun to 10.
Google SheetsGoogle SheetsRead column 'A' of welcomedTabName in intakeSheetUrl. Save it as welcomedRead.Read column 'A' of welcomedTabName in intakeSheetUrl. Save it as welcomedRead.
ValuesSet welcomedKeys to an empty list.Set welcomedKeys to an empty list.
Decisions & LoopsFor each loggedAddress in welcomedRead.values:For each loggedAddress in welcomedRead.values:
Decisions & LoopsIf a step below fails, save the error as loggedErrorIf a step below fails, save the error as loggedError
Try
ListsAdd loggedAddress in lowercase to the end of welcomedKeys and save it as welcomedKeys.Add loggedAddress in lowercase to the end of welcomedKeys and save it as welcomedKeys.
On error
ValuesShow in the output: Ignored an entry in the '@welcomedTabName' tab that could not be read as an email address.Show in the output: Ignored an entry in the '@welcomedTabName' tab that could not be read as an email address.
Google SheetsGoogle SheetsRead the rows of intakeSheetUrl. Save it as intake.Read the rows of intakeSheetUrl. Save it as intake.
Decisions & LoopsIf intake.headers is emptyIf intake.headers is empty
Yes
Decisions & LoopsStop and report a problem: The '@intakeTabName' tab is empty. Its first row must be a header row with columns named Name, Email and Notes.Stop and report a problem: The '@intakeTabName' tab is empty. Its first row must be a header row with columns named Name, Email and Notes.
Else if @intake.headers doesn't contain "Name" or @intake.h…
TextJoin intake.headers with , and save it as foundHeaders.Join intake.headers with , and save it as foundHeaders.
Decisions & LoopsStop and report a problem: The '@intakeTabName' tab needs columns named exactly Name, Email and Notes in its first row, but it has: foundHeaders. Rename those header cells (for a Google Form, rename the questions) to Name, Email and Notes, then run it again.Stop and report a problem: The '@intakeTabName' tab needs columns named exactly Name, Email and Notes in its first row, but it has: foundHeaders. Rename those header cells (for a Google Form, rename the questions) to Name, Email and Notes, then run it again.
Otherwise
No steps
ValuesSet welcomedThisRun to 0.Set welcomedThisRun to 0.
ValuesSet stoppedAtLimit to false.Set stoppedAtLimit to false.
ValuesSet failures to an empty list.Set failures to an empty list.
Decisions & LoopsFor each row in intake.rows:For each row in intake.rows:
Decisions & LoopsIf welcomedThisRun is greater than or equal to maxClientsPerRunIf welcomedThisRun is greater than or equal to maxClientsPerRun
Yes
ValuesChange stoppedAtLimit to true.Change stoppedAtLimit to true.
Decisions & LoopsStop repeating.Stop repeating.
No
No steps
ValuesSet rowLabel to row row.row_number as text.Set rowLabel to row row.row_number as text.
ValuesSet clientName to blank.Set clientName to blank.
ValuesSet clientEmail to blank.Set clientEmail to blank.
ValuesSet clientNotes to blank.Set clientNotes to blank.
ValuesSet rowIsComplete to false.Set rowIsComplete to false.
Decisions & LoopsIf a step below fails, save the error as missingColumnIf a step below fails, save the error as missingColumn
Try
TextTrim row.Name and save it as clientName.Trim row.Name and save it as clientName.
TextTrim row.Email and save it as clientEmail.Trim row.Email and save it as clientEmail.
TextTrim row.Notes and save it as clientNotes.Trim row.Notes and save it as clientNotes.
ValuesChange rowIsComplete to the length of clientName is greater than 0 and the length of clientEmail is greater than 0 and the length of clientNotes is greater than 0.Change rowIsComplete to the length of clientName is greater than 0 and the length of clientEmail is greater than 0 and the length of clientNotes is greater than 0.
On error
ValuesShow in the output: Skipping rowLabel - the intake tab needs a Name, an Email and a Notes column.Show in the output: Skipping rowLabel - the intake tab needs a Name, an Email and a Notes column.
Decisions & LoopsSkip to the next item.Skip to the next item.
Decisions & LoopsIf rowIsComplete is noIf rowIsComplete is no
Yes
ValuesShow in the output: Skipping rowLabel - Name, Email and Notes must all be filled in before a welcome email can be written.Show in the output: Skipping rowLabel - Name, Email and Notes must all be filled in before a welcome email can be written.
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
Decisions & LoopsIf not clientEmail is a valid email addressIf not clientEmail is a valid email address
Yes
ValuesShow in the output: Skipping rowLabel - '@clientEmail' is not a valid email address.Show in the output: Skipping rowLabel - '@clientEmail' is not a valid email address.
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
TextMake clientEmail lowercase and save it as emailKey.Make clientEmail lowercase and save it as emailKey.
Decisions & LoopsIf welcomedKeys contains emailKeyIf welcomedKeys contains emailKey
Yes
ValuesShow in the output: clientEmail has already been welcomed - leaving rowLabel alone.Show in the output: clientEmail has already been welcomed - leaving rowLabel alone.
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
Decisions & LoopsIf a step below fails, save the error as welcomeErrorIf a step below fails, save the error as welcomeError
Try
ValuesSet prompt to You are writing the body of a welcome email to a brand new client of a professional services firm. Write only the body of the email: no subject line, no placeholder brackets such as [Name], and nothing you cannot support from the details below. Greet the client by name, refer to what they asked for in their own terms, say what happens next, and sign off from the firm. Keep it under 200 words and do not invent dates, prices, or people.Set prompt to You are writing the body of a welcome email to a brand new client of a professional services firm. Write only the body of the email: no subject line, no placeholder brackets such as [Name], and nothing you cannot support from the details below. Greet the client by name, refer to what they asked for in their own terms, say what happens next, and sign off from the firm. Keep it under 200 words and do not invent dates, prices, or people. About the firm: firmName firmContext The new client: Name: clientName What they told us: clientNotes.
ClaudeClaudeAsk Claude: prompt. Save it as written.Ask Claude: prompt. Save it as written. Maximum response length: 700
TextTrim written.response_text and save it as welcomeBody.Trim written.response_text and save it as welcomeBody.
Decisions & LoopsIf welcomeBody isn't emptyIf welcomeBody isn't empty
Yes
ValuesSet subject to Welcome to firmName.Set subject to Welcome to firmName.
Google SheetsGoogle SheetsRead column 'A' of welcomedTabName in intakeSheetUrl. Save it as welcomedNow.Read column 'A' of welcomedTabName in intakeSheetUrl. Save it as welcomedNow.
Decisions & LoopsIf welcomedNow.values contains emailKeyIf welcomedNow.values contains emailKey
Yes
ValuesShow in the output: clientEmail is already waiting on another run's approval - leaving rowLabel to it.Show in the output: clientEmail is already waiting on another run's approval - leaving rowLabel to it.
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
Google SheetsGoogle SheetsAdd a row to welcomedTabName in intakeSheetUrl.Add a row to welcomedTabName in intakeSheetUrl.
ListsAdd emailKey to the end of welcomedKeys and save it as welcomedKeys.Add emailKey to the end of welcomedKeys and save it as welcomedKeys.
ValuesAdd 1 to welcomedThisRun.Add 1 to welcomedThisRun.
ApprovalsRequest approval: Send this welcome email to clientName <clientEmail>? Save it as review.Request approval: Send this welcome email to clientName <clientEmail>? Save it as review.Approve and it goes to the client now, under your name. Reject it, or leave it, and it is saved as a Gmail draft for you to edit. Subject: subject welcomeBody
ValuesSet outcome to blank.Set outcome to blank.
Decisions & LoopsIf review.approved is yesIf review.approved is yes
Yes
Decisions & LoopsIf a step below fails, save the error as sendErrorIf a step below fails, save the error as sendError
Try
GmailGmailSend an email to clientEmail.Send an email to clientEmail. Subject: subjectwelcomeBody
ValuesChange outcome to Sent.Change outcome to Sent.
On error
ValuesChange outcome to Send failed.Change outcome to Send failed.
Decisions & LoopsIf a step below fails, save the error as draftAfterSendErrorIf a step below fails, save the error as draftAfterSendError
Try
GmailGmailDraft an email to clientEmail.Draft an email to clientEmail. Subject: subjectwelcomeBody
ValuesChange outcome to Send failed, saved as a draft.Change outcome to Send failed, saved as a draft.
On error
No steps
ListsAdd clientName <clientEmail>: you approved the welcome email but it could not be sent (sendError.message). Recorded as '@outcome' on the '@welcomedTabName' tab. to the end of failures and save it as failures.Add clientName <clientEmail>: you approved the welcome email but it could not be sent (sendError.message). Recorded as '@outcome' on the '@welcomedTabName' tab. to the end of failures and save it as failures.
No
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 clientEmail.Draft an email to clientEmail. Subject: subjectwelcomeBody
ValuesChange outcome to Drafted.Change outcome to Drafted.
On error
ValuesChange outcome to Draft failed.Change outcome to Draft failed.
ListsAdd clientName <clientEmail>: the welcome email could not be saved as a draft (draftError.message). Recorded as 'Draft failed' on the '@welcomedTabName' tab. to the end of failures and save it as failures.Add clientName <clientEmail>: the welcome email could not be saved as a draft (draftError.message). Recorded as 'Draft failed' on the '@welcomedTabName' tab. to the end of failures and save it as failures.
Decisions & LoopsIf a step below fails, save the error as recordErrorIf a step below fails, save the error as recordError
Try
Google SheetsGoogle SheetsAdd a row to welcomedTabName in intakeSheetUrl.Add a row to welcomedTabName in intakeSheetUrl.
On error
ListsAdd clientName <clientEmail>: outcome, but that could not be written to the '@welcomedTabName' tab (recordError.message). to the end of failures and save it as failures.Add clientName <clientEmail>: outcome, but that could not be written to the '@welcomedTabName' tab (recordError.message). to the end of failures and save it as failures.
No
ValuesShow in the output: No welcome email was written for clientName, so rowLabel is left for the next run.Show in the output: No welcome email was written for clientName, so rowLabel is left for the next run.
On error
ListsAdd Could not welcome clientEmail: welcomeError.message. If they show as 'Awaiting approval' on the '@welcomedTabName' tab, delete that row to have them offered again. to the end of failures and save it as failures.Add Could not welcome clientEmail: welcomeError.message. If they show as 'Awaiting approval' on the '@welcomedTabName' tab, delete that row to have them offered again. to the end of failures and save it as failures.
ValuesSet limitNote to blank.Set limitNote to blank.
Decisions & LoopsIf stoppedAtLimit is yesIf stoppedAtLimit is yes
Yes
TextBuild the text This run asked about @maxClientsPerRun clients, the most 'maxClientsPerRun' allows; anyone else is offered on the next run. and save it as limitNote.Build the text This run asked about @maxClientsPerRun clients, the most 'maxClientsPerRun' allows; anyone else is offered on the next run. and save it as limitNote.
No
No steps
Decisions & LoopsIf failures isn't emptyIf failures isn't empty
Yes
TextJoin failures with a new line and save it as failureList.Join failures with a new line and save it as failureList.
Decisions & LoopsStop and report a problem: Some welcome emails did not go out as planned:Stop and report a problem: Some welcome emails did not go out as planned: failureList@limitNote.
No
No steps
ValuesShow in the output: Welcome emails handled this run: welcomedThisRun@limitNote.Show in the output: Welcome emails handled this run: welcomedThisRun@limitNote.