The testimonial engine
Students finish my course, land on a page I own, and leave a testimonial with explicit consent attached. Every morning I get one email with what came in. When I want one on my sales page, I say "add number two to my website" and it appears. This is that whole build.
Want this built for you instead of building it yourself?
Book a call with meWatch the walkthrough
I cover this build as Automation 4 at 13:04. The player above starts there automatically.
What you're building
A loop with five moving parts, four of which run without you.
- The ask. At the end of your course or program, students hit a page that asks for a testimonial while the result is still fresh.
- The page. It lives on your own site. Star rating, what they got out of it, a private feedback box, and a consent checkbox that is doing real work rather than sitting there for decoration.
- The store. Every submission lands as a row in a Google Sheet, marked PENDING. Nothing publishes itself.
- The digest. Each morning you get one email listing what came in. If nothing came in, you get nothing.
- The publish. You tell your agent which one you want live and where. It edits the site, shows you the change, deploys it, and stamps the sheet.
The reason this is worth building rather than doing by hand is not the time saved on any single testimonial. It is that testimonials stop being a thing you get around to. The five-star wall on my course page exists because collection became automatic and publishing became a sentence.
What you'll need
- A website you control the code of. This is the load-bearing requirement and I explain why below
- A free GitHub account (holds the code) and a free Vercel account (hosts it). If you already have a modern site, you almost certainly have both
- A Google account for the sheet and the photo storage
- Claude Code, which does the building. Around $100 a month
- Roughly a day of work spread over a week, most of it waiting for your own review rather than typing
Why not a Google Form
You can absolutely start with a Google Form. I did, and it worked for collecting.
Where it falls down is everything after collection. A form gives you a spreadsheet of raw answers with no photo handling, no consent that is structurally tied to the specific quote, no rating-dependent questions, and a submission experience that looks like a survey from your employer rather than a moment in your product. The quality of what people write drops when the page feels administrative.
The bigger reason is what happens on the publishing side. Once the testimonial is a row you control, in a shape you defined, an agent can move it onto your live site in one step. With a form you are still copying and pasting into a website builder, which means you do it once, feel good, and never do it again.
If you want the smallest possible version today: use a form, collect for a month, then build this once you have proof people will fill it in.
Step 1: Own your website first
This is the step people skip and then get stuck on, so it goes first.
My site used to live on Kajabi. Kajabi hosted the course, the pages, everything. It was fine for what it was and completely rigid for what I wanted. I could not tell an agent "put these testimonials from this sheet onto the sales page" and have anything happen, because there was no code for it to edit. Every change was me, in a page builder, dragging boxes. So I rebuilt the site as code I own and host myself, and the difference is not aesthetic. It is that my agent can now change my website.
If your site is on a closed platform, you have two honest options. Rebuild it as code, which Claude Code can genuinely do with you in a few sessions. Or keep the collection half of this guide and do the publishing half by hand, which still leaves you far better off than nothing.
If you are rebuilding, ask for a static site on Astro or Next.js, deployed to Vercel, with the content in files rather than a database. That combination is what makes the rest of this guide a one-sentence operation later.
Step 2: Build the collection sheet
Build the store before the form. The shape of the sheet is the contract everything else depends on.
Create a Google Sheet called "Testimonials" with one tab named "Submissions". Give it exactly these columns in this order: ID, Name, Job Title, Company, Quote, Rating, Source, Photo URL, Highlight, Approved, Display Section, Private Feedback, Date Submitted, Follow Up Opt In, Email Rules for the columns: - ID is auto-generated as T-001, T-002 and so on. Never reuse a number. - Rating is 1 to 5. - Approved holds exactly one of: PENDING, YES, NO. Nothing else. - Display Section holds where on my site it should appear. Leave it blank on new rows so I assign it myself. - Private Feedback is for anything the person marked as not-for-publication. Nothing in that column ever goes on the website. Add a dropdown on Approved and on Display Section so I cannot fat-finger a value. Then tell me the spreadsheet ID and where to find it in the URL.
Two columns in there carry more weight than they look like they do.
Approved. This is your entire safety mechanism. Everything arrives as PENDING. Your site only ever reads rows marked YES. That means a bad submission, a joke submission, or a genuinely negative one can sit in the sheet forever without any risk of it appearing on your sales page.
Private Feedback. A separate box for things people want you to know but not publish. It gets you far more honest input than a single public box does, and it never touches the website. Keeping the two physically separate in the sheet is what makes that promise credible rather than a policy you have to remember.
Get a new build-with-AI guide every week
Weekly playbooks on prompting, agents, and content systems — free.
You're in. First email lands shortly.
Step 3: Build the testimonial page
Now the page people actually see.
Build a testimonial page on my site at /testimonial. It should be a single page with a friendly form. Fields, in this order: 1. Star rating, 1 to 5, big and clickable. Required. 2. "What did you get out of this?" Long text. Required if they picked 4 or 5 stars. If they picked 1 to 3, do not require it and change the label to "What would have made this better?" 3. Anything you'd rather tell me privately. Long text. Optional. Label it clearly as never published. 4. Name. Required. 5. Job title. Optional. 6. Company. Optional. 7. Email. Optional. 8. Photo upload. Optional, images only, 5MB maximum. 9. A checkbox: "Yes, you can use this on your website." Required to submit if the rating is 4 or 5. Design rules: - One question visible at a time, so it never feels like a form. - Mobile first. Most people will do this on a phone. - After submit, show a genuine thank-you, not a redirect. Do not wire it to anything yet. Build the page, show it to me on my local machine, and stop.
The rating-dependent question is the detail that earns its keep. Somebody who picks two stars is not going to write you a glowing paragraph, and asking them to makes the whole thing feel like a trap. Change the question to "what would have made this better" and you get product feedback from the exact people whose opinion is hardest to obtain.
One question at a time matters too. A wall of nine fields gets abandoned. A single question with a big rating widget gets finished, because each step feels smaller than closing the tab.
Notice the last line of the prompt again: build it, show me, stop. Look at the page on your own machine before it can write anywhere. If you only take one habit from this guide, take that one.
Step 4: Wire the page to the sheet
This is the only genuinely technical part, and you are still not writing any of it.
Now wire the testimonial page to the Google Sheet. Build a server endpoint at /api/testimonials/submit that: 1. Accepts the form, including the photo upload. 2. Validates before writing anything: name present, rating between 1 and 5, and a quote present when the rating is 4 or 5. Return a clear error message the person can actually read if any of that fails. 3. Rejects photos over 5MB or that are not JPEG, PNG or WebP. 4. Uploads an accepted photo to a specific Google Drive folder, makes just that file publicly viewable, and stores the resulting link. 5. Reads the sheet to find the highest existing ID and writes the next one. 6. Appends one row with every field in the right column. 7. Sets Approved to PENDING when the rating is 4 or 5, and to NO otherwise. Nothing lands on my site by default. Ever. 8. Leaves Display Section blank for me to fill in. Store the spreadsheet ID, the Drive folder ID and the Google credentials as environment variables, never in the code. Tell me exactly which ones to create and where to put them. Then walk me through one real end-to-end test submission and show me the row it created.
Point seven is the one to read twice. Approved defaults to PENDING for good ratings and NO for poor ones, and nothing lands on your site by default. Everything else in this build is convenience. That line is the guardrail.
The photo handling is worth understanding at a high level, because it is where people accidentally expose things. The upload goes to one specific Drive folder, and only that individual file is made publicly viewable. Not the folder. If you make the folder public, every photo ever uploaded is public, including any you later decide not to use.
On the environment variables: these are settings stored outside your code, so your spreadsheet ID and your Google credentials never end up in a public repository. Your agent will tell you exactly which to create. You paste them into Vercel's settings screen. That is the whole concept.
Step 5: Put the ask inside your course
The technology is now finished. This step is what determines whether you get testimonials at all.
Mine sits at the end of my 20-day course. Students who reach the final lesson get a link that says, roughly, share your testimonial if you got value from this. That placement is doing three things at once. It catches people at peak result rather than peak enthusiasm at signup. It self-selects for people who actually finished. And it costs you nothing to ask, because they are already in a moment of completion.
What has worked for me: put it at the natural end of the program, ask once rather than nagging, and be specific about what you want. "What did you get out of this" produces usable quotes. "Any feedback?" produces "great course, thanks".
Two other placements worth adding once the first is running: after a milestone inside the program rather than only at the end, and a light follow-up to anyone who completed but did not respond.
Step 6: The digest email
Now you need to know when something arrives without checking a spreadsheet you will forget exists.
Build me a digest email about new testimonials. Every morning: 1. Read the Testimonials sheet. 2. Find rows where Approved is PENDING. 3. Send me one plain email with a count at the top, then each pending testimonial in full: name, job title, the quote exactly as they wrote it, the rating, and the date. 4. Include a direct link to the sheet, and a one-line instruction reminding me that flipping Approved to YES is what publishes it. 5. If nothing is pending, send nothing at all. I do not want a daily email telling me there is no news. Send it from my automation account to my personal address. Draft it for me the first time so I can check the formatting before it starts sending on its own.
Mine reads like a status line and then the raw material: how many were scanned, how many are new, then each quote in full. I can read it on my phone and decide which ones I want without opening anything.
The silence rule is the important one. An automated email that arrives every day whether or not there is news gets filtered within a fortnight, and then you have an automation you are no longer reading. Send nothing on empty days and the email keeps its meaning.
To make this run on a schedule, you have the same two choices as any other automation: schedule it on your laptop, which only fires when the laptop is awake, or run it in the cloud on a free GitHub schedule, which always fires. I use the cloud version for anything I would notice missing.
Step 7: Publish with one sentence
This is the part that feels like magic in the video, and it is the least complicated part of the build.
Create a skill called publish-testimonial so I can publish one by talking to you instead of editing code. When I say something like "add number two to my website" or "publish T-014": 1. Read the Testimonials sheet and find that row. 2. Refuse and tell me why if Approved is not YES, or if the consent checkbox column says no. Never publish without both. 3. Ask me which section of the site it belongs in if Display Section is blank. Write my answer back to the sheet. 4. Add it to the site's testimonial data file in the right section, matching the existing format exactly. Keep the quote verbatim. You may fix a typo or trim a trailing sentence. You may not rewrite what they said, and you may not invent a job title. 5. Show me the exact change before saving it. 6. After I approve, save, commit and deploy. 7. Write today's date back into the sheet row so I can see it went live.
A skill is a small instruction file that teaches your agent a procedure once so every future run is a sentence. Writing it takes ten minutes. It pays for itself the third time you publish a testimonial.
Rule four is the one I would not compromise on. Verbatim quotes, typo fixes and trims allowed, rewriting not allowed, job titles never invented. A testimonial that has been improved by an AI is not a testimonial. It is copy you wrote and attributed to a student, which is a different thing entirely and one you do not want on your sales page.
Step 8: Auto-rebuild the site
One loose end. Approving a testimonial by flipping a cell in a sheet changes the sheet, not the website. Static sites only update when they rebuild.
My site rebuilds when its code changes, but testimonials I approve by flipping a cell in the sheet do not trigger a rebuild. Set up a deploy hook: create one in my hosting dashboard, store the URL as a secret, and have the morning digest job call it after it finishes reading the sheet. That way anything I approved during the day goes live the next morning without me touching anything. Tell me where to click to create the hook, and how to test that calling it actually triggers a build.
A deploy hook is a private web address that triggers a rebuild when something calls it. Your scheduled job calls it after each run, so anything you approved during the day is live by the next morning without a single click from you.
Consent, done properly
Worth being precise about, because testimonials are one of the few places where getting this wrong has consequences beyond embarrassment.
Three things make consent real rather than decorative. It has to be specific, attached to the actual words they wrote rather than buried in terms they accepted at signup. It has to be recorded, stored in the same row as the quote so there is no question later about which quote it covers. And it has to be enforced, so publishing is blocked when it is missing rather than relying on you remembering to check.
That is what the Approved column and the consent checkbox are jointly doing, and it is why the publish skill is instructed to refuse rather than warn. A warning gets clicked through. A refusal does not.
Two more things I do. If someone gives a low rating, their words never enter the publishable pool at all, which removes the temptation to quote a partial sentence out of context. And if anyone ever asks to be removed, it is one cell flipped to NO plus a rebuild, which is a two-minute job rather than an archaeology exercise.
What went wrong for me
- The closed platform was the real blocker. Everything downstream of "an agent can edit my website" was impossible until I moved off Kajabi. If you are stuck, that is probably why.
- Publicly sharing the photo folder instead of each file. Share the individual file. The folder holds everything, including submissions you never approved.
- Row IDs generated from a row count. Delete one row and the next submission reuses an ID that already exists. Read the highest existing ID and add one to it.
- Column drift. When something appends a row with a trailing empty value, some tools silently drop it and every field after that point shifts one column left. Write to an explicit column range, then read the row back and check it landed where you think it did.
- The daily "nothing new" email. Trained me to ignore the whole thread inside two weeks. Silence on empty days.
- Approving in the sheet with no rebuild. I flipped three to YES, checked the site, saw nothing, and assumed the integration was broken. The deploy hook in step 8 is the fix.
- Letting the agent polish a quote once. It read better and it was no longer what the student said. Verbatim, permanently.
What's next
The same four pieces, a page you own, a sheet, a digest and a publish skill, cover a surprising amount of ground once they exist.
Places I have pointed it since: case study intake with the same consent handling, a private feedback loop that never publishes anything at all and just tells me what to fix, and a wall-of-love page that pulls from the same sheet with a different display section.
The general shape is worth stealing on its own. Collect into a structured store, default everything to unpublished, review in one place, and make publishing a sentence.
Want the whole thing built for you? If you would rather have this collecting testimonials by the end of the week than build it yourself, I do that.
Book a call with me