Each day, picks an article for every lead due one, by their interests and stage, writes a personal note around it and emails it.

UsesClaudeGmailGoogle Sheets

Nurture leads with a personalised article

Every morning, your leads tab and your articles tab are read from one spreadsheet. For each lead who is due a touch, one article is chosen for them: one whose topics overlap their interests and whose stage matches theirs is preferred, and the pick within that tier is random so similar leads do not all get the same piece. The AI then writes a short note explaining why it is worth their time, from the facts on those two rows and nothing else, and it goes out from your Gmail with the article title as the subject and your own sign-off and opt-out line added.

Nobody hears from you too often and nobody gets the same article twice. Every send is written to a record tab before the email leaves, and that tab is read back at the start of each run, so a rerun cannot repeat a send and a lead is left alone until the gap you set has passed. A lead without a valid address or a name is reported and skipped, and the leads and articles tabs are never written to.

Before the first run you need a leads tab with Email, Name, Interests and Stage columns, an articles tab with Title, URL, Summary, Topics and Stage, an empty record tab headed Email, Article, URL and Sent at, and a short description of who the emails come from.

ScheduleRun every day at 09:00.Run every day at 09:00.
ValuesSet spreadsheetUrl to blank.Set spreadsheetUrl to blank.
ValuesSet leadsTabName to blank.Set leadsTabName to blank.
ValuesSet articlesTabName to blank.Set articlesTabName to blank.
ValuesSet sentTabName to blank.Set sentTabName to blank.
ValuesSet companyContext to blank.Set companyContext to blank.
ValuesSet signOff to blank.Set signOff to blank.
ValuesSet optOutLine to If you would rather not get these, just reply and say so..Set optOutLine to If you would rather not get these, just reply and say so..
ValuesSet daysBetweenEmails to 7.Set daysBetweenEmails to 7.
ValuesSet maxEmailsPerRun to 20.Set maxEmailsPerRun to 20.
ValuesSet secondsBetweenEmails to 5.Set secondsBetweenEmails to 5.
Google SheetsGoogle SheetsRead the rows of spreadsheetUrl. Save it as leadsRead.Read the rows of spreadsheetUrl. Save it as leadsRead.
Google SheetsGoogle SheetsRead the rows of spreadsheetUrl. Save it as articlesRead.Read the rows of spreadsheetUrl. Save it as articlesRead.
Google SheetsGoogle SheetsRead the rows of spreadsheetUrl. Save it as sentRead.Read the rows of spreadsheetUrl. Save it as sentRead.
Decisions & LoopsIf articlesRead.count is 0If articlesRead.count is 0
Yes
Decisions & LoopsStop and report a problem: The articles tab is empty. Add at least one row with a Title, a URL, a Summary, and optionally Topics and Stage, then run again.Stop and report a problem: The articles tab is empty. Add at least one row with a Title, a URL, a Summary, and optionally Topics and Stage, then run again.
No
No steps
Decisions & LoopsIf leadsRead.count is 0If leadsRead.count is 0
Yes
Decisions & LoopsFinish the run: The leads tab has no rows under its header, so there is nobody to email today.Finish the run: The leads tab has no rows under its header, so there is nobody to email today.
No
No steps
CodeRun Python code on leads, articles, sent, days_between, and now_text and save it as plan.Run Python code on leads, articles, sent, days_between, and now_text and save it as plan.
Decisions & LoopsFor each note in plan.notes:For each note in plan.notes:
ValuesShow in the output: note.Show in the output: note.
Decisions & LoopsIf plan.articles is 0If plan.articles is 0
Yes
Decisions & LoopsStop and report a problem: None of the rows on the articles tab could be used - each needs a Title and a URL starting with http. Nothing was sent.Stop and report a problem: None of the rows on the articles tab could be used - each needs a Title and a URL starting with http. Nothing was sent.
No
No steps
Decisions & LoopsIf plan.picks is emptyIf plan.picks is empty
Yes
Decisions & LoopsFinish the run: No lead is due an article today. Nothing was sent.Finish the run: No lead is due an article today. Nothing was sent.
No
No steps
ValuesSet sentCount to 0.Set sentCount to 0.
ValuesSet skippedCount to 0.Set skippedCount to 0.
Decisions & LoopsFor each pick in plan.picks:For each pick in plan.picks:
Decisions & LoopsIf sentCount is greater than or equal to maxEmailsPerRunIf sentCount is greater than or equal to maxEmailsPerRun
Yes
Decisions & LoopsStop repeating.Stop repeating.
No
No steps
ValuesSet leadEmail to pick.email.Set leadEmail to pick.email.
ValuesSet articleTitle to pick.title.Set articleTitle to pick.title.
ValuesSet articleUrl to pick.url.Set articleUrl to pick.url.
ValuesSet emailBody to blank.Set emailBody to blank.
Decisions & LoopsIf a step below fails, save the error as writeErrorIf a step below fails, save the error as writeError
Try
ClaudeClaudeAsk Claude: 'You are writing a short, friendly email on behalf of a person to a sales lead, sharing one article that person thinks the lead will find useful. This is a helpful touchpoint, not a pitch.Ask Claude: 'You are writing a short, friendly email on behalf of a person to a sales lead, sharing one article that person thinks the lead will find useful. This is a helpful touchpoint, not a pitch. Use ONLY the facts below. Do not invent anything about the lead, their company, or the article beyond what is written here. Never imply you have spoken or met before - no 'as we discussed', no 'following up on our call'. Do not add pricing, offers or deadlines. About the sender and what they do: companyContext The lead: Name: pick.name What they are interested in: pick.interests Where they are in the funnel: pick.stage The article to share: Title: pick.title Link: pick.url Summary: pick.summary It was chosen because it pick.why. Write the email body only, as plain text with no subject line, no markdown and no sign-off (the sender's own sign-off is added afterwards). Start with 'Hi ' followed by the lead's first name and a comma. Then two or three short paragraphs: why this article is worth their time given what they care about and where they are, what it covers in a sentence or two drawn only from the summary, and the link on its own line exactly as written above. Close with one light sentence offering to talk it through if useful, with no pressure. Keep the whole thing under 150 words.'. Save it as written. Maximum response length: 600
TextTrim written.response_text and save it as emailBody.Trim written.response_text and save it as emailBody.
On error
ValuesShow in the output: Could not write the note for leadEmail: writeError as text.Show in the output: Could not write the note for leadEmail: writeError as text.
Decisions & LoopsIf emailBody is emptyIf emailBody is empty
Yes
ValuesShow in the output: No note came back for leadEmail, so they are left for the next run.Show in the output: No note came back for leadEmail, so they are left for the next run.
ValuesAdd 1 to skippedCount.Add 1 to skippedCount.
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
Decisions & LoopsIf emailBody doesn't contain articleUrlIf emailBody doesn't contain articleUrl
Yes
ValuesChange emailBody to emailBodyChange emailBody to emailBody articleUrl.
No
No steps
ValuesChange emailBody to emailBodyChange emailBody to emailBody signOff optOutLine.
ValuesSet recorded to false.Set recorded to false.
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 sentTabName in spreadsheetUrl.Add a row to sentTabName in spreadsheetUrl.
ValuesChange recorded to true.Change recorded to true.
On error
ValuesShow in the output: Could not record the send for leadEmail on the '@sentTabName' tab, so nothing was sent to them: recordError as text.Show in the output: Could not record the send for leadEmail on the '@sentTabName' tab, so nothing was sent to them: recordError as text.
Decisions & LoopsIf recorded is noIf recorded is no
Yes
ValuesAdd 1 to skippedCount.Add 1 to skippedCount.
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
Decisions & LoopsIf sentCount is greater than 0If sentCount is greater than 0
Yes
Decisions & LoopsWait for secondsBetweenEmails seconds.Wait for secondsBetweenEmails seconds.
No
No steps
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 leadEmail.Send an email to leadEmail. Subject: articleTitleemailBody
ValuesAdd 1 to sentCount.Add 1 to sentCount.
ValuesShow in the output: Sent '@articleTitle' to leadEmail.Show in the output: Sent '@articleTitle' to leadEmail.
On error
ValuesShow in the output: Recorded but could not send '@articleTitle' to leadEmail: sendError as text - delete that row from the '@sentTabName' tab to try again next run.Show in the output: Recorded but could not send '@articleTitle' to leadEmail: sendError as text - delete that row from the '@sentTabName' tab to try again next run.
ValuesShow in the output: Sent sentCount as text article email(s), skipped skippedCount as text, out of the length of plan.picks as text lead(s) due today.Show in the output: Sent sentCount as text article email(s), skipped skippedCount as text, out of the length of plan.picks as text lead(s) due today.