Reads the subreddits your customers use, groups the complaints that keep coming back, and emails the ones with real evidence.
Uses
Gmail
Google Sheets
OpenAIReddit
Find the problems your market keeps raising
Every Monday at 08:00, the newest posts in the subreddits you name are read, the AI groups the frustrations and unmet needs that keep coming back into themes, and the ones with real evidence behind them are emailed to you. It looks for problems nobody has solved yet, not for mentions of you, so themes are reported whether or not your product is named.
Each theme comes with how many posts sit behind it, what those people want, and the title and link of every post behind it. Every post the AI cites is checked against the posts actually read, and any it did not get from that list is discarded, so nothing is invented. A theme with too few posts is left out rather than padded, and a week where nothing recurs is reported as exactly that. Each reported theme is also appended to your Google Sheet as a history that is never overwritten.
Before the first run you need a sheet with a tab headed 'Date', 'Theme', 'Posts behind it', 'What they want', 'Subreddits read' and 'Example links'.
subreddits to blank.Set subreddits to blank.marketDescription to blank.Set marketDescription to blank.digestRecipient to blank.Set digestRecipient to blank.themeLogUrl to blank.Set themeLogUrl to blank.themeLogTab to blank.Set themeLogTab to blank.maxSubreddits to 5.Set maxSubreddits to 5.postsPerSubreddit to 50.Set postsPerSubreddit to 50.minPostsPerTheme to 3.Set minPostsPerTheme to 3.maxThemesReported to 10.Set maxThemesReported to 10.subredditCap to maxSubreddits multiplied by 1.Set subredditCap to maxSubreddits multiplied by 1.postCap to postsPerSubreddit multiplied by 1.Set postCap to postsPerSubreddit multiplied by 1.themeMinimum to minPostsPerTheme multiplied by 1.Set themeMinimum to minPostsPerTheme multiplied by 1.themeCap to maxThemesReported multiplied by 1.Set themeCap to maxThemesReported multiplied by 1.postCap is less than 1 or postCap is greater than 100If postCap is less than 1 or postCap is greater than 100subredditCap is less than 1If subredditCap is less than 1themeMinimum is less than 2If themeMinimum is less than 2runDate.Get today's date and save it as runDate.postRefs.Start an empty list and save it as postRefs.postLabels.Start an empty list and save it as postLabels.postLinks.Start an empty list and save it as postLinks.catalogueLines.Start an empty list and save it as catalogueLines.subredditsRead.Start an empty list and save it as subredditsRead.postsRead to 0.Set postsRead to 0.subredditsTried to 0.Set subredditsTried to 0.postsSkipped to 0.Set postsSkipped to 0.subredditEntry in subreddits split by ,:For each subredditEntry in subreddits split by ,:subredditsTried is greater than or equal to subredditCapIf subredditsTried is greater than or equal to subredditCapsubredditEntry and save it as subredditName.Trim subredditEntry and save it as subredditName.subredditName isn't emptyIf subredditName isn't emptysubredditsTried.Add 1 to subredditsTried.subredditPrefix to r/subredditName - .Set subredditPrefix to r/subredditName - .subredditTag to [r/subredditName] .Set subredditTag to [r/subredditName] .listing to nothing.Set listing to nothing.listingRead to false.Set listingRead to false.readErrorIf a step below fails, save the error as readErrorsubredditName. Save it as listing.List posts in r/subredditName. Save it as listing.listingRead to true.Change listingRead to true.subredditName: readError as text.Show in the output: Could not read r/subredditName: readError as text.listingRead is yesIf listingRead is yessubredditName to the end of subredditsRead and save it as subredditsRead.Add subredditName to the end of subredditsRead and save it as subredditsRead.post in listing.posts:For each post in listing.posts:postError If a step below fails, save the error as postErrorpost.title and save it as postTitle.Trim post.title and save it as postTitle.post.permalink and save it as postPath.Trim post.permalink and save it as postPath.postTitle isn't empty and postPath starts with /r/If postTitle isn't empty and postPath starts with /r/postsRead.Add 1 to postsRead.postRef to P@postsRead.Set postRef to P@postsRead.postRef to the end of postRefs and save it as postRefs.Add postRef to the end of postRefs and save it as postRefs.subredditPrefix plus postTitle to the end of postLabels and save it as postLabels.Add subredditPrefix plus postTitle to the end of postLabels and save it as postLabels.postLinks and save it as postLinks.Add https://www.reddit.com@postPath to the end of postLinks and save it as postLinks.postRef plus subredditTag plus postTitle to the end of catalogueLines and save it as catalogueLines.Add postRef plus subredditTag plus postTitle to the end of catalogueLines and save it as catalogueLines.postsSkipped.Add 1 to postsSkipped.postsSkipped.Add 1 to postsSkipped.subredditName: postError as text.Show in the output: Skipped a post with no readable title or link in subredditName: postError as text.postRefs is emptyIf postRefs is emptycatalogueLines with a new line and save it as catalogue.Join catalogueLines with a new line and save it as catalogue.promptOpening to:Set promptOpening to:You are helping a team work out which problems their market keeps running into. Below are recent posts from the communities where their customers spend time. Each post is listed with a label.
The team describes their market like this:promptFormat to:Set promptFormat to:Group the posts that describe a frustration, a complaint or a need nobody has met into themes. Reply with one line per theme and nothing else, in exactly this format:
THEME: <short name> | REFS: <labels, comma separated> | NEED: <one sentence on what these people want and cannot get today>
Rules:
- Every label in REFS must be copied exactly from the list below. Never invent a label, and never cite a post that is not in the list.
- Put a post in a theme only when its own title describes that problem. Do not stretch a post to make a theme bigger.
- Only report a theme that at leastpromptClosing to:Set promptClosing to:different posts support.
- Ignore posts that are not complaints, frustrations or unmet needs: announcements, jokes, self-promotion and questions that were already answered.
- If nothing recurs across enough posts, reply with the single word NONE.
Posts:themePrompt to promptOpening plus marketDescription plus promptFormat plus themeMinimum plus promptClosing plus catalogue.Set themePrompt to promptOpening plus marketDescription plus promptFormat plus themeMinimum plus promptClosing plus catalogue.modelReply to nothing.Set modelReply to nothing.modelAnswered to false.Set modelAnswered to false.modelErrorIf a step below fails, save the error as modelErrorthemePrompt. Save it as modelReply.Ask OpenAI: themePrompt. Save it as modelReply.modelAnswered to true.Change modelAnswered to true.modelError as text.Show in the output: Could not group the posts into themes: modelError as text.modelAnswered is noIf modelAnswered is nothemeAnswer to blank.Set themeAnswer to blank.answerErrorIf a step below fails, save the error as answerErrormodelReply.response_text and save it as themeAnswer.Trim modelReply.response_text and save it as themeAnswer.The model returned no text to read themes out of..Show in the output: The model returned no text to read themes out of..themeAnswer is emptyIf themeAnswer is emptythemeSections.Start an empty list and save it as themeSections.themesReported to 0.Set themesReported to 0.themesDropped to 0.Set themesDropped to 0.unmatchedLabels to 0.Set unmatchedLabels to 0.rawThemeLine in themeAnswer split by a new line:For each rawThemeLine in themeAnswer split by a new line:themesReported is greater than or equal to themeCapIf themesReported is greater than or equal to themeCaprawThemeLine and save it as themeLine.Trim rawThemeLine and save it as themeLine.themeLine by | and save it as themeParts.Split themeLine by | and save it as themeParts.themeLine contains THEME: and themeLine contains REFS: and themeLine…If themeLine contains THEME: and themeLine contains REFS: and themeLine…themeParts[0] with THEME: replaced by and save it as themeName.Trim themeParts[0] with THEME: replaced by and save it as themeName.REFS: with in themeParts[1] and save it as themeRefField.Replace REFS: with in themeParts[1] and save it as themeRefField.themeParts[2] with NEED: replaced by and save it as themeNeed.Trim themeParts[2] with NEED: replaced by and save it as themeNeed.evidenceLines.Start an empty list and save it as evidenceLines.evidenceUrls.Start an empty list and save it as evidenceUrls.seenRefs.Start an empty list and save it as seenRefs.refToken in themeRefField split by ,:For each refToken in themeRefField split by ,:refToken and save it as citedRef. Trim refToken and save it as citedRef.citedRef isn't empty and seenRefs doesn't contain citedRef If citedRef isn't empty and seenRefs doesn't contain citedRefcitedRef to the end of seenRefs and save it as seenRefs.Add citedRef to the end of seenRefs and save it as seenRefs.citedRef in postRefs and save it as postPosition.Find the position of citedRef in postRefs and save it as postPosition.postPosition is greater than or equal to 0If postPosition is greater than or equal to 0postLabels[@postPosition]Add postLabels[@postPosition]
postLinks[@postPosition] to the end of evidenceLines and save it as evidenceLines.postLinks[@postPosition] to the end of evidenceUrls and save it as evidenceUrls.Add postLinks[@postPosition] to the end of evidenceUrls and save it as evidenceUrls.unmatchedLabels.Add 1 to unmatchedLabels.evidenceLines and save it as evidenceCount.Count the items in evidenceLines and save it as evidenceCount.evidenceCount is greater than or equal to themeMinimum and themeName isn't…If evidenceCount is greater than or equal to themeMinimum and themeName isn't…themesReported.Add 1 to themesReported.themeHeadline to themesReported. themeName.Set themeHeadline to themesReported. themeName.themeCountLine to Posts behind it: evidenceCount of the postsRead read this run.Set themeCountLine to Posts behind it: evidenceCount of the postsRead read this run.themeNeedLine to What they want: themeNeed.Set themeNeedLine to What they want: themeNeed.evidenceLines with a new line and save it as themeEvidence.Join evidenceLines with a new line and save it as themeEvidence.themeSection to themeHeadlineSet themeSection to themeHeadline
themeCountLine
themeNeedLine
themeEvidence.themeSection to the end of themeSections and save it as themeSections.Add themeSection to the end of themeSections and save it as themeSections.logErrorIf a step below fails, save the error as logErrorthemeLogTab in themeLogUrl.Add a row to themeLogTab in themeLogUrl.logError as text.Show in the output: Could not log the theme to your sheet: logError as text.themesDropped.Add 1 to themesDropped.evidenceCount of the posts it named were posts actually read this run.Show in the output: Left out '@themeName': only evidenceCount of the posts it named were posts actually read this run.scopeLine to Read up to postCap of the newest posts in each of the length of subredditsRead subreddit(s): subredditsRead joined by , .Set scopeLine to Read up to postCap of the newest posts in each of the length of subredditsRead subreddit(s): subredditsRead joined by , .capsLine to Caps for one run: at most subredditCap subreddits, up to postCap posts from each, and at most themeCap themes reported.Set capsLine to Caps for one run: at most subredditCap subreddits, up to postCap posts from each, and at most themeCap themes reported.evidenceNote to Every post named below was read this run and the link goes straight to the thread. A theme needs at least themeMinimum of them behind it to be reported.Set evidenceNote to Every post named below was read this run and the link goes straight to the thread. A theme needs at least themeMinimum of them behind it to be reported.countsLine to Themes reported: themesReported. Themes left out for too little evidence: themesDropped. Posts skipped for a missing title or link: postsSkipped.Set countsLine to Themes reported: themesReported. Themes left out for too little evidence: themesDropped. Posts skipped for a missing title or link: postsSkipped.integrityLine to blank.Set integrityLine to blank.unmatchedLabels is greater than 0If unmatchedLabels is greater than 0integrityLine to Change integrityLine to
unmatchedLabels cited post label(s) did not match anything that was read, so they were discarded rather than reported.themeBody to blank.Set themeBody to blank.themesReported is greater than 0If themesReported is greater than 0themeSections with a blank line and save it as themeBody.Join themeSections with a blank line and save it as themeBody.themeBody to Nothing came up often enough this week. No theme reached themeMinimum posts, so there is nothing to report - which is the honest answer rather than a thin one.Change themeBody to Nothing came up often enough this week. No theme reached themeMinimum posts, so there is nothing to report - which is the honest answer rather than a thin one.digestOpening to What your market kept complaining aboutSet digestOpening to What your market kept complaining about
scopeLine
capsLine
evidenceNote
.digestClosing to Set digestClosing to
countsLine@integrityLine.digestBody to digestOpening plus themeBody plus digestClosing.Set digestBody to digestOpening plus themeBody plus digestClosing.digestSubject to Recurring complaints in your market - themesReported theme(s) - runDate.Set digestSubject to Recurring complaints in your market - themesReported theme(s) - runDate.digestRecipient.Send an email to digestRecipient.
Subject: digestSubjectdigestBody