Every Monday, checks the on-page SEO of a page you name and emails its score and what to fix, most important first.
Uses
Gmail
Google PageSpeed Insights
OpenAI
Get a weekly SEO checkup of your key page by email
Every Monday morning, one page you name is put through Google's own on-page SEO checks as a mobile or desktop visitor: a title, a meta description, a crawlable page, descriptive link text, alt text on images, a readable font size and a viewport. The page itself is also read to count its H1, H2 and H3 headings, which no automated score covers. You get one email with the score and the number of fixes in the subject, the checks the page failed and why each matters, its heading structure, a list of what to fix first, and a link to the full report.
AI writes the list of fixes from those findings alone: it is told not to add checks that were not run, not to invent numbers, and not to talk about rankings, traffic or competitors. If the page cannot be scored at all, the run stops and nothing is emailed. If the site turns the check away, or no headings show up in the page source (some sites add them with JavaScript), the email says the headings could not be judged instead of telling you to add them.
You need a Google PageSpeed Insights connection (a free key from Google), an OpenAI connection and a Gmail connection. When you add it, you are asked for the page's address and the email address the checkup goes to.
pageUrl to blank.Set pageUrl to blank.reportRecipient to blank.Set reportRecipient to blank.deviceStrategy to mobile.Set deviceStrategy to mobile.maxFindings to 20.Set maxFindings to 20.pageUrl and save it as auditUrl.Trim pageUrl and save it as auditUrl.reportRecipient and save it as sendTo.Trim reportRecipient and save it as sendTo.deviceStrategy lowercase and save it as device.Make deviceStrategy lowercase and save it as device.auditUrl is a valid web addressIf not auditUrl is a valid web addresssendTo is a valid email addressIf not sendTo is a valid email address mobile desktop doesn't contain deviceIf mobile desktop doesn't contain deviceauditUrl with PageSpeed Insights. Save it as report.Analyze auditUrl with PageSpeed Insights. Save it as report.report.scores.seo and save it as seoScore.Trim report.scores.seo and save it as seoScore.seoScore is null or seoScore is emptyIf seoScore is null or seoScore is emptyauditedAddress to auditUrl.Set auditedAddress to auditUrl.report.url and save it as finalUrl.Trim report.url and save it as finalUrl.finalUrl isn't empty and finalUrl is not nullIf finalUrl isn't empty and finalUrl is not nullauditedAddress to finalUrl.Change auditedAddress to finalUrl.report.report_url and save it as reportLink.Trim report.report_url and save it as reportLink.reportLink is null or reportLink is emptyIf reportLink is null or reportLink is emptyreportLink to not available for this run.Change reportLink to not available for this run.report.analyzed_at and save it as checkedOn.Trim report.analyzed_at and save it as checkedOn.checkedOn is null or checkedOn is emptyIf checkedOn is null or checkedOn is emptycheckedOn to not reported.Change checkedOn to not reported.checkedOn readable as “Monday, November 2, 2026” and save it as checkedOn.Make checkedOn readable as “Monday, November 2, 2026” and save it as checkedOn.findings to an empty list.Set findings to an empty list.findingCount to 0.Set findingCount to 0.audit in report.failed_audits:For each audit in report.failed_audits:audit.category and save it as auditCategory.Trim audit.category and save it as auditCategory.auditCategory is not seoIf auditCategory is not seofindingCount is greater than or equal to maxFindings multiplied by 1If findingCount is greater than or equal to maxFindings multiplied by 1` with in audit.title and save it as auditTitle.Replace ` with in audit.title and save it as auditTitle.auditTitle is empty or auditTitle is nullIf auditTitle is empty or auditTitle is nullfindingLine to - auditTitle.Set findingLine to - auditTitle.audit.display_value and save it as auditDetail.Trim audit.display_value and save it as auditDetail.auditDetail isn't empty and auditDetail is not nullIf auditDetail isn't empty and auditDetail is not nullfindingLine to findingLine (auditDetail).Change findingLine to findingLine (auditDetail).\[([^\]]+)\]\(([^)\s]+)\) with $1 ($2) in audit.description and save it as auditWhy.Replace the pattern \[([^\]]+)\]\(([^)\s]+)\) with $1 ($2) in audit.description and save it as auditWhy.` with in auditWhy and save it as auditWhy.Replace ` with in auditWhy and save it as auditWhy.auditWhy isn't empty and auditWhy is not nullIf auditWhy isn't empty and auditWhy is not nullfindingLine to findingLineChange findingLine to findingLine
Why it matters: auditWhy.findingLine to the end of findings and save it as findings.Add findingLine to the end of findings and save it as findings.findingCount.Add 1 to findingCount.failedText to None - every on-page check in this audit passed..Set failedText to None - every on-page check in this audit passed..findingCount is greater than 0If findingCount is greater than 0findings with a new line and save it as failedText.Join findings with a new line and save it as failedText.pageHtml to blank.Set pageHtml to blank.structureText to The page source could not be read from this workflow, so its heading structure was not checked..Set structureText to The page source could not be read from this workflow, so its heading structure was not checked..headingFixes to 0.Set headingFixes to 0.fetchErrorIf a step below fails, save the error as fetchErrorauditUrl and save it as pageFetch.Fetch auditUrl and save it as pageFetch.pageFetch.ok is yesIf pageFetch.ok is yespageHtml to pageFetch.body.Change pageHtml to pageFetch.body.The site answered this workflow with status @pageFetch.status instead of the page (some sites turn away automated visitors), so its heading structure was not c... and save it as structureText.Build the text The site answered this workflow with status @pageFetch.status instead of the page (some sites turn away automated visitors), so its heading structure was not c... and save it as structureText.The page could not be downloaded by this workflow (@fetchError.message), so its heading structure was not checked. and save it as structureText.Build the text The page could not be downloaded by this workflow (@fetchError.message), so its heading structure was not checked. and save it as structureText.pageHtml is greater than or equal to 400If the length of pageHtml is greater than or equal to 400pageHtml lowercase and save it as htmlLower.Make pageHtml lowercase and save it as htmlLower.h1Count to the length of htmlLower split by <h1 minus 1.Set h1Count to the length of htmlLower split by <h1 minus 1.h2Count to the length of htmlLower split by <h2 minus 1.Set h2Count to the length of htmlLower split by <h2 minus 1.h3Count to the length of htmlLower split by <h3 minus 1.Set h3Count to the length of htmlLower split by <h3 minus 1.h1Count plus h2Count plus h3Count is 0If h1Count plus h2Count plus h3Count is 0structureText to No H1, H2 or H3 headings were found in the page source. They may be added by JavaScript after the page loads, or the site may have shown this check a different page, so the heading structure could not be judged. Open the page and check its headings yourself..Change structureText to No H1, H2 or H3 headings were found in the page source. They may be added by JavaScript after the page loads, or the site may have shown this check a different page, so the heading structure could not be judged. Open the page and check its headings yourself..structureLines to an empty list.Set structureLines to an empty list.h1Count as text, H2 h2Count as text, H3 h3Count as text to the end of structureLines and save it as structureLines.Add - Headings found: H1 h1Count as text, H2 h2Count as text, H3 h3Count as text to the end of structureLines and save it as structureLines.h1Count is 0If h1Count is 0- There is no H1 on the page. Give it one heading that states what the page is about. to the end of structureLines and save it as structureLines.Add - There is no H1 on the page. Give it one heading that states what the page is about. to the end of structureLines and save it as structureLines.headingFixes.Add 1 to headingFixes.h1Count is greater than 1If h1Count is greater than 1- The page has more than one H1. Keep one, and demote the rest to H2. to the end of structureLines and save it as structureLines.Add - The page has more than one H1. Keep one, and demote the rest to H2. to the end of structureLines and save it as structureLines.headingFixes.Add 1 to headingFixes.h2Count is 0If h2Count is 0- There are no H2 subheadings, so the page has no visible structure under its title. Break the content into sections. to the end of structureLines and save it as structureLines.Add - There are no H2 subheadings, so the page has no visible structure under its title. Break the content into sections. to the end of structureLines and save it as structureLines.headingFixes.Add 1 to headingFixes.structureLines with a new line and save it as structureText.Join structureLines with a new line and save it as structureText.auditPrompt to:Set auditPrompt to:You are an SEO specialist writing to the owner of one web page.
Below is everything an automated on-page audit of that page found. Write a short, prioritised list of what to fix, most important first, in plain sentences a non-specialist can act on. Say what to change and where.
Rules you must follow:
- Use ONLY the findings below. Do not add checks that are not listed.
- Do not mention search rankings, traffic, competitors or keywords - none of that was measured here.
- Do not invent any number, page title or wording that does not appear below.
- If the heading structure says it was not che...auditPrompt to auditPromptChange auditPrompt to auditPrompt
Page audited: auditedAddress
On-page SEO score out of 100: seoScore.auditPrompt to auditPromptChange auditPrompt to auditPrompt
Failed on-page checks:
failedText.auditPrompt to auditPromptChange auditPrompt to auditPrompt
Heading structure:
structureText.advice to blank.Set advice to blank.adviceErrorIf a step below fails, save the error as adviceErrorauditPrompt. Save it as answer.Ask OpenAI: auditPrompt. Save it as answer.answer.response_text and save it as answerText.Trim answer.response_text and save it as answerText.answerText isn't empty and answerText is not nullIf answerText isn't empty and answerText is not nulladvice to answerText.Change advice to answerText.adviceError as text.Show in the output: Could not write the recommendations for this audit: adviceError as text.advice is emptyIf advice is emptyadvice to No written recommendations were produced this run - the checks listed above are the audit..Change advice to No written recommendations were produced this run - the checks listed above are the audit..bodyLines to an empty list.Set bodyLines to an empty list.auditedAddress to the end of bodyLines and save it as bodyLines.Add On-page SEO audit for auditedAddress to the end of bodyLines and save it as bodyLines.checkedOn as a device visitor. to the end of bodyLines and save it as bodyLines.Add Checked on checkedOn as a device visitor. to the end of bodyLines and save it as bodyLines.seoScore out of 100 to the end of bodyLines and save it as bodyLines.failedText to the end of bodyLines and save it as bodyLines.structureText to the end of bodyLines and save it as bodyLines.advice to the end of bodyLines and save it as bodyLines.reportLink to the end of bodyLines and save it as bodyLines.bodyLines with a new line and save it as emailBody.Join bodyLines with a new line and save it as emailBody.fixCount to findingCount plus headingFixes.Set fixCount to findingCount plus headingFixes.fixSummary to fixCount fixes.Set fixSummary to fixCount fixes.fixCount is 0If fixCount is 0fixSummary to nothing to fix.Change fixSummary to nothing to fix.fixSummary to 1 fix.Change fixSummary to 1 fix.^https?://(www\.)?|/$ with in auditedAddress and save it as pageLabel.Replace the pattern ^https?://(www\.)?|/$ with in auditedAddress and save it as pageLabel.sendTo.Send an email to sendTo.
Subject: SEO seoScore/100 · fixSummary for pageLabelemailBody