SOP Guide

Activation Prompt

You are in Landing Pages mode. Before starting any build, read docs/website-landing-page-ghl-external-hosting-sop.md in full. Core rules: build HTML/CSS externally (Cloudflare Pages or client domain), GHL handles CRM/forms/automations only. Identify the page type (Type 1 campaign, Type 2 website rebuild, Type 3 SEO treatment page) before writing a single line of code. Follow the QA checklist and compliance rules in Sections 6 and 14 before showing anything to the client.

Aurea Growth SOP — Building Websites & Landing Pages with Codex/Claude Code, GHL Logic, and External Hosting

Last updated: 2026-05-13 (revised 2026-05-15: added smart-quote HTML attribute bug to Section 7 and Technical QA; revised 2026-05-13: added GHL iframe performance/reliability pattern, Cloudflare cache headers, robots.txt note, GHL custom code production pattern with absolute URLs, CTA/form tracking hook pattern, mobile sticky CTA note, expanded skill stack and best-practice invocation order)

Use this SOP whenever Aurea Growth builds a client website, paid-search landing page, or campaign page with Codex/Claude Code while using GoHighLevel for CRM, forms, automations, calendars, and follow-up.

Core principle: do not let GHL page-builder limitations force the page to look cheap. Use the best front-end build for the visitor and the best GHL logic for the backend.


0. Page Type Classification

Identify which type you are building before starting. Different rules apply.

Type 1 — Campaign / Paid-Search Landing Page

  • Single treatment or offer.
  • Single primary CTA.
  • Strict qualification gate before calendar access.
  • Free consultation as the offer.
  • GHL handles CRM, tags, automations, calendar, follow-up.
  • Hosted on client domain at a treatment-specific URL.
  • Optimised for Google Ads Quality Score.
  • Does not need ongoing CMS management — it is a campaign asset.

Type 2 — Website Rebuild / Redesign

  • Full homepage plus existing treatment pages.
  • Softer CTA — consultation enquiry or WhatsApp, not a strict suitability gate.
  • SEO preservation is a hard constraint — existing URLs, Rank Math scores, and indexed pages cannot be thrown away without a redirect plan.
  • Ongoing CMS management required — migrate to Framer after Claude Code locks the design, or use a WordPress theme that a non-developer can edit.
  • GHL form is lighter — name, concern, contact — not a 6-step qualification gate.
  • Automation is often already in place via the clinic's existing GHL account.

Type 3 — SEO Treatment Page

  • A single page targeting one treatment keyword for organic search.
  • Not driven by ad spend, so Google Ads Quality Score is not the main concern.
  • Long-form copy, FAQ schema, local intent, internal links to homepage and related pages.
  • Usually part of a website rebuild, not a standalone build.
  • Can be built in WordPress natively or as a standalone HTML page added to an existing site.

Choose the type before opening a file. The structure, CTA logic, form type, and hosting decision all follow from this.


1. Default Recommendation

Best Production Setup

Ad / Search / Campaign Traffic
→ Client domain landing page
→ Custom HTML/CSS built with Codex or Claude Code
→ GHL-powered form / external form tracking
→ GHL calendar or conditional thank-you page
→ GHL workflows, tags, pipeline, tasks
→ GA4 + Google Ads conversion tracking

Why This Is The Default

  • Best for Google Ads Quality Score: page lives on the client domain, with a relevant URL, fast load, strong content match, and trust signals.
  • Best for trust: patients see the real clinic domain, not a generic funnel URL.
  • Best for design quality: Codex/Claude Code can build a premium page without fighting GHL layout constraints.
  • Best for backend operations: GHL still handles CRM, tagging, automations, internal tasks, calendar, SMS/email follow-up, and reporting.

QS vs Attribution — Important Distinction

When pitching the move from GHL subdomain to main client domain, be precise about what actually improves.

Quality Score improves from:
- Better page content relevance to the keyword (this is the main lever)
- Faster page load speed
- Cleaner design and user experience
- Not from the domain change alone

Attribution improves from:
- Having the landing page and thank-you step on the same domain as the ad click (eliminates the cross-domain referral break in GA4)
- Keeping the session alive through form submission without handing off to api.leadconnectorhq.com or page-builder.leadconnectorhq.com as a referral source
- Means Google Ads sees more conversions attributed to google / cpc instead of (direct) or GHL referral

The domain move helps most on attribution. Page content, copy relevance, and load speed help most on QS. Both are worth doing together. Do not over-claim the QS benefit when pitching the domain move — "better page + faster load = better QS" is correct. "Moving to the main domain alone fixes QS" is not.


2. Decision Tree

Option A — Custom Page On Client Domain + GHL Logic

Use this when:

  • We have WordPress, SiteGround, Webflow, Wix, Framer, Cloudflare, or server access.
  • The page needs to look premium.
  • Google Ads Quality Score matters.
  • The offer is high-ticket, medical/aesthetic, dental, or trust-sensitive.
  • The client already has domain authority or a known clinic website.

Recommended for:

  • Google Ads landing pages
  • SEO-ready treatment pages
  • Premium clinic offers
  • High-ticket consultation funnels
  • Pages the client may inspect closely

Option B — GHL Page/Funnel Built Natively

Use this when:

  • Website/domain access is delayed.
  • The page must be live immediately.
  • The form/calendar/workflow logic is more important than perfect design.
  • The page is for internal testing or a fast pilot fallback.

Recommended for:

  • Temporary pilot pages
  • Quick MVP funnels
  • Fallback before a client meeting
  • Simple lead capture pages where design stakes are lower

Option C — Full Custom HTML Pasted Into GHL Custom Code

Primary use: visual testing, fast internal preview, showing the concept to the team.

Also validated as a temporary production approach when DNS/subdomain access is blocked and the existing GHL URL must stay untouched. This keeps the same final URL, the same post-form GHL logic, and avoids any DNS complexity or CNAME negotiation with the client.

Required fixes before pasting into GHL:

  1. Replace all relative assets/ paths with absolute Cloudflare Pages URLs — GHL has no local file context so relative paths silently fail. Catch src=, href=, srcset=, imagesrcset=, and comma-separated srcset values — srcset is easy to miss and will break the hero image.
sed \
  -e 's|href="assets/|href="https://your-project.pages.dev/assets/|g' \
  -e 's|src="assets/|src="https://your-project.pages.dev/assets/|g' \
  -e 's|srcset="assets/|srcset="https://your-project.pages.dev/assets/|g' \
  -e 's|imagesrcset="assets/|imagesrcset="https://your-project.pages.dev/assets/|g' \
  -e 's|, assets/|, https://your-project.pages.dev/assets/|g' \
  index.html | pbcopy
  1. Add a GHL width escape to the outermost page div. GHL wraps pasted code in a constrained container. Without this the page renders narrower than intended:
.page {
  position: relative;
  width: 100vw;
  left: 50%;
  transform: translateX(-50%);
}

Permanent limitations of Option C (still apply even for production use):

  • Not editable section-by-section in GHL — copy/image changes require re-pasting the full HTML.
  • GHL adds a small script/wrapper overhead on top of the page.
  • PageSpeed score will be slightly lower than a pure Cloudflare-hosted version.
  • Not suitable if Camille or the client needs to make copy edits without John.

3. GHL Form Strategy

Best Form Strategy On GHL Pages

Use native GHL forms or surveys.

Benefits:

  • Clean CRM capture.
  • Native custom fields.
  • Tags and workflow triggers work predictably.
  • Conditional logic/disqualification can be configured inside GHL.
  • Calendar routing is easier.

Best Form Strategy On External Pages

Prefer a DOM-compatible form that GHL can track through External Form Tracking.

Requirements:

  • The form exists directly in the page DOM.
  • It uses a real <form> element.
  • Inputs have valid name attributes.
  • The form can be mapped into GHL fields.
  • Success/thank-you states can be tracked for Google Ads conversions.

Iframe Form Embeds

The standard GHL embed often looks like this:

<iframe
  src="https://api.leadconnectorhq.com/widget/form/FORM_ID"
  style="width:100%;height:100%;border:none;border-radius:1px"
  id="inline-FORM_ID"
  data-layout="{'id':'INLINE'}"
  data-trigger-type="alwaysShow"
  data-form-name="Clinic Assessment Form"
  data-height="1437"
  data-form-id="FORM_ID"
  title="Clinic Assessment Form">
</iframe>
<script src="https://link.msgsndr.com/js/form_embed.js"></script>

This is acceptable for quick embedding, but it is not the ideal tracking-critical setup.

Use iframe embeds when:

  • Speed matters more than perfect external tracking.
  • The form is simple.
  • We are using GHL-native submission data and not relying on DOM-level external form tracking.
  • We can still fire conversions from a thank-you page or redirect.

Avoid iframe embeds when:

  • We need precise external form tracking.
  • We need custom JavaScript to inspect field-level answers.
  • We need maximum Google Ads conversion clarity.
  • The form sits inside a custom page and must behave like a native component.

Embed Test Learning — Ayadi 2026-05-11

When testing GHL form embeds inside a custom HTML page:

  • Test through https://, not file://. GHL embed scripts may not behave correctly from a local file.
  • Cloudflare Pages is a good temporary HTTPS preview environment.
  • The GHL POLITE_SLIDE_IN embed is designed to appear as a slide-in overlay. It may render as blank/hidden inside a normal page section because its iframe often starts with display:none and waits for the GHL script to reveal it.
  • For a form that should sit inside the landing page's assessment section, use the INLINE embed mode.
  • If two test URLs look identical after converting both to INLINE, that is expected. The original slide-in version has effectively been changed into the same inline form experience.
  • If replacing a custom/native form with a GHL iframe, remove or guard any JavaScript that expects the old form elements to exist. Otherwise page scripts can crash before scroll-reveal or layout JavaScript runs, making the page look empty between header and footer.

Safe JavaScript pattern:

const form = document.getElementById("suitabilityForm");
const nextBtn = document.getElementById("nextBtn");

function initNativeSuitabilityForm() {
  if (!form || !nextBtn) return;
  // Native form logic here.
}

function initReveal() {
  document.querySelectorAll(".reveal").forEach((element) => {
    element.classList.add("in");
  });
}

initReveal();
initNativeSuitabilityForm();

Production note: for the final client domain version, the inline iframe embed can be acceptable if speed matters and conversion tracking is handled on the thank-you/redirect step. For maximum external tracking visibility, use a DOM-native form with GHL External Form Tracking or webhook submission instead of iframe.

GHL Iframe Performance Pattern — Nurse Kelsey 2026-05-13

GHL iframe embeds can behave like a performance trap on custom landing pages.

What happened:

  • A custom Cloudflare landing page scored around 90 on mobile when the GHL form was not loaded initially.
  • Loading the GHL iframe immediately made the form visible, but dropped mobile performance to around 59.
  • The iframe pulled in the LeadConnector/GHL stack, including third-party JavaScript, Facebook resources, phone-number libraries and extra main-thread work.
  • Over-optimising the form with generic lazy loading / reveal animations / content-visibility caused the form to disappear or render unreliably.

Recommended pattern for campaign pages:

<iframe
  data-src="https://api.leadconnectorhq.com/widget/form/FORM_ID"
  style="width:100%;height:1019px;border:none;display:block;"
  id="inline-FORM_ID"
  data-layout="{'id':'INLINE'}"
  data-form-id="FORM_ID"
  title="Clinic Assessment Form">
</iframe>
const ghlFrame = document.getElementById("inline-FORM_ID");

function loadGhlForm() {
  if (!ghlFrame || ghlFrame.src) return;
  ghlFrame.src = ghlFrame.dataset.src;
}

if (ghlFrame) {
  if (window.location.hash === "#assessment") {
    loadGhlForm();
  } else if ("IntersectionObserver" in window) {
    const formObserver = new IntersectionObserver((entries) => {
      if (entries.some(entry => entry.isIntersecting)) {
        loadGhlForm();
        formObserver.disconnect();
      }
    }, { rootMargin: "900px 0px" });

    formObserver.observe(ghlFrame);
  } else {
    loadGhlForm();
  }

  document.querySelectorAll('a[href="#assessment"]').forEach(link => {
    link.addEventListener("click", loadGhlForm, { once: true });
  });
}

Rules:

  • Do not put reveal animation classes on the form card or form iframe container.
  • Do not use content-visibility on the section containing a GHL iframe.
  • Avoid loading="lazy" on a GHL iframe if the form fails to render reliably.
  • Prefer intent-based loading: load when the user opens #assessment, clicks a CTA, or scrolls near the form.
  • When the iframe is intent-loaded, push a lightweight analytics event at the exact moment src is assigned. GTM/GA4 can then track "form became available" even if the form itself lives inside LeadConnector and cannot be inspected from the parent page.
  • If using GHL's form_embed.js, test carefully. A plain iframe can be more reliable if the height is fixed and the form displays correctly.
  • Expect PageSpeed to penalise immediate GHL iframe loading. This is not necessarily a page-design issue; it is the third-party form stack.

Use this pattern only when an iframe is acceptable. If attribution and field-level visibility are business-critical, build a DOM-native form and submit to GHL instead.

Optional tracking hook pattern for external pages:

window.dataLayer = window.dataLayer || [];

function trackEvent(eventName, properties = {}) {
  window.dataLayer.push({
    event: eventName,
    page_type: "treatment_assessment_landing_page",
    ...properties
  });

  if (typeof window.gtag === "function") {
    window.gtag("event", eventName, properties);
  }
}

document.querySelectorAll("[data-track-cta]").forEach(link => {
  link.addEventListener("click", () => {
    trackEvent("cta_clicked", {
      cta_location: link.dataset.trackCta,
      cta_text: link.dataset.trackLabel || link.textContent.trim()
    });
  });
});

function loadGhlForm() {
  if (!ghlFrame || ghlFrame.src) return;
  ghlFrame.src = ghlFrame.dataset.src;
  trackEvent("assessment_form_loaded", {
    form_id: ghlFrame.dataset.formId,
    form_location: "assessment_section"
  });
}

This does not replace real GA4/GTM setup. It gives GTM a clean dataLayer event contract once the container is added, and it still works harmlessly if gtag is not present.


Step 1 — Gather Client Context

Read the client/prospect file first.

Minimum inputs:

  • Client name, domain, address, phone.
  • Treatment/service focus.
  • Offer and price anchor.
  • Primary audience.
  • Top objections.
  • Proof points.
  • Booking system.
  • GHL sub-account status.
  • Access owner.
  • Existing website/brand style.
  • Required assets.
  • Tracking state: GA4, Google Tag, GTM, Google Ads.

For Google Ads, also define:

  • Primary keyword group.
  • Secondary keyword group.
  • Negative keyword themes.
  • Campaign goal.
  • Conversion event.
  • Budget.
  • Landing page URL.

Step 2 — Choose The Build Path

Use this hierarchy:

  1. Client domain custom page if access exists.
  2. Client domain custom page prepared locally if access is expected soon.
  3. GHL native page fallback if access is blocked.
  4. Full custom HTML pasted into GHL only for temporary preview/testing.

Step 3 — Build The Page As A Source Of Truth

With Codex/Claude Code:

  • Create a local folder under the relevant client/prospect folder or tools/.
  • Build index.html.
  • Keep assets in /assets.
  • Use real images where possible.
  • Compress images.
  • Use patient/customer-facing copy only.
  • Avoid internal language such as:
  • "Quality Score"
  • "lead quality"
  • "campaign"
  • "filter"
  • "GHL"
  • "Vagaro"
  • "Google"
  • "client diary"
  • Any reference to the client by first name in an operational context
  • Any reference to your team member or VA by name

Important: the page must read like the end customer will see it, not like an internal setup guide.

Step 4 — Landing Page Structure

Use this structure for most paid-search clinic campaigns:

  1. Hero
  2. Treatment/service keyword in H1.
  3. Location in H1 or subhead.
  4. Clear outcome.
  5. One CTA.
  6. Real image or clinic/device visual.

  7. Trust strip

  8. Years of experience.
  9. Location.
  10. Founder-led / clinician-led care.
  11. Technology/device proof.
  12. Review or rating if available.

  13. Problem / fit

  14. Who this is for.
  15. What concerns it helps with.
  16. Avoid insecurity-led copy.

  17. Benefits

  18. 3-5 outcome-driven blocks.
  19. Keep claims careful and compliant.

  20. Treatment/service explanation

  21. What it is.
  22. How it works.
  23. What happens during consultation.
  24. Why a course/plan may be needed.

  25. Why this clinic

  26. Local differentiation.
  27. Founder/clinician experience.
  28. Environment.
  29. Proof.

  30. Form / suitability check

  31. Short enough to complete.
  32. Strict enough to protect client time.
  33. One question per step if multi-step.
  34. On long mobile pages, keep a restrained sticky CTA visible after the first screen so users do not need to scroll thousands of pixels back to the form. It should use the same primary CTA, be hidden on tablet/desktop, respect safe-area insets, and never cover footer content.

  35. FAQ

  36. Handle objections before final CTA.
  37. Include timeline, price, suitability, pain, downtime, what happens next.

  38. Final CTA

  39. Same primary CTA.
  40. Clear next step.
  41. Address/location.

Step 5 — Copy Rules

Every visible word must be customer-facing.

Use:

  • "Request an assessment"
  • "Check suitability"
  • "Choose a consultation time"
  • "Your clinician will explain..."
  • "A full course can require..."
  • "This helps keep the consultation transparent..."

Avoid:

  • "Lead"
  • "Filter"
  • "Disqualify"
  • "Pipeline"
  • "Workflow"
  • "GHL"
  • "Quality Score"
  • "Google will learn"
  • "We train Google"
  • "Protect the client's time"
  • "This campaign..."

Step 6 — Form Logic

For high-ticket free consultation funnels, use qualification questions that map to routing.

Example:

1. What are you looking to improve?
2. When would you like to start?
3. Are you open to a course-based plan?
4. Is the likely investment within range?
5. Can you attend the clinic location?
6. Name, phone, email

Routing:

  • Suitable answer path → calendar / consultation booking.
  • Not suitable answer path → polite thank-you page without calendar.
  • Qualified but no booking → GHL task + call/message follow-up.
  • Booked in GHL → manual or automated sync into clinic diary if needed.

Step 7 — Calendar / Booking Logic

Choose one source of truth for patient-facing reminders.

Recommended:

  • GHL handles form, qualification, calendar booking, internal tasks, non-booker follow-up.
  • Clinic booking system handles official appointment confirmation/reminders once duplicated or synced.

Avoid duplicate reminders from both systems unless intentionally configured.

For manual sync:

GHL booking created
→ Internal task assigned
→ Team duplicates appointment into clinic booking system
→ Clinic system sends official confirmation/reminder

Step 8 — Tracking Plan

Minimum tracking:

  • GA4 page_view on landing page.
  • Google Ads click ID capture where possible.
  • Conversion event for suitable form submission.
  • Conversion event for booked consultation.
  • Thank-you page or success state that can be tracked.
  • GHL source fields: campaign, keyword, ad group, GCLID, landing page URL, form answers.

For Google Ads:

  • Primary conversion: suitable assessment request or booked consultation.
  • Secondary conversion: form start, form complete, calendar view.
  • Do not optimise to raw page views or low-quality submissions.

Step 9 — Hosting

WordPress / SiteGround

Best when the client site is WordPress.

Implementation options:

  • Create a custom page template.
  • Use a lightweight custom HTML block if theme allows.
  • Use Elementor only if needed, but avoid bloated widgets.
  • Upload assets to WordPress media library.
  • Add page-specific CSS/JS if supported.

Recommended URL format:

https://clientdomain.com/treatment-name-location/
https://clientdomain.com/treatment-name-assessment/

WordPress + ACF pattern (recommended for campaign pages that need ongoing copy management):

Use Advanced Custom Fields (ACF) to separate design from content. Build the page template in code once, expose only the editable text fields and images as ACF fields in the WordPress admin. The VA or client edits copy through a simple form in WP admin with no HTML exposure. The design and layout are locked.

When to use this pattern:

  • The page needs to look premium (full code control) but the VA or client needs to update headline, body copy, or photos without touching HTML.
  • The alternative would be training a VA on Claude Code just to edit a landing page.
  • The client is on WordPress and Elementor would compromise design quality.

What to expose as ACF fields (typical campaign page):

  • Hero headline
  • Hero subheadline / lede
  • CTA button text
  • Stat strip (4 stats: number + label)
  • Section headlines
  • Body paragraphs
  • Hero image, founder image, treatment/clinic images
  • FAQ questions and answers

What to lock in the template (never editable via ACF):

  • Layout, grid, spacing
  • Typography scale and fonts
  • Colour palette
  • Section order
  • Button styles
  • Scroll reveal logic

Cloudflare Pages

Use when:

  • Client domain access is not ready.
  • We need a fast temporary preview.
  • We need to host a guide or internal client-facing planning doc.
  • Sharing a v2 mockup with a client before committing to full implementation.

For live ads, prefer client domain/subdomain over Aurea-owned domains.

Cloudflare preview note:

  • Deploy the whole project folder, including index.html, test pages, and /assets.
  • Use the root https://project.pages.dev URL if the hashed preview URL has temporary SSL issues immediately after deploy.
  • Hash preview URLs and root Pages URLs may cache differently; after fixes, redeploy and test the root URL first.
  • Add a valid robots.txt file. Otherwise Cloudflare Pages may return the HTML page for /robots.txt, causing Lighthouse/SEO audits to report a malformed robots file.
  • Add a _headers file for long-lived static asset caching:
/assets/*
  Cache-Control: public, max-age=31536000, immutable

/*
  X-Content-Type-Options: nosniff
  Referrer-Policy: strict-origin-when-cross-origin
  • Keep _headers conservative. Avoid adding broad Link: <...>; rel=preconnect headers unless needed, because duplicate/unused preconnects can hurt Lighthouse diagnostics.

Cloudflare Pages deploy gotcha — project must exist before deploying:

wrangler pages deploy with --project-name will fail with "Project not found" if the project does not already exist. wrangler pages project create can also fail with a generic API error (code 8000000) for unclear reasons.

Workaround: create the project directly via the Cloudflare API, then deploy normally.

ACCOUNT_ID="your-account-id"
TOKEN="your-oauth-token-from-~/.wrangler/config/default.toml"

curl -s -X POST "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/pages/projects" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"project-name","production_branch":"main"}'

After that succeeds, deploy normally:

wrangler pages deploy ./folder --project-name project-name --branch main

The account ID is visible in wrangler whoami. The OAuth token is in ~/Library/Preferences/.wrangler/config/default.toml (macOS).

Framer

Use for website rebuilds where the client or a non-developer needs to manage content after launch.

  • Claude Code / Codex locks the design and page structure.
  • Migrate the final design to Framer for CMS-level editing (text, images, pages).
  • Framer handles hosting, CDN, and SSL automatically.
  • GHL still handles forms, CRM, automations, and calendar — same as any other setup.
  • Connect GHL form via JS embed or use a Framer form that posts to a GHL webhook.

Do not use Framer for campaign landing pages where Google Ads Quality Score or custom tracking logic is critical. Use WordPress or a static HTML page for those.

GHL

Use when:

  • External hosting access is blocked.
  • Speed to launch matters.
  • Native form logic is the main priority.

Build natively in GHL, section-by-section. Do not paste the whole page into one code block for production.


5. GHL Native Fallback Build Process

If building in GHL:

  1. Create a new funnel/page in the client sub-account.
  2. Rebuild sections with native rows, columns, headings, images, buttons.
  3. Put global CSS in page custom CSS/header settings.
  4. Use GHL native form or survey.
  5. Configure conditional logic:
  6. Good fit → calendar.
  7. Poor fit → polite thank-you page.
  8. Add tags:
  9. Campaign name.
  10. Suitable.
  11. Not suitable.
  12. Booked.
  13. No booking.
  14. Follow-up required.
  15. Add hidden fields:
  16. UTM source.
  17. UTM medium.
  18. UTM campaign.
  19. UTM term.
  20. UTM content.
  21. GCLID if available.
  22. Landing page URL.
  23. Test workflow triggers.
  24. Test mobile layout.
  25. Test form → contact → tags → pipeline → calendar → reminders.

6. QA Checklist Before Showing Client

Copy QA

  • [ ] No internal terms visible.
  • [ ] H1 matches search intent.
  • [ ] Location is visible above the fold.
  • [ ] CTA is clear and repeated.
  • [ ] Price/investment expectation is transparent if needed.
  • [ ] Claims are careful and compliant.
  • [ ] FAQ answers real objections.

Design QA

  • [ ] Looks like the client brand.
  • [ ] Does not look like a cheap funnel template.
  • [ ] Uses real clinic/device/team assets where possible.
  • [ ] Mobile layout is clean.
  • [ ] Buttons are tappable.
  • [ ] No text overlap.
  • [ ] No oversized desktop-only typography breaking mobile.

Technical QA

  • [ ] No smart/curly quotes (", ", ', ') inside HTML tag attributes — run grep -Pn '[\x{201C}\x{201D}\x{2018}\x{2019}]' index.html before QA. Smart quotes in class=, href=, src= etc. silently break CSS matching and look identical to straight quotes in most editors. Browser assigns no class, styles never apply. (Diagnosed on Dr A Winter site, May 2026.)
  • [ ] Page loads on target URL.
  • [ ] Images load from real hosted URLs, not local paths.
  • [ ] No base64 production images unless unavoidable.
  • [ ] Form submits.
  • [ ] Suitable path works.
  • [ ] Unsuitable path works.
  • [ ] Calendar works.
  • [ ] GHL contact is created/updated.
  • [ ] Tags apply correctly.
  • [ ] Pipeline stage updates.
  • [ ] Internal task fires where needed.
  • [ ] GA4 sees page view.
  • [ ] Google Ads conversion fires on correct event.
  • [ ] Privacy policy link works.

7. Common Mistakes To Avoid

  • Smart/curly quotes (", ") inside HTML attributes — the Edit tool can introduce these silently. The browser treats the attribute value as including the quote characters literally, so class="review-card" never matches .review-card in CSS. Content renders as unstyled plain text with no error shown. Scan for them before debugging cascade or specificity. Run: grep -Pn '[\x{201C}\x{201D}]' index.html
  • Pasting the entire custom page into one GHL custom HTML block and expecting it to be editable.
  • Pasting into GHL with relative assets/ paths — images will silently fail. Always replace with absolute Cloudflare URLs first (see Option C sed command). Do not forget srcset= and imagesrcset= attributes — src= alone is not enough.
  • Forgetting the GHL width escape CSS on .page — the page renders narrower than the design without it.
  • Building a premium page visually but leaving internal setup copy visible.
  • Using GHL iframe embeds and assuming external form tracking has full field visibility.
  • Sending all form submissions to Google Ads as conversions, including low-fit leads.
  • Using discount language for premium/high-ticket clinic offers.
  • Letting two systems send appointment reminders.
  • Hosting a paid-search page on an unrelated Aurea domain when the client domain is available.
  • Forgetting privacy policy, address, business identity, and contact details.
  • Waiting for perfect automation before launching a manual-sync pilot.

8. Handoff To Builder / VA

When handing to a builder or VA, include:

  • Client file path.
  • Final page URL target.
  • Local mockup path.
  • Assets folder.
  • Page structure.
  • Form questions.
  • Routing logic.
  • Tags/custom fields.
  • Workflow list.
  • Calendar link.
  • Reminder ownership.
  • Tracking events.
  • Launch checklist.

Template:

Client:
Page purpose:
Target URL:
Local mockup:
Assets:
Primary CTA:
Form type:
Qualified path:
Not suitable path:
Calendar:
Booking system:
Reminder owner:
GHL tags:
Custom fields:
Tracking events:
Open access needed:
Client review date:

9. Design Standards

Master Skill

For future client landing-page builds, use the Codex skill:

client-landing-page-builder

Use it when John gives a client/prospect name and asks for a landing page, campaign page, website demo, GHL form test, or externally hosted client-domain page. The skill is located at:

/Users/johntossou/.codex/skills/client-landing-page-builder/SKILL.md

It is the orchestration layer for this SOP. It should read the client file, gather local assets, inspect/scrape the website where appropriate, use the relevant design/conversion skills, decide GHL vs external hosting, build the page, add form logic, test via HTTPS, and return the preview path/URL plus open items.

Skills To Invoke

Invoke skills by phase. Do not build from scratch without loading the relevant framework first.

Use the full stack for paid traffic, premium clinic offers, externally hosted pages, or any page that will be sent to a client. Use a lighter stack only for internal mockups, wireframes, or quick one-hour tests.

Phase 1 — Plan (before writing any code)

Task Skill
Orchestrate the whole client landing-page workflow /client-landing-page-builder
Define page structure and conversion logic /landing-page
Establish design system — colour palettes, font pairings, UX patterns /ui-ux-pro-max
Positioning context for repeat clients or multi-page builds /product-marketing-context
Customer language, review mining, objections, and decision triggers /customer-research

Phase 2 — Build (while writing HTML/CSS)

Task Skill
Aesthetic guardrails — what to do and what never to do /impeccable
Premium/luxury visual direction and agency-level restraint /high-end-visual-design
Taste pass for less template-like frontend execution /design-taste-frontend
Copy and value proposition /copywriting
Edit existing copy for clarity, concision, and premium tone /copy-editing
Conversion psychology — framing, anchoring, social proof, objection handling /marketing-psychology
Page-level CRO — offer, proof, objections, CTA hierarchy, section order /page-cro
Pricing/course framing, value anchoring, deposits, packages /pricing-strategy

Phase 3 — Refine (after first draft exists)

Task Skill
Typography — scale, pairing, leading, optical corrections /typeset
Spacing and visual rhythm — fix monotonous grids, weak hierarchy /layout
Remove excess cards, claims, badges, or noisy complexity /distill
Tone down pages that feel too salesy, loud, or synthetic /quieter
UX microcopy, form labels, reassurance copy, and unclear instructions /clarify
Motion — purposeful entrances, hover states, micro-interactions /animate
Colour — add warmth or expressiveness if design feels flat /colorize
Shadows — polished elevation on cards and panels (Tailwind projects) /beautiful-shadows
Tasteful brand moments where appropriate /delight

For premium aesthetic/medical pages, prefer /distill, /quieter, /typeset, and /layout before reaching for louder visual skills. /bolder, /delight, and /overdrive are optional, not default. Use them only when the brand or brief can carry them.

Phase 4 — QA (before client review)

Task Skill
Form friction, field order, qualification logic, reassurance, embed behaviour /form-cro
Metadata, headings, crawlability, local SEO, and Quality Score support /seo-audit
Structured data — local business, service, FAQ, offer where relevant /schema-markup
AI search/entity optimisation for authoritative treatment pages /ai-seo
Events, GA4/GTM contracts, CTA clicks, form load, submission, thank-you conversion /analytics-tracking
UX audit — visual hierarchy, cognitive load, anti-pattern detection /critique
Responsive / mobile pass — breakpoints, touch targets, fluid layout /adapt
Performance — image weight, lazy third-party embeds, animation cost /optimize
Technical QA — accessibility, contrast, errors, responsive checks /audit

Phase 5 — Finish

Task Skill
Final polish — micro-details, consistency, last-mile quality /polish
Intentional A/B test plan for traffic-backed iterations /ab-test-setup
Cloudflare deploy and Pages/Workers checks /cloudflare:wrangler
Browser inspection of local/live page, CTA clicks, mobile states /browser-use:browser

Minimum for every client-facing build: /client-landing-page-builder + /landing-page + /ui-ux-pro-max + /impeccable + /copywriting + /marketing-psychology + /page-cro in Phase 1–2, then /form-cro + /seo-audit + /analytics-tracking + /critique + /adapt + /optimize before sending to client.

Premium/luxury clinic stack: add /high-end-visual-design, /design-taste-frontend, /typeset, /layout, /distill, /quieter, /clarify, and /polish. The desired feeling is expensive, calm, clinically credible, and easy to act on — not decorative, glossy, or over-animated.

Documentation best practice: when a page ships, record the skills used, deploy URL, tracking hooks, GHL form/calendar IDs, and open test items in the project notes or handoff. This prevents the next pass from guessing which quality layers were already applied.

Key distinction — /impeccable vs /ui-ux-pro-max: /impeccable is philosophy and guardrails (what never to do, banned CSS patterns, font reject list). /ui-ux-pro-max is the reference catalogue (161 colour palettes, 57 font pairings, 99 UX guidelines, 50+ styles). Use both together — one sets the rules, the other gives you the options.

Font Selection

Do not use fonts from the impeccable skill reject list. Key banned fonts include: Cormorant Garamond, Playfair Display, Fraunces, DM Sans, DM Serif Display, Inter, Plus Jakarta Sans, Instrument Sans, Instrument Serif, Space Grotesk, Syne, IBM Plex Sans, Lora, Crimson Pro, Newsreader, Outfit.

Process:
1. Write 3 words that describe the brand voice (e.g. warm, clinical, precise).
2. List the fonts you would normally reach for — reject them.
3. Find alternatives on Google Fonts, Pangram Pangram, or Future Fonts.
4. Pair a distinctive display/serif with a clean body sans.

Colour

Use OKLCH throughout, not hex or HSL. OKLCH is perceptually uniform — equal lightness steps look equal.

:root {
  --ink:   oklch(17.5% 0.008 52);   /* near-black, warm tinted */
  --muted: oklch(47% 0.013 57);     /* secondary text */
  --cream: oklch(95% 0.019 82);     /* warm background */
  --green: oklch(33% 0.065 196);    /* brand accent example */
}

Tint all neutrals toward the brand hue at low chroma. Pure black (#000) and pure white (#fff) should not appear.

Banned CSS Patterns

These patterns mark a page as AI-generated and must never appear:

  • border-left: Xpx solid [colour] on cards, alerts, or list items — any width greater than 1px
  • background-clip: text with a gradient (gradient text fill)
  • Glassmorphism used decoratively everywhere (blur + glow borders on every card)
  • Identical card grids (same icon + heading + text repeated 3-6 times)

Spacing

Use a 4pt spacing scale with semantic tokens:

--sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px;  --sp-4: 16px;
--sp-5: 20px; --sp-6: 24px; --sp-8: 32px;  --sp-12: 48px;
--sp-16: 64px; --sp-24: 96px;

Vary spacing for hierarchy. Do not apply the same padding to every section.


10. Post-Launch Edit Ownership

Before choosing the hosting setup, answer: who needs to make copy or image changes after launch, and how often?

This question drives the hosting and CMS decision more than anything else.

Who edits How often Right setup
John via Claude Code Occasionally, on request Static HTML on Cloudflare Pages or client domain. Redeploy with one command. No CMS needed.
VA (Camille) for copy changes Regularly, without involving John WordPress + ACF. VA edits text fields in WP admin. Design stays locked.
VA (Camille) for layout/section changes Regularly GHL native builder. VA stays in her trained environment. Accept design constraints.
Client (Kelsey/Tracey) self-service Occasionally WordPress + Elementor or Framer. Accept some design compromise for the independence.
Nobody — campaign page frozen after launch Never Static HTML anywhere. No CMS overhead needed.

The Bottleneck Problem

Custom HTML pages built with Claude Code are fast to update (describe the change, Claude edits, redeploy in 60 seconds) but only if John is the person making the change. If Camille is supposed to handle landing page copy updates and she does not have Claude Code access, John becomes the bottleneck for every copy edit.

Rule: if the plan is for Camille to own copy updates on a page, do not build it in plain HTML. Use WordPress + ACF or GHL native so she can edit without code.

Rule: if the plan is for John to own the page with Claude Code, static HTML is fine and is actually faster than any CMS for iterating.

When Pitching The Custom Build To A Client

Be upfront about edit access before they commit. State clearly:

  • The page looks better and loads faster than a GHL builder page.
  • Copy or photo changes go through you (John), not self-service.
  • Turnaround for a text change is a few minutes.
  • If they want self-edit access, the option is WordPress + Elementor or ACF, with some trade-off on design quality.

Do not let a client assume they are getting a GHL-style drag-and-drop editor and then discover they cannot log in and change a headline.


11. File Organisation

Single Source Of Truth Rule

One file per project. Do not maintain two copies of the same page in different folders.

Correct location: client or prospect folder.

Clients/Prospects/[ClientName]/[project-folder]/index.html
Clients/Prospects/[ClientName]/[project-folder]/assets/

Do not duplicate into tools/ or .tmp/ for production files. Temporary previews in .tmp/ are disposable and should never become the source of truth.

If two copies exist, reconcile immediately: pick one, delete the other, update the client file and session log.


12. Demo-First Sales Pattern (Website Rebuilds)

For prospects who need to see before they commit:

  1. Build a premium one-page demo using Claude Code or Codex.
  2. Deploy to Cloudflare Pages for a shareable preview link.
  3. Send the link via WhatsApp with a short message: what you built, what can be changed, optional mention of ads if relevant.
  4. Wait for the prospect's reaction before scoping further.
  5. Once they respond positively, send pricing.
  6. Close on setup fee + monthly retainer.
  7. After agreement, rebuild properly with real assets and correct brand copy.

Do not send pricing before sending the demo. The demo anchors the value before the number lands.

The Cloudflare preview is a sales tool, not the final deliverable. The final build goes on the client's domain.


13. SEO Preservation (Website Rebuilds)

Before rebuilding any site that has existing indexed pages:

  1. Audit existing URLs — list every published page and its Rank Math score if available.
  2. Identify which pages Google has indexed — check Google Search Console if accessible, or run site:domain.com.
  3. Keep or redirect every indexed URL. Do not delete a page that Google has already indexed without a 301 redirect to an equivalent page.
  4. Preserve the homepage keyword target — if the current homepage ranks for a core term, the new homepage must target the same term.
  5. Rebuild treatment pages as SEO pages with: H1 containing the keyword, FAQ schema, local intent modifiers, internal links, and accurate alt text.
  6. Do not rely on Rank Math scores as proof of rankings. They are a checklist indicator, not a search position guarantee.

Recommended hybrid structure:

Homepage          → conversion-optimised, clean CTA, premium design
Treatment pages   → SEO-optimised, long-form, FAQ schema, keyword-rich

14. UK Aesthetics Compliance Rules

All copy on aesthetics clinic pages must follow these rules before going live.

Wording

Avoid Use instead
Botox Anti-wrinkle treatment, botulinum toxin treatment
Filler (as a casual shorthand) Dermal filler, lip filler
"Guaranteed results" "Expected outcomes", "results vary by individual"
"Permanent" "Long-lasting", "results that build over time"
"Safe" (as a blanket claim) "Clinician-led", "clinically appropriate", "suitable for..."
"No risk" "Minimal downtime", "non-surgical"

Before/After Photos

  • Add a disclaimer near any results imagery: "Results vary. Individual outcomes depend on the treatment area, skin type, and course followed."
  • Do not make before/after the primary hook. Use them as secondary proof, not the headline claim.

Regulated Treatments

  • Botulinum toxin (anti-wrinkle) can only be prescribed by a qualified prescriber in the UK.
  • Do not imply that a practitioner administers prescription-only medicines without confirming their prescribing status.
  • Do not use CQC-regulated language loosely (e.g. "medical clinic", "clinical", "doctor-led") unless the practitioner holds the relevant credentials.

Pricing

  • Showing price ranges is allowed and recommended for transparency.
  • Do not use "from £X" if the lowest price is not genuinely available.
  • Course pricing should be presented as a total investment, not disguised as a per-session number only.

15. Asset Management

Priority order for imagery

  1. Real clinic photos — reception, treatment room, device, practitioner.
  2. Real before/after results (with consent and disclaimer).
  3. Brand-aligned stock — only if nothing real is available and the stock image is not recognisably generic.

Requesting assets from the client

Send this request early, ideally before starting the build:

To get the page looking its best, I need:
- 2-3 photos of the clinic interior or reception
- 1-2 photos of the treatment device or setup
- A practitioner photo if they are happy to be featured
- Any before/after results with patient consent
- Logo in SVG or high-res PNG if available

While waiting for real assets

Use placeholder structure images from the assets folder if they exist, or a neutral dark/cream colour block as a background. Never use obviously generic stock (white-coat models, staged smiling patients, stock devices).

Mark placeholder images with an HTML comment so they are easy to find and replace:

<!-- TODO: Replace with real Exion device photo once received from client -->
<img src="assets/placeholder-device.jpg" alt="Treatment device at [Clinic Name]">

Image format and compression

  • Use WebP where possible for production (smaller file size, supported by all modern browsers).
  • JPEG is acceptable for photos. PNG only for logos or images requiring transparency.
  • Compress all images before uploading. Target: under 200KB per image for hero/above-fold, under 100KB for below-fold.
  • Use loading="lazy" on all below-fold images.
  • Use fetchpriority="high" on the above-fold hero image.

Quick macOS compression with sips (no extra tools needed):

# Resize to max 900px wide (maintains aspect ratio, overwrites in place)
sips -Z 900 assets/kelsey-headshot.jpg

# Batch compress all JPEGs in assets folder
for f in assets/*.jpg; do sips -Z 900 "$f"; done

For the Nurse Kelsey v2 build, compressing a 4MB headshot to 900px reduced it to 113KB with no visible quality loss at display size. Always compress before deploying. A 4MB hero image is a hard fail on mobile QS.


16. Final Rule

Build for the patient/customer first, then wire the backend.

The page should never expose the machinery behind the campaign. GHL, tracking, qualification logic, and manual sync are backend decisions. The visitor should only experience:

This is relevant to me.
This clinic feels trustworthy.
I understand what happens next.
I know whether this is likely within my budget.
I can request the right consultation without confusion.

Activation Prompt

You are in Clinic SEO mode. This lane covers SEO delivery for aesthetics/dental clinic clients. Read lane-clinics.md in full before touching any client site. Priority order: GBP optimisation first, then treatment pages (1,500+ words, 10 sections), then concern pages, then blog clusters. Google treats aesthetics as YMYL — E-E-A-T, practitioner credentials, and real patient signals matter heavily. Never clone location pages. Every location page must have unique local content.

Lane: Client SEO Service Delivery

Lane: Aesthetics and dental clinic clients
Domains: Client-owned (Wix, WordPress/SiteGround, Cloudflare Pages)
Audience: Clinic patients searching locally on Google
Owner: Camille executes, John signs off on strategy and keyword choices
Priority: High — billable service, requested by Ayadi and Nova unprompted
Active clients: Ayadi (WordPress + Yoast), Nova Beauty (Wix)
Back to: MASTER.md


Aurea Growth — AI-Powered SEO Guide for Aesthetics Clinics

Scope: Practical, low-overhead SEO playbook for UK aesthetics/dental clinics.
Who executes: John (strategy + Claude Code) or Camille (content implementation).
Leverage: Claude Code and Codex handle the heavy lifting — writing, schema, GBP copy, keyword clustering. You implement.


0. Platform Snapshot (What Each Site Already Has)

From Wappalyzer audit (May 2026):

Nova Beauty (Wix) Ayadi (WordPress)
Platform Wix WordPress + Elementor
SEO Plugin None detected Yoast SEO ✓
Analytics Facebook Pixel only Site Kit (GA + Search Console) ✓
CDN Google Cloud CDN SiteGround CDN
Schema Wix auto (partial) Yoast auto (full)
Hosting Wix managed SiteGround
Tech SEO readiness Moderate Strong

What this means:
- Ayadi has a stronger baseline. Yoast + Site Kit = ready to optimize immediately.
- Nova has no Google Analytics. First action: connect GA4 + Search Console via Wix dashboard.
- Both sites are missing treatment-specific pages. That is the #1 gap for both.


1. Priority Stack — What To Do First

Do not try to do everything at once. This order matters.

Month 1 — Foundation (no content yet, just setup)

Priority Action Who Time
1 Connect Google Analytics 4 to Nova Beauty (Wix) John 20 min
2 Connect Google Search Console to Nova Beauty (Wix) John 10 min
3 Claim and fully optimize Google Business Profile for both clinics John / Camille 1.5 hrs each
4 Audit and fix NAP consistency (name, address, phone) across all directories Camille 2 hrs
5 Set up automated review request flow (GHL post-visit SMS → Google review link) John 1 hr
6 Build keyword list for each clinic using Claude Code + SEMrush/free tools John 1 hr

Month 2 — Content Build

Priority Action Who Time
7 Create 8-12 treatment pages per clinic (Claude Code generates, Camille implements) Both 2-3 hrs/clinic
8 Add JSON-LD schema markup to every treatment page (Claude Code generates, paste in) Camille 30 min total
9 Optimize all meta titles and descriptions (Claude Code generates batch) Camille 1 hr
10 Write and implement GBP service descriptions for every treatment Camille 1 hr

Month 3 — GEO + Growth

Priority Action Who Time
11 Restructure existing content for GEO (AI search) using Claude Code reformat prompt Camille 2 hrs
12 Publish 2-4 blog posts targeting informational keywords (how much does X cost, etc.) Camille 2 hrs
13 Submit site to relevant directories (Doctify, Fresha, Treatwell profile if kept, Phorest) Camille 1 hr
14 First review audit — are reviews coming in? Adjust automation if not John 30 min

2. Google Business Profile — Full Optimization

This is free, high-leverage, and takes one afternoon. Do this before anything else.

Step-by-step setup

  1. Go to google.com/business and sign in with the clinic's Google account
  2. Claim the profile if not yet claimed
  3. Fill in every field. Do not leave anything blank.

Exact fields to complete

Business name: Exactly as it appears on signage — no keyword stuffing.

Primary category:
- Aesthetics clinic: "Medical Spa" or "Skin Care Clinic"
- Dental + aesthetics (Nova): "Dental Clinic" as primary, add "Medical Spa" as secondary

Secondary categories: Add all that apply:
- Laser Hair Removal Service
- Skin Care Clinic
- Medical Spa
- Beauty Salon (only if applicable)

Business description (250-300 words):
Use Claude Code to generate this (see prompt in Section 5). Key rules:
- Paragraph 1: Who you are, what you specialise in, why patients trust you (credentials, years of experience)
- Paragraph 2: Key treatments, differentiators (prescriber-led, non-invasive, medically supervised)
- Include location (East Finchley, North London) naturally — not forced
- Do NOT mention prices or offers here

Services tab: Add every treatment individually.
For each service include:
- Name of treatment
- Description (50-80 words, written by Claude — see prompt in Section 5)
- Price range or "Contact for pricing"

Photos: Minimum 20 photos.
- Clinic exterior (from street)
- Reception and waiting area
- Treatment rooms
- Before/after results (with patient consent)
- Team photos (practitioners with credentials visible)
- Equipment photos (device brand logos are trust signals)
- At least 1 new photo per month going forward

Hours: Exact, including special holiday hours. Keep these updated.

Q&A section: Pre-populate this yourself. Ask the 5 most common questions patients have and answer them. Claude Code can draft these (see prompt in Section 5).


3. Technical SEO — Platform-Specific Setup

3a. Wix (Nova Beauty)

Wix is not the ideal SEO platform but it is workable for a single-location clinic. Follow these steps.

Step 1: Connect Analytics (Critical — do this first)
- Wix dashboard → Marketing & SEO → Marketing Integrations
- Connect Google Analytics 4 (enter GA4 Measurement ID)
- Connect Google Search Console: Settings → Domains & Emails → Add Google Search Console

Step 2: Set up Wix SEO Basics
- Go to: Marketing & SEO → SEO → SEO Setup Checklist
- Complete every item in the checklist (takes 30 min)
- Enable the XML sitemap (it auto-generates — just make sure it's on)
- Set your primary domain (with or without www — pick one, stick to it)

Step 3: URL structure
- Keep URLs short and keyword-focused
- Format: /treatments/botox-north-london or /botox-north-london
- Avoid: /page-12345 or /copy-of-treatments
- In Wix: Edit page → Settings → SEO → Page URL

Step 4: Meta tags on every page
For each treatment page, fill in:
- SEO Title: [Treatment] in [Location] | [Clinic Name] (max 60 characters)
- Meta Description: 150-160 characters — state the treatment, location, and one differentiator

Use Claude Code to generate these in bulk (see Section 5).

Step 5: Add JSON-LD schema
Wix supports custom JSON-LD. For each treatment page:
- Edit page → Settings → Advanced → Additional meta tags
- Paste in the schema code generated by Claude (see Section 5)

Step 6: Image optimization
- Before uploading any image to Wix, rename the file descriptively
- Bad: IMG_3845.jpg
- Good: botox-north-london-nova-beauty.jpg
- After upload, add alt text to every image via the Wix image editor
- Use Claude to generate alt text in bulk from a list of image names

Key Wix SEO limitations to know:
- You cannot install SEO plugins like Yoast — all optimization is manual
- Wix's built-in performance is slower than WordPress on complex pages (acceptable for a clinic)
- URL customization is limited but sufficient for this use case
- Structured data is manual JSON-LD paste — Claude generates it for you

3b. WordPress + Yoast (Ayadi)

Ayadi already has the best possible setup. Yoast is installed, Site Kit is connected. Optimize the configuration.

Step 1: Yoast configuration
- Go to: Yoast → General → Features
- Make sure all are ON: SEO analysis, Readability analysis, Schema, XML Sitemaps

Step 2: Set your site identity in Yoast
- Yoast → General → Site Representation
- Choose "Organisation" (not individual)
- Add clinic name, logo, business type ("MedicalBusiness")

Step 3: Schema type for pages
- For treatment pages: Yoast → Schema → Page type = "Web Page", Article type = "None"
- Schema for services is added via custom JSON-LD (Claude generates it — see Section 5)

Step 4: Yoast for every treatment page
In the Yoast panel at the bottom of each page editor:
- Focus keyword: [treatment] [location] (e.g. "HIFU East Finchley")
- SEO title: [Treatment] in [Location] | Ayadi Beauty
- Meta description: 150-160 characters
- The Yoast traffic light will tell Camille if the page passes — aim for green on both SEO and Readability

Step 5: Internal linking
- Every treatment page links to at least 2 related treatment pages
- Every treatment page links to the contact/booking page
- The homepage links to all main treatment category pages

Step 6: Cornerstone content
In Yoast, mark your most important pages as "Cornerstone Content" — these get prioritised in Yoast's internal link suggestions.
Cornerstone pages: Botox, Fillers, HIFU, Laser Hair Removal, whatever the top 3 treatments by revenue are.


4. Treatment Page Structure — The Core SEO Asset

Every treatment needs its own dedicated page. This is the single highest-impact SEO action.

How many pages to build

Nova Beauty targets:
- Anti-wrinkle (Botox) — North London
- Dermal Fillers — North London
- HIFU — East Finchley
- Profhilo / Polynucleotides — North London
- Laser / Photofractional — East Finchley
- Dental Implants — East Finchley
- Invisalign — North London
- IV NAD+ Therapy — London (broader — less local competition)
- Exosome Therapy — London
- Microneedling — North London

That is 10 pages. Build all 10 before anything else.

Ayadi targets:
- EMSculpt Neo / BTL Exion — North London
- Geneo X Facial — East Finchley
- Body Contouring — North London
- Laser Hair Removal — East Finchley / North London
- Clinical Massage — East Finchley
- Facials — East Finchley

That is 6-8 pages. Each one targets a different keyword combination.

Exact page template

Use this structure for every treatment page. Claude Code generates the content — Camille pastes it in.

URL: /treatments/[treatment-name]-[location]
Example: /treatments/hifu-east-finchley

H1: [Treatment] in [Location] | [Clinic Name]
Example: HIFU Treatment in East Finchley | Nova Beauty Clinic

Opening paragraph (60-80 words):
State what the treatment is, who it is for, what results patients can expect,
and how long results last. Include the target keyword in the first sentence.

H2: What is [Treatment]?
Plain-language explanation. No medical jargon without explanation.
3-4 short paragraphs or a bullet list.

H2: How [Treatment] Works
Step-by-step process: before, during, and after.
Keep it factual and reassuring.

H2: Key Benefits
Bullet list of 5-8 patient-outcome benefits.
Focus on: results, downtime, how it compares to alternatives.
Example: "No surgery, no downtime — results in one session"

H2: Ideal Candidate
Who is a good fit for this treatment.
Who to consult on first (skin type, contraindications — brief and reassuring, not frightening).

H2: Results & Timeline
When they see initial results.
When results peak.
How long results last.
What affects longevity.

H2: Before & After
[Image gallery placeholder or actual images with alt text]

H2: Frequently Asked Questions
Minimum 6 questions. Format as question/answer pairs.
Must include: How much does [treatment] cost? Does [treatment] hurt?
How long does [treatment] last? How many sessions do I need?
What should I do before/after? Is [treatment] safe?

H2: Book Your [Treatment] Consultation
Short closing paragraph (2-3 sentences).
One clear CTA: a button or embedded form linking to the booking system.
Include phone number and WhatsApp if applicable.

Local keyword targeting

Format: [Modifier] + [Treatment] + [Location]

Use these location modifiers for East Finchley clinics:
- East Finchley
- North London
- N2 (postcode — some patients search by postcode)
- Finchley (without "East")
- Highgate (5 min away — searches spill over)
- Muswell Hill (adjacent)

Do not try to rank for "Botox London" — too competitive. Start with East Finchley and North London.


5. Claude Code Workflow — Prompts and Processes

This section is the core of the AI leverage. John or Camille uses these prompts. Claude Code does the work.

How the 6 workflows fit together

There are 6 workflows. They are not all equal. Read this before diving in.

F is always first. Run it once per clinic at the start. It gives you the keyword list that drives everything else — which treatments to target, which location modifiers to use, which blog topics to write. Do not run A, B, C, D, or E before you have run F.

A and E are always paired. Every time you generate a treatment page with A, you immediately run E on the output. A writes the page. E restructures it for AI search. One does not get published without the other. Think of them as one step in two parts.

B, C, D are one-time batch runs per clinic. Once all treatment pages are written (after all your A+E runs), run B to generate all meta titles and descriptions in a single prompt. Run C to generate all schema markup in one go. Run D once for GBP content (business description, service descriptions, Q&A). These do not repeat unless the clinic adds new treatments.

Sequence per clinic:

Step 1 — Run F once         → get keyword list
Step 2 — Run A+E per treatment → build all treatment pages
Step 3 — Run B once         → all meta titles + descriptions
Step 4 — Run C once         → all schema markup
Step 5 — Run D once         → all GBP content

How many times to run each workflow for Nova and Ayadi:

Workflow Nova Beauty Ayadi Notes
F (keyword research) 1x 1x Once per clinic at start
A+E (treatment pages) 10x 6-8x Once per treatment, always paired
B (meta batch) 1x 1x After all pages are written
C (schema batch) 1x 1x After all pages are written
D (GBP content) 1x 1x Once per clinic

Total runs for both clinics combined: roughly 20-22 A+E pairs, 4 batch runs (B, C, D, F x2).

Who runs what:

  • John runs F (keyword research requires judgment on which keywords to include)
  • John or Camille runs A+E (mechanical once you have the keyword list)
  • Camille runs B, C, D (batch prompts, pure execution)
  • Camille implements everything into Wix (Nova) or WordPress (Ayadi)

Workflow A — Generate a treatment page

You are a medical aesthetics SEO copywriter for a UK clinic.

Write a complete, SEO-optimised treatment page for the following:
- Treatment: [INSERT TREATMENT NAME]
- Clinic: [INSERT CLINIC NAME]
- Location: [INSERT AREA, e.g. East Finchley, North London]
- Primary keyword: [e.g. "HIFU East Finchley"]
- Secondary keywords to include naturally: [e.g. "HIFU North London, face lifting East Finchley, non-surgical facelift N2"]
- Clinic differentiators: [e.g. "medically supervised, 25 years experience, Obagi-certified"]
- Target word count: 900-1,100 words

Page structure required:
H1 → Opening paragraph → H2: What is it → H2: How it works → H2: Benefits (bullet list) → H2: Ideal candidate → H2: Results & timeline → H2: FAQs (6 questions minimum) → H2: Book a consultation

Tone: Authoritative and warm. Clinical but not cold. No jargon without explanation. Short paragraphs (2-3 sentences max). Definitive statements, not vague claims.

Do NOT include: Prices (unless instructed), competitor names, unsubstantiated superlatives ("best in London").

Output the full page content ready to paste into [Wix/WordPress].

Workflow B — Generate meta titles and descriptions in batch

Write SEO meta titles and meta descriptions for the following treatment pages at [CLINIC NAME] in [LOCATION].

Rules:
- Title: max 60 characters, format = [Treatment] in [Location] | [Clinic Name]
- Description: 145-155 characters, include treatment + location + one differentiator + implicit CTA
- No clickbait, no ALL CAPS, no em dashes

Pages:
1. Botox / Anti-wrinkle injections
2. Dermal fillers
3. HIFU face lifting
4. Profhilo
5. Microneedling
[Continue for all pages]

Output as a table: | Page | Title | Description |

Workflow C — Generate JSON-LD schema for a treatment page

Generate valid JSON-LD structured data markup for the following:

Page type: Service page for a UK medical aesthetics clinic
Clinic name: [CLINIC NAME]
Clinic address: [FULL ADDRESS WITH POSTCODE]
Clinic phone: [PHONE]
Clinic website: [URL]
Treatment: [TREATMENT NAME]
Treatment description: [1-2 sentences]
Price range: [e.g. "£180–£400" or omit if not sharing prices]
Provider type: MedicalBusiness

Include: LocalBusiness + MedicalBusiness schema, Service schema for the treatment, AggregateRating if provided.

Output: Production-ready JSON-LD code block only. No explanation.

Paste the output into:
- Wix: Edit page → Settings → Advanced → Additional meta tags
- WordPress: Use a plugin like "Schema Pro" or paste in the page's Custom HTML block

Workflow D — Generate GBP content

Business description:

Write a Google Business Profile description for [CLINIC NAME], a [type of clinic, e.g. "doctor-led medical aesthetics clinic"] in [LOCATION].

Details:
- Key treatments: [list top 5]
- Credentials: [e.g. "prescriber-led, CQC registered, 25 years experience"]
- Differentiators: [e.g. "no Groupon deals, no discounts, outcome-focused"]
- Target patient: [e.g. "discerning patients in N2 and surrounding areas"]

Rules:
- 200-300 words
- Include location (area name and postcode) naturally
- Do not mention prices
- Do not use em dashes
- No generic phrases like "state of the art" or "cutting edge"
- End with a soft CTA: "Book a consultation" or "Get in touch"

Service descriptions (GBP):

Write Google Business Profile service descriptions for the following treatments at [CLINIC NAME]:
[List of 8-10 treatments]

Rules per description:
- 60-80 words each
- What the treatment does + who it is for + one key benefit
- Include location if it fits naturally
- No prices
- No em dashes

Output as a list: Service name → Description

GBP Q&A pre-population:

Write 8 Google Business Profile Q&A entries for [CLINIC NAME], an aesthetics clinic in [LOCATION].

Questions should cover:
- Opening hours and booking process
- Whether the clinic accepts walk-ins
- What to expect at a first consultation
- Whether treatments are medically supervised
- How to find the clinic (parking, public transport)
- Whether they cater to nervous or first-time patients
- What makes them different from beauty salons
- Cancellation policy

Output format: Q: [question] A: [answer, 40-60 words]

Workflow E — GEO restructure (AI search optimization)

Once a treatment page is drafted, run this to optimize it for ChatGPT, Perplexity, and Google AI Overviews:

Rewrite the following treatment page content to be optimized for AI search engines (ChatGPT, Perplexity, Google AI Overviews).

Rules for AI-search optimization:
- Maximum 3 sentences per paragraph
- Lead every section with the direct answer first
- Convert any long paragraphs into bullet lists where possible
- Add specific numbers and timelines (e.g. "results appear within 3-7 days" not "results appear quickly")
- Replace vague claims with definitive statements
- Add a short "Quick Summary" section at the top (4-6 bullet points: what it is, who it is for, how long results last, downtime, price range if available)

Do not change the page structure or headings. Only restructure the prose within each section.

[PASTE TREATMENT PAGE CONTENT HERE]

Workflow F — Keyword research with Claude

I am doing local SEO for [CLINIC NAME], a [type of clinic] in [SPECIFIC AREA, e.g. East Finchley, North London, N2].

Generate a keyword list targeting local patients. Focus on:
1. Treatment-specific keywords with location modifier (e.g. "botox East Finchley")
2. Intent-based keywords (patients ready to book, not just browsing)
3. Question-based keywords for blog content (e.g. "how much does HIFU cost in London")
4. Adjacent keywords that capture patients earlier in the decision process

For each keyword, estimate:
- Search intent (commercial, informational, navigational)
- Approximate competition level (low/medium/high based on your knowledge)
- Which page on the site should target this keyword

Treatments to target: [list all clinic treatments]
Avoid: Brand keywords for other clinics, keywords targeting practitioners (not patients), national keywords too broad to compete on.

Output as a table: Keyword | Intent | Competition | Target Page

6. Review Automation — The Off-Page SEO Lever

Reviews are the fastest local SEO signal after treatment pages. Every new Google review improves local pack ranking.

Setup (GHL-based, already in John's stack)

  1. Create a GHL workflow: trigger = appointment completed (status change in GHL or Vagaro webhook)
  2. Wait: 60-90 minutes after appointment end time
  3. Send SMS: "Hi [name], thank you for visiting us today. If you have a moment, we'd love to hear how it went: [Google review link]"
  4. If no response after 48 hours: send one follow-up email with the same link
  5. Cap at 2 touchpoints — no more

Google review link format:
https://search.google.com/local/writereview?placeid=[PLACE_ID]
Get the Place ID from Google Business Profile → Manage profile → Get more reviews → Copy link

Review velocity targets

Both Nova and Ayadi have very few reviews relative to their quality. Targets:
- Month 1-3: 1 new review per week minimum
- Month 4+: 2 per week as patient volume grows

At 2 reviews/week, you reach 50 reviews in ~6 months. That alone moves a clinic from invisible to visible in local pack results for most treatment keywords in East Finchley.

What to do with reviews for SEO

  • Respond to every review within 48 hours. Include the treatment name and location naturally.
    Example: "Thank you so much for sharing your experience with our HIFU treatment in East Finchley..."
    This is free keyword placement in a Google-indexed response.
  • Flag and report any fake negative reviews promptly.

7. GEO — Optimising for AI Search (ChatGPT, Perplexity, Google AI Overviews)

AI-referred web traffic grew 527% year-over-year in early 2025. By the time your clients rank on traditional search, GEO is already the next frontier. Build it in from the start.

See MASTER.md for the universal GEO rules and restructure prompt.

GEO content to create separately (beyond treatment pages)

FAQ pages: A dedicated FAQ page per treatment is high-value for AI citation. Patients often ask AI tools questions like "how many sessions of laser hair removal do I need in the UK?" A well-structured FAQ page has a high chance of being cited.

Comparison content: "HIFU vs. surgical facelift: key differences" — AI models love comparison content. One blog post per competitive comparison, written using the GEO restructure prompt.

Local authority content: "What to look for in an aesthetics clinic in North London" — written from the clinic's perspective. This positions the clinic as the authoritative voice and increases citation probability for local queries.


8. Blog Strategy — Only What You Can Sustain

Do not publish 100 blog posts. Publish 6-10 high-quality, GEO-optimized posts and keep them updated.

Blog topics to prioritise (informational + high local intent)

Cost/pricing posts (high conversion intent):
- "How much does Botox cost in North London in 2026?"
- "HIFU price guide: what to expect in a London clinic"
- "Dental implants vs veneers: cost comparison in East Finchley"

Process/expectation posts (pre-decision research):
- "What to expect at your first Botox consultation in the UK"
- "How many sessions of laser hair removal do you need?"
- "What is the recovery like after HIFU?"

Adjacent content (top of funnel):
- "Signs you might benefit from Profhilo (and who it is not right for)"
- "HIFU vs. thread lifts: which is better for skin laxity?"

Blog cadence

  • 1-2 posts per month is sufficient for a local clinic
  • Prioritise updating existing posts over creating new ones after 6 months
  • Always check: does this post target a keyword with real local search volume before writing it?

9. Directory & Citation Building

NAP consistency (Name, Address, Phone) across the web is a local ranking factor. Camille can do this in 2-3 hours once, then it only needs checking quarterly.

Directory list for UK aesthetics clinics

High priority (submit first):
- Google Business Profile (already covered)
- Bing Places for Business (free, often forgotten)
- Doctify (UK healthcare review platform — important for medical credibility)
- Fresha (if the clinic uses or plans to use it for booking)
- Treatwell (even if moving away from it as a booking channel, the listing still drives discovery)
- Yell.com
- Thomson Local
- Trustpilot

Secondary priority:
- Facebook Business Page (check address is correct)
- Instagram Business profile (check phone/website)
- Whatclinic.com
- Booksy (if relevant to treatment mix)

NAP format to use consistently everywhere:

Nova Beauty Clinic
260 East End Road
East Finchley
London
N2 8AU
020 5925 1232

Any variation in capitalisation, comma placement, or abbreviation counts as an inconsistency. Pick one format and replicate it exactly.


10. Ongoing Cadence — What To Do Each Week/Month

Weekly (Camille, 30-45 min)

  • Check if any new Google reviews came in — respond to all within 48 hours
  • Check GBP for new Q&A submissions from patients — answer any within 24 hours
  • Flag any technical errors in Search Console (Ayadi) or Wix SEO dashboard (Nova)

Monthly (John or Camille, 1-2 hrs)

  • Review Search Console data: which queries are generating impressions? Which pages are ranking but not getting clicks?
  • Publish or update 1-2 blog posts
  • Add 5-10 new photos to each clinic's GBP
  • Check review count and velocity — is the automation working?

Quarterly (John, 1 hr)

  • Full keyword position check: are the treatment pages moving up in rankings?
  • Review NAP consistency across directories — anything changed?
  • Update any treatment pages with new clinical information or updated pricing notes
  • Update "2026" references in content to stay current

11. What Not To Do

Do not keyword stuff. Writing "botox east finchley botox north london botox n2" in a paragraph kills the page.

Do not create thin location pages. For a single-location clinic, creating 40 near-identical pages triggers a duplicate content penalty. Build 10 high-quality unique pages.

Do not ignore page speed. Compress every image before uploading. Use TinyPNG or Squoosh.

Do not build backlinks through link farms. Only pursue: Doctify, local press mentions, genuine patient review platforms.

Do not use AI-generated content without review. Claude Code generates the draft. A human reviews for accuracy before publishing.


12. Measuring Success

After 60-90 days, check these metrics:

Metric Tool What to look for
Organic impressions Google Search Console Increasing month-over-month
Treatment page clicks Google Search Console Treatment pages appearing with clicks
GBP calls and directions Google Business Profile Insights Increasing post-optimisation
Google review count GBP Adding 1+ per week
Local pack appearance Manual search (incognito, location set to East Finchley) Do you appear in the 3-pack for any treatment?
Organic sessions GA4 Month-over-month growth

Do not obsess over keyword position before 90 days.


Appendix: Quick Reference — Claude Code Prompts at a Glance

Execution order (always follow this sequence)

1. F  — Keyword research         → run once per clinic, before anything else
2. A  — Treatment page           → run once per treatment (10x Nova, 6-8x Ayadi)
3. E  — GEO restructure          → run immediately after every A, never skip
4. B  — Meta titles + descs      → run once after all A+E pages are done
5. C  — Schema markup            → run once after all A+E pages are done
6. D  — GBP content              → run once per clinic (3 sub-prompts: description, services, Q&A)

Who does what

Task John Camille
Run F (keyword judgment) Yes No
Run A+E (content generation) Either Either
Run B, C, D (batch outputs) Either Yes
Implement into Wix (Nova) No Yes
Implement into WordPress (Ayadi) No Yes
Connect GA4 + Search Console (Nova) Yes No
Set up GHL review automation Yes No
Optimise GBP (upload photos, fill fields) Either Yes

13. 2026 Site Architecture for Serious Clinics

Added 2026-05-15. Source: synthesis of current aesthetics SEO data — CosmediCheck, Boutique SEO, Clinic Grower, YEAH! Local, Local SEO Services UK.

Google treats aesthetics as YMYL (Your Money or Your Life) healthcare content. Trust, local authority, practitioner credibility, and real patient signals carry heavy weight in the ranking algorithm. The clinics winning local search are not the best marketers — they are the ones with the strongest combination of structural and entity signals.

The 4-Level Architecture

The correct structure for a serious multi-treatment aesthetics clinic:

/                          — Homepage (brand + authority hub)
/treatments/               — Treatment hub (navigation + internal link distribution)
/treatments/botox/         — Core treatment page (1,500-3,000 words, money page)
/treatments/lip-fillers/   — Core treatment page
/concerns/tired-eyes/      — Concern page (problem-first SEO)
/concerns/jowls/           — Concern page
/locations/london/         — Real location page (not cloned treatment pages)
/locations/chiswick/       — Real location page
/blog/profhilo-vs-pn/      — Blog cluster (supporting topical authority)
/practitioners/dr-x/       — Practitioner entity page

This is NOT: /botox-london/, /botox-chelsea/, /botox-fulham/ with 95% identical content. That pattern gets algorithmically suppressed as doorway spam.

What a 2026 Treatment Page Looks Like

Minimum 1,500 words. 3,000+ for competitive treatments like Botox, fillers, Profhilo. Sections:

  1. Hero — clear H1, subhead, CTA, real clinic image, trust badges, review snippet, practitioner name
  2. Immediate trust block — GMC/NMC number, years experience, clinic awards, press, real practitioner photo
  3. "What is [Treatment]?" — mechanism, science, category, who it helps, alternatives. Captures informational and AI search.
  4. Candidate section — "Who is this best for?" Captures symptom/concern/long-tail intent (tired eyes, crepey skin, skin laxity after weight loss)
  5. Treatment areas — under-eyes, neck, hands, face, décolleté. Each adds semantic relevance.
  6. Process — consultation, preparation, injection process, downtime, recovery, aftercare
  7. Results timeline — when results appear, how many sessions, longevity, maintenance. Reduces pogo-sticking.
  8. Real FAQs — pulled from People Also Ask, GBP Q&A, Reddit, consultation objections. FAQ schema is non-negotiable.
  9. Before & After — optimise alt text, filenames, captions, practitioner attribution. Example filename: before-after-polynucleotides-under-eyes-london.jpg
  10. Internal links — every treatment page links to related treatments, concern pages, practitioner page, location pages, relevant blog posts

Concern Pages — Underused Ranking Gold

Problem-first SEO captures patients before they know which treatment they want. Build these:

  • /concerns/tired-eyes/ — links to: polynucleotides, tear trough filler, under-eye treatment blog
  • /concerns/jowls/ — links to: thread lifts, Morpheus8, skin tightening
  • /concerns/acne-scars/ — links to: microneedling, skin peels, collagen stimulation
  • /concerns/skin-laxity/ — links to: Profhilo, Morpheus8, collagen stimulation, blog

These pages rank well because patients search problems before treatments. They also build internal link authority back to core treatment pages.

What a GOOD Location Page Looks Like

Real clinic pages. Not cloned treatment pages with city names swapped.

Each location page must have:
- Real address, embedded map, parking, transport, nearby landmarks
- Clinic-specific photos (not stock photos used across all locations)
- Staff specific to that location
- Unique local content — talk about local clientele, common treatments, demographic patterns, clinic-specific expertise
- Local reviews and testimonials
- Embedded treatment modules: short summaries of treatments available at that location with links to the full treatment pages (not duplicating the full pages)
- "Patients travel from [nearby areas]" to expand local relevance

Example of real local content: "In Chiswick, many patients come to us for subtle regenerative treatments rather than volume-focused filler work." That is unique and useful. That is what Google rewards.

GBP — Still One of The Strongest Local Ranking Factors

Separate GBP per physical location. For each:
- Unique photos per clinic (not the same stock images)
- Treatment services configured properly in the Products/Services section
- Booking links connected
- Regular posts (minimum monthly)
- Practitioner photos and videos
- Q&A populated with clinic team answers

Review wording matters. Coach patients to mention treatment + location. Natural examples:
- "Best lip filler clinic in Chiswick"
- "Dr X explained polynucleotides clearly, really reassured me"
- "Profhilo in Acton — exactly what I was looking for"

Those review strings reinforce both entity relevance and local geographic signals simultaneously.

Schema Priority List

Most clinics implement less than half of this. Do all of it:

Schema Where Priority
MedicalBusiness or MedicalClinic Homepage, location pages Critical
Physician Homepage, practitioner page Critical
FAQPage Every treatment page, every blog post High
Article Every blog post High
BreadcrumbList All pages except homepage High
LocalBusiness Location pages High
Service Core treatment pages Medium
Review / AggregateRating Homepage, treatment pages Medium — only if real reviews

AI Search / GEO Considerations

AI systems (ChatGPT, Perplexity, Google AI Overviews) reward:
- Clean structure with clear headings
- Semantic clarity — what is it, who is it for, what does it cost, what is the downtime
- Expert attribution on every claim (practitioner name, credential)
- Concise, direct answers before expanded explanation
- FAQ formatting throughout
- Entity consistency — same practitioner name, GMC number, clinic name across all pages and external profiles

Pages should answer: what, who, cost, downtime, risks, alternatives, candidacy — directly, not buried in prose.

Internal Linking — The Most Underused Lever

Every treatment page should link out to:
- Related treatments (Profhilo → Polynucleotides)
- Concern pages (anti-wrinkle → concerns/tired-look)
- Practitioner page
- Location pages
- Relevant blog posts

This is one of the biggest ranking levers in healthcare SEO and almost every clinic fails at it completely.

What Wins Local Search — Summary

Signal Priority
GBP authority (completeness, photos, posts, reviews) 1
Review volume + review wording quality 2
Website speed (mobile) 3
Real, unique location pages 4
Deep treatment pages (1,500+ words, 10 sections) 5
Practitioner trust signals (GMC, photos, credentials) 6
Internal linking (treatment ↔ concern ↔ blog) 7
Topical authority blog clusters 8
Consistent local citations 9
Concern pages (problem-first SEO) 10

Activation Prompt

You are in Aurea Brand SEO mode. This lane targets UK clinic owners searching for growth solutions. Read lane-aurea.md in full. Domain: insights.aureagrowth.co (Cloudflare Worker). Content angle: operator-level intelligence, not generic marketing advice. Camille publishes after John reviews. Run the GEO pass (Workflow E) on every draft before publishing.

Lane: Aurea Brand Content

Lane: Aurea Growth agency brand
Domain: insights.aureagrowth.co (Cloudflare Worker)
Audience: UK aesthetics and dental clinic owners
Owner: John (editorial), Camille (publishing execution)
Priority: High — pre-sells trust behind Mailman outreach
Back to: MASTER.md


Aurea Growth — Content Engine Master Guide

Strategy, system map, weekly workflow, brand rules, and troubleshooting in one place.

Related navigation:
- [[MASTER_GUIDE|Master guide]]
- [[CLAUDE|Agent rules]]
- [[docs/Aurea Growth V5|Aurea Growth product architecture]]
- [[research/content-insights-base|Content insights base]]
- [[aurea-growth-encyclopedia/wiki/index|Aurea Growth encyclopedia]]
- [[memory/project_content_doctrine|Aurea content doctrine]]
- [[workflows/SKILL_ROUTER|Skill router]]


1. The Strategic Logic

Aurea Growth sells a high-trust, high-ticket service to clinic owners. Content exists for one reason: to make every sales conversation easier to win.

SEO rules now in force

  1. Target commercial and transactional keywords only.
  2. Do not create separate pages for synonymous searches with the same buying intent.
  3. Reverse-engineer LLM citations before you outline. If ChatGPT, Gemini, or Perplexity cite listicles, pricing pages, or service pages, match that format exactly.
  4. Use competitor URLs as upgrade targets. The goal is not to be different. The goal is to be the clearest, most decision-useful version of the page already winning.
  5. Treat technical SEO as part of publishing, not a later clean-up step. PageSpeed, schema, sitemap, robots, and image compression are part of the workflow.
  6. Traffic is good when it is intention-led and routed into qualification. Traffic is bad when it is broad, unfiltered, and sent toward an open diary.

Content does three jobs:

  1. Pre-sells trust. A clinic owner who receives a Mailman letter and Googles you should land on 5–10 sharp articles that confirm you understand their world. By the time they get on a call, they don't need convincing — they've already read it.
  2. Builds inbound over time. Bottom-of-funnel SEO content ranks for the searches clinic owners are making when they are close to acting. Slow to kick in (2–4 months), but compounds permanently.
  3. Dual-audience leverage. Every piece serves two audiences simultaneously. Patients read it to understand their treatment. Clinic owners read it and realise this person understands their business. One post, two jobs.

Content is not the primary revenue channel right now. Mailman is. Content makes Mailman work better.

1.1 Skill Layer To Consult

Before writing new content, repurposing assets, or inventing ad copy frameworks from scratch, check the relevant skill-routing and context files first:

  • memory/MEMORY.md -> installed command-style marketing skills and when to use them
  • workflows/SKILL_ROUTER.md -> trigger -> skill -> context mapping
  • memory/project_content_doctrine.md -> non-negotiable Aurea content rules
  • docs/aurea-copywriting-context.md -> Aurea-facing copy rules
  • docs/aurea-ad-creative-context.md -> Meta / Google creative rules

Supporting command-style skill files live in:
- /Users/johntossou/.claude/commands/copywriting.md
- /Users/johntossou/.claude/commands/paid-ads.md
- /Users/johntossou/.claude/commands/ad-creative.md

These are useful for structure and variation frameworks.
They do not override Aurea doctrine.


2. System Map

research/
  content-insights-base.md        ← The brain. All market intelligence lives here.

aurea-content-engine/
  scrapers/blog_writer.py          ← The agent. Reads an outline, writes the post.
  outputs/outlines/                ← The queue. One JSON file = one article.
  outputs/voice_model.json         ← Style reference (sentence length, tone, readability).
  outputs/blog_posts/              ← Where finished articles land.
  config.yaml                      ← Positioning, pillars, approved/forbidden phrases.
  .env                             ← ANTHROPIC_API_KEY lives here.

The flow is linear:

Insight (Mailman / observation / market research)
  ↓
Write an outline (JSON in outputs/outlines/)
  ↓
Run the blog writer
  ↓
Article lands in outputs/blog_posts/
  ↓
Publish to Ghost → repurpose to IG / ad hooks / video

When choosing how to create or repurpose an asset:
- use project_content_doctrine.md for positioning and CTA rules
- use aurea-copywriting-context.md for Aurea-facing copy and operator voice
- use aurea-ad-creative-context.md when the output becomes an ad hook, creative brief, or paid asset

What's built

Component What it does Location
Content insights base Central knowledge store: patient psychology, clinic owner pain, real verbatim language, SEO keyword bank research/content-insights-base.md
Article outline queue JSON files defining each article's structure, editorial truths, and target keywords aurea-content-engine/outputs/outlines/
Blog writer Python script: reads outline, calls Claude, outputs a full blog post aurea-content-engine/scrapers/blog_writer.py
Branded HTML blog preview Full Aurea-branded page preview (Canela font, brand colours, logo, sidebar TOC) aurea-content-engine/outputs/blog_posts/preview_*.html
IG carousel generator Outputs 6 branded HTML slides (1080x1080) from a blog post insight aurea-content-engine/outputs/instagram/
Brand config Voice rules, forbidden phrases, forbidden punctuation, tone guidelines aurea-content-engine/config.yaml

Planned (not yet built)

Component What it does Priority
Ghost blog Hosted at insights.aureagrowth.co — where all articles publish Set up manually (15 min)
Playwright PNG export Screenshots each IG carousel HTML slide to 1080x1080 PNG Build next
Repurposing script Takes a finished blog post and outputs IG copy, video script, and ad hooks in one run Build next
Creatomate video render REST API: sends slide content, receives rendered MP4 Reel Evaluate (~$49/month)
Ghost API auto-post Pushes finished blog post directly to Ghost via Content API After Ghost is live
Buffer scheduling Queues IG posts (PNGs) and video for timed publishing After video pipeline is ready

What's been ruled out

Tool Why
Canva API Enterprise-only, requires ByteDance partnership approval
CapCut API Same — enterprise/ByteDance only
Medium as main hub Splits domain authority, wrong audience signal
LinkedIn (active channel) Not a current priority
Twitter/X automation Low ROI for clinic owner audience

3. One-Time Setup

The engine is installed at aurea-content-engine/. Dependencies are in requirements.txt and were installed in the original build. The voice model (outputs/voice_model.json) is already extracted.

Only thing needed to run: an ANTHROPIC_API_KEY in a .env file inside the content engine directory.

cd "/Users/johntossou/Desktop/Aurea Growth/Claude Code Workflow/aurea-content-engine"
echo "ANTHROPIC_API_KEY=sk-ant-YOUR_KEY_HERE" > .env

Get the key from: console.anthropic.com → API Keys.


4. The Weekly Workflow

One cycle per week. ~90 minutes total.


Step 1 — Pick an insight

Every article starts with a real observation. Not a keyword. Not a topic. An observation about how clinics behave, how patients think, or what goes wrong between ad and booking.

Sources:
- A Mailman audit you just wrote
- A pattern that came up in a sales conversation
- A Reddit thread or market research finding in research/content-insights-base.md
- A client delivery note where something surprised you

The test: can you complete this sentence in one line?

"Clinics are losing money because __."
"Patients feel disappointed because
__."

If yes, that's the article.

Then force the insight through this filter before you write anything:

  • Is the keyword commercial or transactional?
  • Is the search intent distinct from pages we already have?
  • What content format do AI engines already cite for that prompt?
  • Which competitor URL is currently closest to winning that intent?

Step 2 — Create an outline (JSON)

Create a new file in aurea-content-engine/outputs/outlines/. Name it sequentially: 08_your_topic_outline.json.

See brief-template.md for the full field specification.

Content pillars — assign one per article:

Pillar Audience Lead with
The Economics Clinic owner Revenue leak, cost per slot, no-show rate
The Problem Patient The symptom — tired, hollow, overnight ageing
The Fear Patient Risk not yet voiced — pain, botched result, wasted money
The Expectation Patient What actually happens — timelines, post-treatment reality
The Standard Both What good looks like — clinical, conservative, evidence-led

Structural rules:
- Every H2 should address one question a reader would actually have
- H3s go one level deeper — specific, not vague ("The economics of an empty slot vs a no-show" not "Examples")
- editorial_truth is the most important field. Without concrete claims, the output is generic filler
- intent_signature must be unique. If two keywords have the same signature, they belong on one page
- llm_citation_target should be chosen after checking real AI citations, not guessed in isolation
- CTA must always be low-pressure: "book a clinical assessment" or "map your booking flow in 10 minutes" — never "call us today"


Step 3 — Run the blog writer

cd "/Users/johntossou/Desktop/Aurea Growth/Claude Code Workflow/aurea-content-engine"

Generate one specific article:

python3 -c "
from scrapers.blog_writer import BlogWriter

writer = BlogWriter()
writer.load_voice_model()
writer.load_outline('outputs/outlines/01_why_leads_no_bookings_outline.json')
blog_post, analysis = writer.write_blog_post('outputs/outlines/01_why_leads_no_bookings_outline.json')
print('Done. Check outputs/blog_posts/')
"

Generate all articles in the queue:

python3 -c "
from scrapers.blog_writer import BlogWriter
from pathlib import Path

writer = BlogWriter()
writer.load_voice_model()

for outline_file in sorted(Path('outputs/outlines').glob('*_outline.json')):
    print(f'Writing: {outline_file.name}')
    blog_post, analysis = writer.write_blog_post(str(outline_file))
    print('Done.')
"

Test without spending API credits (mock mode):

python3 test_blog_writer_mock.py

Output lands in outputs/blog_posts/ as .md files, ready to add to the Cloudflare blog worker.


Step 4 — Quality check before publishing

  • [ ] Leads with the problem, not the solution
  • [ ] Targets a commercial or transactional intent only
  • [ ] Does not duplicate an existing intent signature
  • [ ] Matches the content format already cited by LLMs for that prompt
  • [ ] Uses specific economic language (cost per slot, revenue per session, no-show rate)
  • [ ] Editorial truths from the outline are actually in the article
  • [ ] Sounds like a consultant, not an agency
  • [ ] No forbidden phrases (search for: cutting-edge, painless, amazing, bespoke, free consultation)
  • [ ] No em dashes anywhere in the copy
  • [ ] CTA says "book a clinical assessment" or "map your booking flow" — never "call us today"
  • [ ] Reading level accessible (Grade 8–9 target)

If the article reads like a generic AI blog, the outline's editorial_truth field needs to be sharper. Add more specific, concrete claims then regenerate.


Step 5 — Publish to insights.aureagrowth.co

Blog runs on a Cloudflare Worker at tools/cloudflare-workers/aurea-insights-blog/worker.js.

Adding a new article:
1. Convert the .md draft to clean HTML (use the existing article in worker.js as the format reference)
2. Add a new object to the ARTICLES array in worker.js with: slug, title, meta, tag, tagSub, date, readTime, toc, body
3. Deploy: cd tools/cloudflare-workers/aurea-insights-blog && npx wrangler deploy

First-time domain setup (one-time):
1. Deploy the worker (step 3 above) — it goes live at a *.workers.dev URL first
2. Cloudflare dashboard → Workers and Pages → aurea-insights-blog → Settings → Domains and Routes → Add custom domain
3. Add insights.aureagrowth.co (requires aureagrowth.co DNS managed in Cloudflare)

Adding a second lane blog (johntossou.com/blog or tossouadvisory.com/articles):
Copy the aurea-insights-blog/ folder, change the name in wrangler.jsonc, swap brand tokens in the CSS, update article data. Same pattern, 15 minutes.


Step 6 — Repurpose (the 1 to many system)

Every published article becomes 4 more assets. Do this immediately after publishing while the thinking is fresh.

Instagram carousel (10 min)

5–7 slides. One idea per slide. No paragraphs.

Slide 1: The problem statement (big text, plain background)
Slide 2: Why it happens (one sentence)
Slide 3: What it costs
Slide 4: What the fix looks like
Slide 5: The principle / takeaway
Slide 6: CTA ("Book a clinical assessment")

Every slide 6 pivots from patient education to clinic owner framing. Patients share slides 1–5. Clinic owners see slide 6 and think: that's me.

Short video hook (10 min)

20–40 second script:

Hook (5 sec): the most jarring claim
Problem (10 sec): why it matters
Pattern (10 sec): "we see this every week with clinics that..."
Exit (5 sec): soft CTA or question

Ad hook (5 min)

Pull the pain from the article into a single line. This becomes a headline test for future Meta ads.

Examples:
- "You don't need more leads. You need better patients."
- "Most lost revenue isn't your ads. It's the hour you're closed."
- "Your diary is full. Your revenue isn't growing. This is why."

LinkedIn post (5 min) — not a current priority, but the format:

Pull the sharpest sentence from the article as the hook. Then 3–4 short lines expanding it. End with a question or a quiet CTA.

Repurposing boundaries

  • Automate draft generation, formatting, and export where it saves time.
  • Keep the insight selection, final framing, and publish/no-publish decision human.
  • Do not auto-post raw RSS summaries to social channels.
  • Do not let repurposing flatten the original editorial truth into generic motivational content.

5. SEO Strategy

Clinic owners don't search "aesthetic marketing agency UK." They search:

  • "why am I getting no shows clinic"
  • "meta ads not working aesthetics"
  • "how to reduce cancellations dental"
  • "why do patients not rebook"

These are the entry points. Every article starts with a real clinic problem, not a keyword.

Teardown content (your unfair advantage)

Mailman audits contain the best SEO content you'll ever produce. The process:

  1. Do the Mailman audit (you're already doing this)
  2. Strip the clinic identity
  3. Keep the insight — the leak, the pattern, the consequence
  4. Publish as an anonymous pattern breakdown

Example:

"Why discount ads and open booking attract the wrong patients"
Inside: "In a recent clinic we analysed..." — then describe the situation, the leak, the fix.

You keep 100% of the insight. Zero of the risk.

LLM takeover workflow

  1. Pick a commercial prompt worth owning.
  2. Search it in Gemini, Perplexity, and ChatGPT.
  3. Record the cited sources and their format.
  4. Choose the weakest current winner.
  5. Build an Aurea page that preserves the winning format but carries sharper economics, stronger operator logic, and clearer qualification thinking.
  6. Publish fast, then monitor citations and branded search behavior.

What AI content needs to rank

AI content is not penalised by Google. Generic content is.

  • Generic: "Here are 5 tips for reducing no-shows"
  • Ranking: "A clinic running £15 Meta ads with no deposit requirement loses £33k/quarter in attended-but-didn't-convert slots"

The editorial_truth field in each outline is what separates these. Specific numbers. Real scenarios. Concrete consequences.


6. Brand Voice Rules

Tone: Direct. Punchy. Pain-focused. No jargon.

Rules:
1. Lead with the problem, not the solution. Reader feels seen before being sold to.
2. Short sentences. Short paragraphs. One idea per line.
3. Say it in the first sentence. No preamble or scene-setting.
4. Write how a sharp operator talks to a peer — not how an agency writes a brochure.

Reference: The Aurea Mailman letters — short observational paragraphs, one claim per sentence, pain named before anything else.

Forbidden phrases: cutting-edge, bespoke, industry-leading, omnichannel, synergy, seamless, holistic approach, in today's competitive landscape, leverage, solutions, painless, amazing, free consultation, call us today.

Forbidden punctuation: Em dash (—). It signals AI-generated copy. Rewrite the sentence instead. Replace with a full stop, comma, colon, or bracket.


7. Adding New Insights to the Knowledge Base

When you learn something new — from a client conversation, a Mailman outcome, a Reddit thread, a sales call — add it to research/content-insights-base.md.

The file is structured in 6 parts:
1. Patient psychology (emotional drivers, language, fears, buying triggers)
2. Clinic owner psychology (pain points, agency trauma, decision triggers)
3. The connecting insight (how both sides are solving the same problem)
4. Operational gaps (product opportunities)
5. Content pillars and copy phrases
6. Content strategy and distribution

Add to the relevant section. If you learn a new verbatim phrase patients use, add it to Part 1. If you discover a new clinic owner objection, add it to Part 2. The richer this gets, the better the articles.


8. Article Queue

# Title Pillar Status
01 Why Your Clinic Is Getting Leads But No Bookings Economics Ready to generate
02 The Real Cost of a No-Show Economics Ready to generate
03 Are You a Sinker or a Sagger? Problem (patient) Ready to generate
04 Why Meta Ads Fail for Aesthetic Clinics Economics Ready to generate
05 The 48-Hour Panic After a Skin Booster Expectation (patient) Blog + HTML preview done
06 How to Choose a Safe Aesthetics Clinic in London Fear (patient) Ready to generate
07 Why Most Clinic Open Days Attract the Wrong Patients Economics Ready to generate
08 Why the Best Aesthetic Clinics Make You Wait Before They Inject Standard Outline ready
09 Why Polynucleotides Won't Fix Your Under-Eye Hollows Fear (patient) Outline ready

Suggested next outlines:
- Why your clinic is paying for the wrong patients (discount ads + open diary + no deposit)
- The real cause of no-shows (it's not patients, it's the system)
- After-hours missed calls: the £33k/quarter revenue leak
- Why open booking systems kill high-ticket treatments
- GHL vs Fresha/ANS/Pabau for qualification flows
- The post-treatment WhatsApp check-in (why most clinics skip it and what it costs)


9. Where Everything Lives

Asset type Location
Market intelligence research/content-insights-base.md
Article outlines aurea-content-engine/outputs/outlines/
Finished blog posts (markdown) aurea-content-engine/outputs/blog_posts/
HTML blog previews aurea-content-engine/outputs/blog_posts/preview_*.html
IG carousel previews aurea-content-engine/outputs/instagram/
Brand config aurea-content-engine/config.yaml
Brand assets (fonts, logo) /Users/johntossou/Desktop/Aurea Growth/Aurea Growth Branding Kit/
Published blog insights.aureagrowth.co (Ghost — to be set up)

10. Connection to Revenue

Mailman letter sent to clinic
    ↓
Clinic owner Googles "Aurea Growth"
    ↓
Lands on 5–10 sharp articles that confirm expertise
    ↓
Replies to Mailman warmer, books call pre-sold
    ↓
Close rate goes up, sales cycle goes down

SEO inbound is a secondary benefit that kicks in after month 3–4 of consistent publishing.

Primary acquisition channel: Mailman. Content is the trust layer behind it.

10.5 What to automate and what to keep human

Automate:
- keyword clustering
- outline scaffolding
- citation-format reminders
- markdown generation
- image/export formatting
- PageSpeed remediation planning

Keep human:
- editorial truth extraction
- final CTA judgment
- publish decision
- repurposing angle selection
- anything that could change clinical positioning or patient trust


11. Troubleshooting

API key error:

ValueError: ANTHROPIC_API_KEY environment variable not set

Create .env in aurea-content-engine/ with: ANTHROPIC_API_KEY=sk-ant-...

Output is too generic:
The editorial_truth field in the outline is too vague. Add more specific claims — real numbers, real scenarios, real consequences. Regenerate.

Output uses forbidden phrases:
Search for: "cutting-edge", "industry-leading", "painless", "amazing", "bespoke", "free consultation". The system prompt already bans these but the model occasionally slips. Edit manually.

File not found for outline:
Make sure the outline filename ends in _outline.json. The generator globs for that pattern.

Activation Prompt

You are in johntossou.com SEO mode. This lane targets hiring managers, adtech/martech founders, and aesthetics marketers. Read lane-johntossou.md in full. Focus: positioning John as a senior adtech/martech consultant with UK aesthetics vertical expertise. Competition is low — content compounds fast. This lane is the fastest ROI per article published.

Lane: John Tossou Personal Brand

Lane: johntossou.com personal brand
Domain: johntossou.com
Audience: Three distinct groups (see below)
Owner: John — strategy, drafts, publishes
Priority: High — job signal, adtech authority, Aurea cross-pollination, GEO footprint
Status: Site exists. SEO content not yet started. Start here.
Back to: MASTER.md


Why This Lane Matters

Three compounding reasons, each valid independently:

  1. Job insurance. With a baby due and Aurea pre-MRR, a content footprint on johntossou.com means hiring managers and headhunters find you before you need them. One well-ranking article on "programmatic advertising UK" or "adtech GTM consultant" puts you in relevant search results passively.

  2. Advisory pipeline. Founders and execs Googling UK market entry or adtech consulting find you. This is the inbound layer for JCT Advisory and Tossou Advisory referrals that doesn't require LinkedIn activity.

  3. Aurea authority transfer. Every article about aesthetics clinic marketing, GHL workflows, or Google Ads for clinics strengthens Aurea's positioning by proxy. Prospects who Google John Tossou before signing find proof he knows what he's doing.


Audiences

Three audiences, one site — different articles serve different groups.

Audience What they search What they need to see
Hiring managers / recruiters "adtech consultant London", "programmatic advertising UK", "GTM strategy UK" Track record, specific platforms, outcomes
Adtech/martech founders considering UK expansion "UK market entry adtech", "UK adtech GTM", "programmatic UK landscape" Market knowledge, operator credibility, practical intelligence
Aesthetics marketers / clinic owners "GoHighLevel aesthetics UK", "Google Ads aesthetics clinic", "clinic automation UK" Practitioner proof, specific results, actionable frameworks

Keyword Universe

Tier 1 — Job signal (low competition, high hiring-manager intent)

These rank fast because almost no one is writing about them at a personal brand level.

Keyword Intent Notes
adtech consultant London Commercial Direct job/advisory signal
programmatic advertising consultant UK Commercial Very few competitors at personal brand level
UK market entry adtech Informational/commercial Founder-audience entry point
GTM strategy UK adtech Commercial JCT Advisory angle
GoHighLevel aesthetics clinic Informational Ranks fast — almost no content exists
GHL workflows clinic UK Informational Same — extremely low competition
Google Ads aesthetics clinic UK Informational/commercial Direct Aurea service proof

Tier 2 — Authority and cross-pollination

Keyword Intent Notes
clinic automation UK Informational Bridges adtech and aesthetics audiences
aesthetics clinic marketing UK Commercial Aurea proof by proxy
GHL vs Fresha comparison Informational Very searchable, low competition
GoHighLevel vs booking systems aesthetics Informational Long-tail, zero competition
programmatic advertising UK agencies Commercial Market overview content
adtech UK landscape 2026 Informational Authority positioning

Tier 3 — GEO targets (optimise for AI citation)

These are prompts people are already asking AI tools. Rank here = cited in ChatGPT/Perplexity answers.

  • "What is the best CRM for aesthetics clinics?"
  • "How to reduce no-shows in a UK aesthetics clinic"
  • "GoHighLevel for medical aesthetics — is it worth it?"
  • "UK adtech market entry — what to know"
  • "How to set up Google Ads for an aesthetics clinic"

Target Pages to Build

Build in this order. Each page targets one intent cluster.

Phase 1 — Foundation (do these first, unlocks job signal)

Page URL Primary keyword Notes
Home / About / John Tossou adtech consultant Bio, proof points, three lanes clearly stated
Adtech / GTM consulting /adtech-consulting adtech consultant London JCT Advisory offer, Corvidae / Pixis proof
Aesthetics marketing /aesthetics-marketing aesthetics clinic marketing UK Aurea proof, NK / Elhama / BrightSmile outcomes

Phase 2 — Blog articles (compounds over time)

Article Primary keyword Pillar Priority
Why Google Ads beats Meta for premium aesthetics clinics Google Ads aesthetics clinic UK Practitioner High
GoHighLevel vs Fresha: which is better for UK aesthetic clinics GHL vs Fresha aesthetics Comparison High
How we reduced cost-per-consultation for a North London aesthetics clinic aesthetics clinic Google Ads case study Proof High
What adtech companies get wrong about UK market entry UK market entry adtech Authority High
GoHighLevel setup guide for aesthetics clinics (the non-technical version) GoHighLevel aesthetics clinic setup Practitioner Medium
Why clinic automation starts with intake, not marketing clinic automation UK Practitioner Medium
The GHL workflow stack that actually works for a solo aesthetics practitioner GHL workflows clinic Practitioner Medium

Content Angles

These angles apply across all articles on johntossou.com.

Practitioner teaching practitioners. Every article is written by someone who has run real campaigns and seen real outcomes — not an agency pitching services. "Here is what happened, here is the exact number, here is what I would do differently."

Specificity over breadth. A post titled "How we cut CPA from £467 to £89 for a North London under-eye treatment clinic over 90 days" is worth 10 generic "how to run Google Ads" posts for both SEO (low competition) and GEO (AI models love specific, citable numbers).

Comparison content. "GHL vs Fresha", "Google Ads vs Meta for aesthetics", "Appointwise vs manual triage" — comparison posts rank fast, get cited by AI tools, and are directly useful to the Aurea prospect audience.

UK-specific operator intelligence. Most adtech and clinic marketing content is US-written. CQC compliance, GHL setup for UK numbers, UK carrier rules for WhatsApp Business — none of this exists in useful form. First-mover advantage is real here.


Brand Voice for johntossou.com

Different to Aurea's agency voice. Slightly more personal — this is John as an operator, not as an agency brand.

Tone: Direct. Specific. No preamble. Practitioner-to-practitioner.

Lead with the outcome or the problem: "We cut cost per consultation by 81% in 90 days. Here is exactly how." Not "In this article I will share..."

Permitted first-person: "I built", "we ran", "I tested", "here is what I found" — this is a personal brand, first-person is appropriate and builds trust.

Forbidden (same as Aurea): Em dashes, cutting-edge, bespoke, industry-leading, seamless, leverage, solutions.

No agency speak. This is not a pitch document. No "if you're looking to transform your clinic's performance..." — just direct practitioner language.


CTA Structure

Every article on johntossou.com routes to one of three destinations depending on the reader's likely intent:

Reader type CTA Destination
Hiring manager "See my background" LinkedIn or CV page
Adtech founder / exec "Talk about UK market entry" Contact form (JCT Advisory angle)
Clinic owner / aesthetics marketer "See what Aurea Growth does" aureagrowth.co

Use the reader's intent to pick the right CTA. Don't use the same CTA on every page.


Publishing Setup

Platform: johntossou.com (existing site — check CMS before deciding on blog setup)

Blog path: /blog or /insights — keep it separate from service pages

Schema to add on every post:
- Article schema (author: John Tossou, credentials listed)
- Organization schema (John Tossou / JCT Advisory)
- BreadcrumbList

Meta format:
- Title: [Specific outcome or claim] | John Tossou
- Description: 145-155 characters, lead with the specific number or claim

No stock photos. Real screenshots, real campaign data (anonymised where needed), real clinic dashboards.


Article Brief Template

Use brief-template.md for every article. Set lane: johntossou and pick the right audience from the three groups above.


Progress Tracker

Page / Article Status Published URL Notes
Home / About Not started
Adtech consulting page Not started
Aesthetics marketing page Not started
Google Ads vs Meta for aesthetics Not started
GHL vs Fresha comparison Not started
NK case study (Google Ads + CPA) Not started Anonymise client details
UK market entry adtech post Not started

Activation Prompt

You are in Tossou Advisory SEO mode. Low priority lane. Read lane-tossou-advisory.md before touching anything. Domain: tossouadvisory.com. Audience: French-speaking Series B CEOs/CFOs. Owner: John + Dan. Only produce content here when explicitly asked — do not generate unprompted.

Lane: Tossou Advisory

Lane: Tossou Advisory brand
Domain: tossouadvisory.com
Audience: French-speaking Series B CEOs, CFOs, CMOs, founders considering UK-France expansion
Owner: John (commercial content), Dan (finance content)
Priority: Low — narrow audience, slow compounding, no time pressure
Status: Site exists. SEO content not started. Start after johntossou.com Phase 1 is live.
Back to: MASTER.md


Why This Lane Is Different

Every other lane targets English-speaking, Google-searchable audiences. This lane is French-speaking B2B with very specific needs. The audience is small but extremely high-value — one inbound lead from this lane is worth more than 50 aesthetics clinic leads.

Two implications:
1. Volume does not matter. Rank for 5 highly specific French-language queries and you own the lane. You do not need 50 articles.
2. Compounding is slow. French-language SEO for UK advisory services is a 12-24 month play. Do not expect organic traffic before then. The content exists to be found when someone is already in the research phase — not to generate cold volume.

Start building this lane only after johntossou.com has at least 3 articles live.


Audience

A French-speaking Series B CEO or founder who is:
- Considering UK expansion (from France, Belgium, Switzerland, or Canada)
- Navigating a commercial or financial decision that has UK implications
- Skeptical of generic consultants who pitch strategy decks without accountability
- Looking for an independent view that isn't paid by the agency or vendor they're evaluating

The site's positioning is buyer-side only. No commissions, no referral fees, no pay-to-play. This is the differentiator and it must come through in every piece of content.


Keyword Universe

French-language (primary)

Keyword Intent Notes
expansion marché UK Informational/commercial High-intent entry point for founders
entrée marché royaume-uni Informational Alternative phrasing for same intent
consultant expansion UK France Commercial Direct advisory search
direction commerciale externalisée UK Commercial JCT Advisory service directly
direction financière externalisée UK Commercial Dan's service directly
advisory UK France Commercial Bilingual search — some leads search in English
développement commercial UK Commercial Broader but relevant
conseil expansion internationale startup Commercial Startup audience, Series A-B
marché UK adtech Informational Niche — adtech founders only

English-language (secondary — French founders often search in English for UK content)

Keyword Intent Notes
UK market entry France Informational/commercial French companies Googling in English
UK expansion consultant France Commercial Direct advisory intent
fractional CCO UK France Commercial Some audience uses this term
UK commercial development France Commercial Less competitive than "UK market entry"

Target Pages

Very few pages needed. The audience is narrow. Quality over volume.

Phase 1 — Foundation

Page URL Language Primary keyword Notes
Home / French consultant UK-France The "Décider avant de dépenser" positioning page
Decision Diligence /diligence-de-decision French conseil décision UK Format Court explanation
Externalised Direction /direction-externalisee French direction commerciale externalisée Format Continu explanation
Contact /contact French Simple, no pitch

Phase 2 — 3-5 long-form articles

Article Language Primary keyword Pillar
Les 5 erreurs des entreprises françaises qui entrent sur le marché UK French expansion marché UK erreurs Education
Comment choisir un partenaire commercial UK sans se faire rouler French choisir partenaire commercial UK Comparison/authority
UK vs France: ce que les fondateurs ne savent pas avant d'y aller French différences marché UK France Education
Ce que coûte vraiment une entrée marché UK mal préparée French coût expansion UK Economics

Content Rules for This Lane

Language: French primarily. Use formal but direct French — this is a senior executive audience, not startup Twitter. Do not use anglicisms unnecessarily, but do not avoid English terms that are standard in the business world (KPI, go-to-market, due diligence).

Tone: Sober, senior, buyer-side. The site language rules from TOSSOU-ADVISORY-SITE-STRATEGY.md apply to all content.

Vocabulary to use:
- Direction externalisée
- Direction commerciale / direction financière
- Binôme senior
- Mission continue
- Diligence de décision
- Présence terrain
- Buyer-side
- Indépendance

Vocabulary to avoid:
- Fractional CFO, fractional CRO, fractional CMO (US startup language, wrong register)
- Growth advisor, unlock your potential (overblown)
- Transformational, world-class, elite (same)
- Em dashes in French or English (same rule as all lanes)

GEO note: French-language GEO is less competitive than English. Well-structured French content with entity clarity, FAQ schema, and direct answers can appear in French AI Overview results faster than equivalent English content. Prioritise the Quick Summary block and structured FAQ on every page.


CTA Structure

One CTA only: contact for an initial conversation.

No pricing on the site (per site strategy). No self-service booking. The CTA is always:

"Prenez contact pour un premier échange."

Route to a contact form or email. No calendly-style booking — too informal for this audience.


Publishing Setup

Platform: Cloudflare Worker (existing — see tools/cloudflare-workers/johntossou-advisory-landing/)

Blog path: Add /insights or /articles section when Phase 2 content is ready

Schema on every page:
- Organization schema (Tossou Advisory / JCT Advisory LTD)
- WebPage schema
- French-language hreflang tag

Meta format (French):
- Title: [Service or claim] | Tossou Advisory (max 60 characters in French)
- Description: 145-155 characters in French


Article Brief Template

Use brief-template.md for every article. Set:
- lane: tossou-advisory
- language: fr
- audience: French-speaking Series B CEOs / CFOs


Progress Tracker

Page / Article Status Published URL Notes
Home Exists (Cloudflare Worker) tossouadvisory.com Review SEO meta and schema
Decision Diligence page Not started
Externalised Direction page Not started
Les 5 erreurs des entreprises françaises Not started
Comment choisir un partenaire commercial UK Not started

Activation Prompt

You are in AI Systems mode. Read docs/clinic-omnichannel-ai-stack-playbook.md and docs/vapi-ghl-best-practices-2026-03-31.md before touching any setup. Stack: GHL + Vapi + Appointwise + n8n + WhatsApp Business API. Critical: Vapi fires multiple webhook events per call — filter by end-of-call-report only. GHL loop lock prevention is mandatory. Vapi native GHL OAuth connector is broken as of 2026-05 — use the Cloudflare webhook bridge.

Clinic Omnichannel AI Stack — Decision Playbook

Last updated: 2026-05-06
Source client: Elhama Beauty Boutique (setup April 2026)

This document captures every decision, constraint, failure mode, and architecture pattern from building a full omnichannel AI system for a UK aesthetics clinic. Use it at the start of every new client engagement to avoid re-learning the same lessons.


What This Stack Does

A single system that:
- Handles all inbound messages (WhatsApp, Instagram DM, Facebook DM, SMS) via AI triage
- Handles all inbound phone calls via an AI voice agent using the clinic's own cloned voice
- Stores every interaction in a CRM, logs calls, extracts contact details automatically
- Lets the clinic owner see everything in one inbox without touching a phone


Stack Components

Tool Role Required?
GHL (GoHighLevel / LeadConnector) Unified inbox, CRM, workflows, WhatsApp Business API, Instagram/Facebook DMs, contact management Always
Appointwise AI triage agent for chat channels (WhatsApp, Instagram, Facebook, SMS). Sits on top of GHL. Yes for chat AI
Vapi AI voice agent for inbound phone calls. Handles call, collects details, generates transcript + summary Yes for voice AI
Twilio Phone number infrastructure. Buy numbers here. Vapi connects via Twilio. Yes (via Vapi)
ElevenLabs Voice cloning. Clone the owner's voice and assign it to the Vapi agent Yes if voice clone needed
n8n Automation bridge for complex logic between systems Optional — not needed for v1
Meta Business Manager Required to connect Instagram DMs and Facebook DMs to GHL Yes if using Meta channels

Channel Coverage — What Is and Is Not Possible

Channel AI Triage Notes
WhatsApp messages Yes Via GHL WhatsApp Business API + Appointwise
Instagram DMs Yes Via Meta Business Portfolio connected to GHL
Facebook DMs Yes Via Meta Business Portfolio connected to GHL
SMS (inbound) Yes Via GHL LC Phone number
Inbound phone calls Yes Via Vapi + carrier forwarding
WhatsApp voice calls No Hard technical limit — see below
Outbound SMS follow-up post-call Yes, with conditions Requires LC Phone number with SMS capability

WhatsApp Voice Calls — Hard Limit

When a number is connected to WhatsApp Business API, it cannot receive WhatsApp in-app voice or video calls. The call shows as failed or dropped on the caller's screen. GHL sees nothing — no notification, no contact record created. This cannot be fixed technically.

Mitigation:
1. Add a note to the business WhatsApp bio: "For calls, please use the regular phone number. For messages, WhatsApp us here."
2. Send a broadcast to existing contacts before go-live explaining the change
3. First-message GHL automation: when a new contact messages, the opening reply includes "If you tried to call via WhatsApp and it didn't connect, please call [number] directly."

Accept this as a trade-off. The client must understand it before the number goes live on the API.


The "Same Number" Problem

Every clinic wants one number that does everything. Here is the reality:

What they want What is actually possible
07501 914514 handles WhatsApp messages Yes — connect to GHL WhatsApp Business API
07501 914514 receives carrier calls and Vapi picks up Yes — set carrier forwarding to Vapi number
07501 914514 receives WhatsApp voice calls No — hard limit, see above
SMS sent back to caller from same Vapi number Depends on number capability — see below
One number for everything Achievable for 3 of the 4 above

Number architecture in practice:

The client keeps their existing mobile number (e.g. 07501 914514). It gets two roles:
1. Connected to GHL WhatsApp Business API — handles all messages
2. Carrier-level call forwarding to a separate Twilio/Vapi number — handles all calls

The Vapi number is a separate UK number (e.g. +44 1923 311259) bought in Twilio. Callers dial the clinic number, get forwarded, and Vapi answers.

Client dials 07501 914514
       |
       ├── Carrier-level forwarding → +44 1923 311259 (Vapi)
       |         Vapi answers, collects details, logs to GHL
       |
       └── WhatsApp message → GHL inbox → Appointwise AI triage

Call Forwarding — Unconditional vs Conditional

Two options. Decide with the client.

Type Behaviour Use case
Conditional (busy / no answer) Vapi picks up only if the owner doesn't Owner wants to answer sometimes
Unconditional (always forward) Vapi always answers, owner never picks up Owner is always working and cannot answer

For clinics like Elhama: unconditional is almost always the right call. Owner-operators doing hands-on treatments physically cannot break to take calls.

Unconditional forwarding dial codes (UK carriers):
- All carriers: **21*+441923311259# (replace with actual Vapi number)
- To cancel: ##21#

Confirm the client's carrier before go-live — codes vary slightly.


Voice AI — GHL Native vs Vapi

Option Status Notes
GHL Native Voice AI Unreliable in production (tested April 2026) Call forwarding failed in production on tested GHL numbers. Not ready for live client use. Review again after 30+ days.
Vapi Reliable, production-tested Preferred for all live client voice work. Connects via Twilio. Full transcript, recording, structured data extraction.

Decision: always use Vapi for voice in v1. Revisit GHL native at 30 days.


Voice Cloning with ElevenLabs

When to do it: at the first in-person meeting, once the client has seen the system and trusts it enough to want their voice on it.

Process:
1. Client reads a 2-minute script (natural, conversational — not a formal read)
2. Record audio (phone voice memo is fine)
3. Upload to ElevenLabs → Instant Voice Clone → name it clearly
4. Copy the voice ID
5. PATCH the Vapi assistant: {"voice": {"provider": "11labs", "voiceId": "VOICE_ID"}}

What to expect: the clone will not be perfect on the first attempt. The client may describe it as "too fast" or "too English" — this is a prompt issue as much as a voice issue. Soften the system prompt alongside the voice change.

System prompt tone for clinic voice agents:
- Warm, unhurried, conversational UK English
- One question at a time — never stack questions
- Use connectors: "Of course", "No worries at all", "That sounds lovely", "Absolutely"
- Frame photo review / qualification as a caring personal touch, not a barrier
- Never invent pricing, availability, or clinical advice


Vapi → GHL Integration

Vapi does not natively sync with GHL. The bridge is a GHL Inbound Webhook workflow.

Architecture

Vapi call ends
    ↓
Vapi fires end-of-call-report webhook → GHL Inbound Webhook trigger
    ↓
GHL workflow runs:
  1. Create/Update Contact (phone number)
  2. Log External Call (direction, status, recording URL)
  3. (optional) Send SMS / WhatsApp follow-up

Structured Data Extraction

Vapi can extract structured fields from every call automatically. Configure via analysisPlan.structuredDataSchema on the assistant:

{
  "analysisPlan": {
    "structuredDataSchema": {
      "type": "object",
      "properties": {
        "callerName": {"type": "string"},
        "callerEmail": {"type": "string"},
        "callerPhone": {"type": "string"},
        "treatmentInterest": {"type": "string"}
      }
    },
    "structuredDataPrompt": "Extract caller name, email, phone number stated during call, and treatment interest. Leave blank if not mentioned."
  }
}

These fields appear in the webhook payload at message.analysis.structuredData.* and can be mapped directly in GHL workflow actions.

GHL Workflow — Critical: Loop Lock Prevention

Problem: Vapi fires multiple webhook events per call (status-update, transcript, assistant-request, end-of-call-report — typically 6–10 events per call). If all hit the GHL workflow, it runs 10+ times per call. GHL detects this as a loop (threshold: 50 workflow starts per contact in 30 minutes) and auto-locks the workflow to Draft. Even a NO-branch exit counts as a start, so an If/Else filter inside GHL alone is not enough once test volume builds.

Fix — two steps, in this order:

Step 1 (do first — eliminates the problem at source): Vapi dashboard → your assistant → Advanced → Server Messages → set to ["end-of-call-report"] only. Vapi stops sending all intermediate events. GHL never sees them. This must go in before the workflow is republished.

Note: serverMessages filter only works on Vapi Assistants — not Vapi Workflows.

Step 2 (secondary safeguard): Add an If/Else branch as the first action after the Inbound Webhook trigger:
- Condition: {{message.type}} equals end-of-call-report
- Yes branch: all actions (Create Contact, Log Call, field mappings) run here
- No branch: workflow ends immediately

Never publish the GHL webhook workflow without both fixes in place.

If the workflow gets loop-locked: contact GHL support via live chat and say "Our inbound webhook workflow was auto-locked to Draft due to loop detection. We have identified the root cause and have a fix ready. Please unlock the workflow so we can re-publish it." GHL will unlock it manually — their senior support team can do this. Do not attempt to republish before the Vapi serverMessages filter is set.

GHL Workflow — Field Mapping Reference

Confirmed working paths. Do NOT use common wrong guesses — they silently map nothing.

GHL Field Vapi Payload Path Notes
Contact Phone {{message.call.customer.number}}
Contact First Name {{message.analysis.structuredData.callerName}} Requires structuredDataSchema on assistant
Contact Email {{message.analysis.structuredData.callerEmail}}
Custom — treatment interest {{message.analysis.structuredData.treatmentInterest}}
Custom — call summary {{message.summary}}
Custom — transcript {{message.artifact.transcript}} NOT message.transcript — that path does not exist
Custom — recording URL {{message.artifact.recording.recordingUrl}} NOT message.recordingUrl or message.artifact.recordingUrl
Custom — call outcome manual dropdown no direct Vapi field — set after human review
Log Call — Direction inbound (hardcoded)
Log Call — From Vapi UK number (hardcoded, e.g. +441923311259)
Log Call — To {{message.call.customer.number}}
Log Call — Status completed (or map from {{message.endedReason}})
Log Call — Attachment (recording) {{message.artifact.recording.recordingUrl}} same corrected path as above

Do not map message.durationSeconds — this field does not exist in the Vapi end-of-call-report payload. Derive duration from call.startedAt / call.endedAt if needed later.

Re-entry Setting

Set the workflow re-entry to Allow every time. Every call must run the workflow, not just the first one per contact.


SMS Follow-up After Call

Constraint: UK Twilio local numbers often do not support SMS. Always check the number's capabilities in Twilio before assuming SMS is available. In the Configure tab, if you see "Messaging configuration is unavailable for this phone number" — SMS is not possible from that number regardless of GHL settings.

Options:

Option When to use
GHL LC Phone number (separate number) Fastest to set up. SMS comes from a different number but message identifies the clinic.
Twilio Connect (connect existing Twilio account to GHL LC Phone) Preferred for future clients. Lets GHL use numbers from your Twilio account for SMS. Requires GHL to confirm whether it conflicts with Vapi using the same number simultaneously.
WhatsApp template message post-call Cleanest option. Once the client's number is on WhatsApp Business API, send a WhatsApp follow-up from their own number. Requires a pre-approved message template. Available after go-live.
Full number migration to GHL Avoid. Moves the number out of your Twilio account, breaking Vapi.

Recommended for v1: skip SMS entirely. Add WhatsApp follow-up as a post-go-live workflow action using the client's main number.


Twilio Number Buying — What to Check

Before buying a Twilio number for any client:

  1. Capabilities — confirm Voice AND SMS are listed under Capabilities in Twilio console. UK local numbers (01xxx / 02xxx) often do not support SMS. UK mobile-style numbers (07xxx) usually do.
  2. Regulatory bundle — UK numbers require a Regulatory Bundle with an approved address. Create this before buying if it doesn't already exist.
  3. Messaging capability — after buying, check the Configure tab. If "Messaging configuration is unavailable" appears, you cannot send SMS from that number.

Meta / Instagram / Facebook Setup

What is needed

  • Client grants access to their Meta Business Portfolio (Business Manager)
  • Facebook Page must be connected to the portfolio
  • Instagram account must be switched to Professional and linked to a Facebook Page
  • GHL must be approved as a messaging integration in Meta Business Suite
  • All accounts must show "Connected" in GHL → Settings → Integrations

Critical visibility rule for GHL

For Meta-connected channels, Business Portfolio ownership alone is not enough.

The final gate that controls which Pages / Instagram accounts are actually visible to GHL is usually the Facebook login that authenticated LeadConnector:

Facebook -> Settings & privacy -> Business integrations -> LeadConnector -> View and edit

This is where the connected Facebook user can expose or hide the assets available to GHL.

If an account looks correctly connected in Meta but still does not appear in GHL:
1. Log into the exact Facebook user that connected LeadConnector
2. Open Settings & privacy -> Business integrations
3. Open LeadConnector -> View and edit
4. Confirm the correct Pages / Instagram accounts are enabled
5. Reconnect or refresh the Facebook/Instagram connection inside GHL if needed

Treat this as a first-line debug step on every clinic setup.

Common blockers

Blocker Fix
Instagram shows "Login needed" in Meta Client must log into Instagram and reconnect the account
2FA sent to an old/unavailable phone Find which phone holds the 2FA. That person must be physically present with the device to complete the connection.
Business partner's account linked to wrong number Same — the account holder must be present with the registered device
Instagram not linked to a Facebook Page Switch to Professional account first; Instagram prompts to create or link a Page during that process

Multi-account setup (e.g. business partner with separate Instagram)

If a client has a business partner with a separate Instagram driving traffic:
1. Add the partner's Instagram to the client's Meta Business Portfolio (Business Settings → Instagram Accounts → Request Access)
2. Connect as a second Instagram channel in GHL
3. The same Appointwise agent handles both — the partner does not need to do anything operationally

Requires the partner's agreement and Meta page access.

Important: even after the Page + Instagram are correctly linked in Meta, the second account may still stay invisible in GHL until the Facebook user who connected LeadConnector enables that asset in Business integrations -> LeadConnector -> View and edit.


WhatsApp Business API Registration

Pre-registration requirements

  • Meta Business Portfolio must be verified. Go to business.facebook.com → Settings → Security Center → Business Verification. Status must show Verified before GHL can register any number. Verification takes 2–5 business days after submitting documents.
  • Correct legal name in Meta. The legal business name entered in Meta Business Manager must match the Companies House certificate exactly — including "Limited." Any mismatch causes rejection.
  • Full address required. The address field in Meta Business Manager must contain the full registered address, not just "United Kingdom."
  • Primary document for UK businesses: Certificate of Incorporation from Companies House. Free to download. Covers both legal name and registered address. A utility bill alone is not accepted for the legal name — it can only supplement for address/phone verification.
  • Identity verification: Meta also requires a personal ID from the company director to complete verification. Passport is preferred. This must be done in person — the director must be physically present to upload their ID.
  • The phone number must be in an active SIM on the day of registration (Meta sends an OTP via SMS or voice call).

The number must be released from the WhatsApp Business App first

A phone number cannot be registered on the WhatsApp Business API while it is active on the WhatsApp Business App. This is the most common cause of the "account locked" error in GHL.

Fix: the client must delete their WhatsApp Business App account from within the app before the API registration can proceed. Uninstalling the app alone is not sufficient — the account deletion must happen inside the app.

Steps:
1. Open WhatsApp Business App on the client's phone
2. Settings → Account → Delete my account → confirm
3. Wait 5 minutes for Meta's servers to deregister the number
4. Then proceed with GHL registration

Important: warn the client that this creates a downtime window of approximately 20–30 minutes. Plan it for a quiet period — not mid-clinic.

GHL registration sequence (embedded signup via 360dialog)

  1. GHL Settings → WhatsApp → Connect a new number
  2. Log in with the Facebook account that has Business Manager Admin access to the Meta Business Portfolio
  3. Select the correct business portfolio
  4. Enter the phone number in international format: +447XXXXXXXXX — never with a leading 0
  5. Choose Voice Call for OTP delivery (more reliable than SMS for UK O2 numbers)
  6. Client receives call on their phone, reads out the 6-digit code
  7. Enter the code in GHL
  8. Set display name (trading name the client wants customers to see)
  9. Test immediately — send a message to the number from a separate phone and confirm it appears in the GHL inbox

What the client loses when the number goes on the API

  • WhatsApp in-app voice and video calls (hard technical limit — see above)
  • The number is no longer usable on the WhatsApp Business App

What the client gains

  • Unified inbox in GHL — all WhatsApp messages centralised
  • AI triage via Appointwise
  • Broadcast campaigns to opted-in contacts
  • Automated follow-up workflows

Common errors and fixes

Error Cause Fix
"Account locked" Meta Business Portfolio not verified, OR number still active on WhatsApp App Check verification status in Security Center first; if verified, ensure client has deleted WhatsApp App account
"Number already registered" Account deletion not fully processed Wait 10 more minutes, retry
OTP not arriving SMS blocked by carrier Switch to Voice Call option
Documents rejected Legal name mismatch between Meta and Companies House Check exact name — must include "Limited" if that is the registered name

One-time WhatsApp history import: during setup, GHL supports importing up to 6 months of WhatsApp Business history. This is a one-time process — do it at setup, not after.


Contact Data Migration

Before connecting any live number:
1. Export contacts from all devices (iPhone via iCloud → .vcf, Android via Google Contacts)
2. Merge and deduplicate
3. Import into GHL subaccount

Do not migrate the live number until the contact import is done. Some CRMs treat number connection as a trigger for history import — doing it in the wrong order risks losing data.


Go-Live Order

Always follow this sequence. Do not shortcut it.

  1. Export and import contacts into GHL subaccount
  2. Build and test the AI triage agent (Appointwise) on a separate test WhatsApp number — not the live number
  3. Run 3-7 days internal stress testing on the test line
  4. Refine AI tone, photo timing, pricing handling, and handoff rules from test chats
  5. Set up and test Vapi voice agent independently — call the Vapi number directly
  6. Validate Vapi → GHL webhook end to end (contact created, call logged, structured data populated)
  7. Connect Instagram and Facebook DMs — confirm Meta permissions first
  8. Register live WhatsApp number on GHL only when test line is stable
  9. Set carrier call forwarding to Vapi number only after WhatsApp is live and tested
  10. Run full end-to-end test across all channels
  11. Go live — AI qualifies, human books manually for v1
  12. Set up Stripe recurring billing — only charge when full system is live

Appointwise Setup Notes

  • One Appointwise agent can handle multiple channels in the same GHL subaccount (WhatsApp, Instagram, Facebook, SMS)
  • Trigger pattern for v1: Customer Replied → apply AW-Active-[ClientName] tag
  • Stop logic at photo / qualification stage: handle in CTA and prompt instructions, not a separate trigger
  • Fixed prices, from prices, and consultation-led pricing all handled in the prompt and knowledge base
  • Multilingual: instruct the AI to mirror the lead's language

Billing Setup

  • Never start billing before the full system is live and tested
  • Agreed billing trigger: all channels live, tested, and client has seen the system working
  • Standard: Stripe recurring, £250/month, auto-debit
  • Keep a clear written record of the agreed go-live date and billing start date

Common Failure Modes

Failure Root Cause Fix
GHL workflow loop-locked Vapi fires 6–10 events per call; GHL counts each workflow start toward the 50/30min lock threshold Step 1: set Vapi serverMessages to ["end-of-call-report"] only (eliminates events at source). Step 2: add {{message.type}} = end-of-call-report If/Else filter as first GHL action. Both required.
Workflow re-locks after republish Vapi serverMessages not set before republishing — GHL still gets hit with all intermediate events Set Vapi serverMessages filter FIRST, then republish GHL workflow. Order matters.
Transcript or recording URL not mapping Using wrong payload paths (message.transcript, message.recordingUrl) Use {{message.artifact.transcript}} and {{message.artifact.recording.recordingUrl}} — the outer artifact wrapper is required
Contact created but name/email missing Structured data not configured on Vapi assistant Add analysisPlan.structuredDataSchema to Vapi assistant, map fields in GHL Create Contact action
SMS action fails in GHL No LC Phone number in subaccount, or Twilio number doesn't support messaging Check Twilio capabilities first; buy LC Phone number or use WhatsApp template instead
Vapi not answering calls Server URL not set on Vapi assistant PATCH {"serverUrl": "GHL_WEBHOOK_URL"} on the Vapi assistant record
Instagram DMs not routing Instagram account not connected or shows "Login needed" Client must reconnect Instagram login in Meta Business Manager
2FA blocks Meta connection 2FA registered to an unavailable phone (old number, old device) Find who holds the registered number; that person must be present with the device
Voice agent sounds rushed / too formal Default voice + generic prompt Clone client's voice via ElevenLabs; rewrite prompt with warm UK connectors, one question at a time
WhatsApp calls dropping silently Number on WhatsApp Business API — cannot receive WA calls Explain to client, add bio note, send pre-go-live broadcast
Carrier forwarding not working Wrong dial code for carrier, or number not on compatible carrier Confirm carrier, get exact code, test with a second phone before go-live

Vapi + GoHighLevel Best Practices

Plain-English reference for what we learned while testing Vapi with GoHighLevel / LeadConnector.

Last updated: 2026-04-07


Production Findings — Elhama Beauty (2026-05-04)

This section documents what was discovered and fixed during the live Elhama Beauty Vapi → GHL setup. These are production-validated findings, not theory. Apply them to every future client using this stack.


Critical: Vapi fires multiple webhook events per call

By default, Vapi sends every event type to the server URL — not just the final end-of-call-report. Events fired per call include:

  • assistant-request
  • status-update
  • transcript
  • function-call
  • hang
  • end-of-call-report

All fire to the same URL within the same second. GHL runs the workflow once per event. On a single call, the same contact gets enrolled 6–10+ times in 2 seconds. GHL's loop detection locks the workflow at 50 starts per contact within 30 minutes.

This will happen on every new Vapi → GHL client if not fixed before the first test call.


Fix 1 — Set serverMessages on the Vapi assistant (do this first)

The serverMessages field on the Vapi Assistant object controls which event types fire to the server URL. Set it to only end-of-call-report before pointing any GHL workflow at the assistant.

"serverMessages": ["end-of-call-report"]

Via API:

curl -X PATCH "https://api.vapi.ai/assistant/{assistantId}" \
  -H "Authorization: Bearer {VAPI_PRIVATE_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"serverMessages": ["end-of-call-report"]}'

Via dashboard: Assistant → Advanced → Server Messages → select end-of-call-report only.

Important: serverMessages only works on Vapi Assistants. It does not work on Vapi Workflows. If using a Vapi Workflow, filter at a middleware layer (n8n, Make, or a small relay script) before the payload reaches GHL.


Fix 2 — GHL loop lock mechanics

GHL locks a workflow when it reaches 50 starts for the same contact within 30 minutes. "Starts" counts every workflow entry — including ones that exit immediately via a NO branch on an If/Else filter. The filter inside GHL reduces processing but does not prevent the lock. The only clean fix is suppressing events at the Vapi level via serverMessages.

The If/Else filter on message.type is still worth adding as a secondary safeguard, but it is not a substitute for serverMessages.


Fix 3 — GHL mapping reference must be set before building actions

When creating a new Inbound Webhook workflow in GHL, GHL requires a mapping reference payload before any field variables are available in the workflow builder. Without it, all mapped fields return empty and Create Contact errors with "no value was found for any of the mapped fields."

Correct order:
1. Create the workflow and copy the webhook URL
2. Send a sample end-of-call-report payload to that URL (via curl or a test call)
3. Go back to the trigger settings → Mapping Reference → Check for new requests → select the payload
4. Only then build the action steps

If you build actions before setting the mapping reference, the variable picker will either be empty or suggest incorrect paths. Re-select the mapping reference and re-map each field.

Sample payload to send for mapping reference setup:

curl -X POST "{GHL_WEBHOOK_URL}" \
  -H "Content-Type: application/json" \
  -d '{
    "message": {
      "type": "end-of-call-report",
      "endedReason": "customer-ended-call",
      "summary": "Test call summary.",
      "artifact": {
        "transcript": "AI: Hello.\nUser: Hi.",
        "recording": {
          "recordingUrl": "https://storage.vapi.ai/sample.mp3",
          "stereoRecordingUrl": "https://storage.vapi.ai/sample-stereo.mp3"
        }
      },
      "analysis": {
        "structuredData": {
          "callerName": "Test User",
          "callerEmail": "test@example.com",
          "callerPhone": "+447911000000",
          "treatmentInterest": "Brow Treatment",
          "callOutcome": "Booking Inquiry"
        }
      },
      "call": {
        "id": "sample-001",
        "customer": { "number": "+447911000000" },
        "startedAt": "2026-05-04T14:00:00.000Z",
        "endedAt": "2026-05-04T14:03:30.000Z"
      }
    }
  }'

Fix 4 — Corrected Vapi end-of-call-report field paths

The following paths are production-confirmed from the Vapi end-of-call-report payload. GHL prefixes all webhook fields with inboundWebhookRequest. in the variable picker.

Field Correct GHL variable Common mistake
Caller phone {{inboundWebhookRequest.message.call.customer.number}} message.phoneNumber.number does not exist
First name {{inboundWebhookRequest.message.analysis.structuredData.callerName}}
Email {{inboundWebhookRequest.message.analysis.structuredData.callerEmail}}
Treatment interest {{inboundWebhookRequest.message.analysis.structuredData.treatmentInterest}}
Call summary {{inboundWebhookRequest.message.summary}}
Transcript {{inboundWebhookRequest.message.artifact.transcript}} message.transcript does not exist at top level
Recording URL {{inboundWebhookRequest.message.artifact.recording.recordingUrl}} message.recordingUrl does not exist at top level
Ended reason {{inboundWebhookRequest.message.endedReason}}
Event type {{inboundWebhookRequest.message.type}}
Duration Does not exist as a standalone field Derive from call.startedAt / call.endedAt

structuredData fields only populate if analysisPlan.structuredDataPrompt and a JSON schema are configured on the Vapi assistant. Verify with a test call payload before relying on them.


Fix 5 — Vapi native GHL OAuth connector is broken (as of 2026-05)

The official Vapi MCP OAuth connector for GHL has been reported broken for months. Users report silent credential errors and 500/502 responses even when Vapi logs show tools triggering correctly. Do not attempt to use the native connector. Use the custom Inbound Webhook workflow approach documented above.


Standard setup checklist for new Vapi → GHL inbound client

  1. Create GHL Inbound Webhook workflow → copy webhook URL
  2. Patch Vapi assistant serverMessages to ["end-of-call-report"] via API
  3. Set Vapi assistant server URL to the GHL webhook URL
  4. Send sample payload to the GHL webhook URL
  5. In GHL workflow trigger → Mapping Reference → select the sample payload
  6. Build action steps using inboundWebhookRequest. prefixed variables from the table above
  7. Publish the workflow
  8. Make a real test call → confirm exactly 1 enrollment in Enrollment History
  9. Confirm all fields populated on the contact record

End-of-Day Update (2026-04-07)

Transcript Receiver — deployed to Cloudflare

The ghl-transcript-receiver Cloudflare Worker is now live:

  • Worker: https://ghl-transcript-receiver.johntossou.workers.dev
  • Webhook endpoint: POST /webhook/transcript (no auth — GHL doesn't support request headers)
  • Query endpoint: GET /transcripts?token=AUTH_TOKEN&date=YYYY-MM-DD
  • KV namespace ID: 01c5ffa00a554d96ab95ce889a97df6a
  • Wrangler config: tools/cloudflare-workers/ghl-transcript-receiver.wrangler.jsonc
  • Auth token: stored in .env as AUTH_TOKEN

Still required in GHL: go to Automation → "Transcript Generated" workflow → update the webhook action URL to the worker endpoint above → publish. Without this step, GHL won't push any transcripts to the worker.

GHL API — confirmed limitation

The GHL public REST API (/conversations/messages/{id}) does not return recording URLs or transcript text. These are stored GHL-internally. The only programmatic path is the "Transcript Generated" webhook workflow.

Twilio also does not hold the recordings from GHL native dialer calls — GHL uses its own pipeline.

GHL call recording — Camille's number

Call recording is not enabled on +44 7414 269881. Camille's human calls connect and last up to 5 minutes but leave no transcript. Enable recording in GHL → Phone Numbers → +44 7414 269881 → Call Recording ON.

2026-04-07 AI calling session findings

20 VAPI calls across 2 batches (~$0.73). Key issues observed:

  1. Premature hang-up — AI said "Goodbye" while a receptionist was mid-sentence giving an email address (London City Skin Clinic). Bhavisha prompt needs a longer patience window before ending when a human is actively speaking.
  2. Garbled primary_issue — Halcyon Medispa had a broken observation ("positive reviews costing inquiries"). Field content must be validated before calls go out.
  3. Best near-miss — Gielly Green: AI disclosed itself, Christie fetched the owner, call timed out on hold. Silence timeout threshold may need extending when AI has been explicitly told to hold.

End-of-Day Update (2026-04-04)

By the end of the 4 April 2026 session, the main shift was this:

  • Vapi prompting and GHL enrichment are no longer the only concern
  • the next automation layer is now a post-call renderer for email follow-up

New operating principle

Do not let GoHighLevel send post-call follow-up emails by stitching together raw dropdown values.

Use this pattern instead:

  1. GHL workflow is triggered by a custom disposition
  2. GHL waits ~10 minutes for transcript / summary availability
  3. GHL calls an external renderer endpoint
  4. the renderer combines:
  5. transcript nuance
  6. canonical GHL fields
  7. disposition
  8. the renderer writes final copy back into GHL
  9. GHL only sends if render_status = ready

Why this matters

The live outbound schema uses machine-friendly fields like:
- primary_issue
- booking_system
- team_structure

Those are excellent for:
- filtering
- routing
- choosing the angle

They are not finished copy.

That means:
- primary_issue should not be pasted raw into an email
- GHL template logic alone is too brittle for high-quality personalised follow-up

Local build status

Prepared locally:

  • docs/aurea-post-call-renderer-design.md
  • docs/ghl-post-call-renderer-workflow-blueprint.md
  • tools/cloudflare-workers/aurea-followup-renderer.js
  • tools/cloudflare-workers/aurea-followup-renderer.wrangler.jsonc
  • tools/cloudflare-workers/README-aurea-followup-renderer.md
  • tools/setup_ghl_render_fields.py

Not yet live

Two blockers remain:

  1. Cloudflare deploy
  2. wrangler is not authenticated on this machine yet

  3. GHL render-field creation

  4. the current GHL token can read and update contacts
  5. but it does not have the scope to manage custom field definitions
  6. so the renderer fields still need manual creation or a higher-scope token

Required renderer fields

  • rendered_email_subject
  • rendered_email_body
  • render_status
  • render_type
  • render_confidence
  • render_error

Best-practice conclusion

For Aurea’s post-call follow-up layer, the safest stack is now:

  • Vapi handles the call
  • GHL owns workflows and sending
  • Cloudflare renders bounded copy
  • tasks happen only on exception branches

Update suite au feedback de Claude

One important refinement was added after architecture review:

  • the renderer should not become a fragile all-or-nothing dependency

So the recommended v1 is now:
- fallback-first
- renderer second

In practice that means:
- safe fallback templates should exist before renderer rollout
- transcript quality should be checked before transcript-based rendering
- if rendering fails, times out, or lacks enough context, the system should degrade gracefully rather than silently skip follow-up
- the renderer remains a quality layer, not the only path to a follow-up email


Update Note (2026-04-04)

This document began as a Vapi + GHL integration reference, but it now also needs to reflect the live demo-clinic voice setup.

Important live clarifications:

  • +44 1355 209592 is the Demo Voice number, not the default Bhavisha prospecting number
  • the inbound demo assistant on that number is Riley
  • Riley assistant ID: cca2a23d-abcf-45ed-9aca-f54902ae3aaf
  • Riley is a demo clinic front-desk / after-hours reception assistant only
  • Riley should remain separate from Bhavisha outbound and callback roles

Voice configuration clarification:

  • the exact ElevenLabs voice ID for Bhavisha 2 is BIS6xSao7QaZrZ0MgsWG
  • if Riley uses the Bhavisha 2 voice for demo consistency, document that explicitly rather than inferring it from another assistant

Prompting clarification:

  • for demo inbound agents, pronunciation and pacing should not rely only on prompt wording
  • use Vapi voice-layer formatting / replacements and Deepgram keyterms where possible
  • keep the prompt structured into sections and keep it to one question at a time

Outbound schema clarification:

  • the live JCT Advisory outbound schema is now locked to:
  • decision_maker
  • booking_system
  • primary_issue
  • meta_ads
  • google_ads
  • team_structure
  • worth_mentioning
  • these are now the source-of-truth fields for Bhavisha personalization
  • the older vapi_observation / vapi_leak / vapi_angle model is now historical, not canonical

Executive Summary

We now have two useful patterns for connecting Vapi and GoHighLevel:

  1. Transcript push workflow
    This sends transcript-related data out of GoHighLevel to our own webhook endpoint.

  2. External call logging workflow
    This accepts a webhook into GoHighLevel and creates a contact + logs an external call directly into the contact's native activity / call history.

The important result from testing on JCT Advisory is:

  • the Inbound Webhook -> Create Contact -> Log External Call workflow executes successfully
  • it creates the contact
  • it adds an outbound call activity in GoHighLevel
  • this means we likely do not need the full Marketplace / Conversation Provider route for this use case

That is a meaningful simplification.


What We Confirmed

1. The native GHL workflow path works

We tested this workflow:

Inbound Webhook -> Create Contact -> Log External Call

Using a test payload, GoHighLevel:

  • accepted the webhook
  • created the contact John Test
  • logged an Outbound Call
  • showed the result on the contact record

This is the most important practical finding.

2. The workflow must use webhook payload variables, not contact variables

When using Inbound Webhook, the early workflow steps do not yet have a GHL contact object.

So for Create Contact, you must map fields from the incoming webhook payload, for example:

  • phone
  • firstName
  • lastName

Do not map fields like:

  • {{contact.first_name}}
  • {{contact.last_name}}
  • {{contact.phone}}

Those are empty before the contact exists, and the step fails.

3. Log External Call is strict about values

For the external call step:

  • direction should be lower-case: inbound or outbound
  • status should use allowed values such as:
  • completed
  • answered
  • busy
  • no-answer
  • failed
  • canceled
  • voicemail
  • pending

4. You can leave the date blank

If the Date field is left blank in Log External Call, GoHighLevel will apply the current date and time automatically.

That is useful for fast testing.

5. Use the real outbound number in From

For accurate logging, the From field should be the real caller number used by the Vapi outbound setup.

For the Bhavisha / JCT Advisory setup, the Vapi outbound number must match the live prospecting architecture.

Important clarification:

  • +44 1355 209592 is now the demo inbound number used by Riley
  • do not treat +44 1355 209592 as the default Bhavisha outbound number unless the live routing has explicitly been changed again
  • always cross-check the current source of truth in docs/phone-number-architecture.md

At the time of the original test, the number we resolved was:

+441355209592

6. The public workflow creation API is not available in the way we hoped

We tested the public GHL API and found:

  • GET /workflows/ returned an authorization / scope issue
  • POST /workflows/ returned 404 Not Found

So, at least with the current token and public API surface, we should assume:

  • workflows need to be created in the GHL UI
  • then tested via webhook

We should not assume we can fully automate workflow creation programmatically.


Workflow 1: Transcript Workflow

What it does

This workflow sends transcript-related call data out of GoHighLevel to our own webhook.

In the current setup:

  • trigger: Transcript Generated
  • action: Webhook
  • destination: our ngrok / server endpoint

What data it sends

Based on the current workflow, it sends values such as:

  • contactId
  • contactName
  • phone
  • companyName
  • transcript
  • summary
  • callDuration
  • direction

What this workflow is for

This workflow is useful when:

  • GoHighLevel already has the call / transcript event
  • we want to push the transcript elsewhere
  • we want custom downstream processing
  • we want to enrich, summarize, store, or route the transcript outside GHL

In simple terms

GoHighLevel says:

"A transcript has been generated. I will now send that data to your server."

So this workflow is:

GHL -> our webhook

Best use case

Use this when the transcript already exists inside GHL and you want custom handling outside GHL.


Workflow 2: Vapi External Call Logging Workflow

What it does

This workflow lets an external system send call details into GoHighLevel.

In the tested setup:

  • trigger: Inbound Webhook
  • action 1: Create Contact
  • action 2: Log External Call

What this workflow is for

This workflow is useful when:

  • Vapi handles the call
  • we want GHL to reflect that call natively
  • we want a call activity in the contact record
  • we want to avoid a more complex Marketplace / Conversation Provider setup

In simple terms

Vapi (or our script) says:

"Here is a call that happened. Please create or update the contact, then log the call in GHL."

So this workflow is:

Vapi / script -> GHL webhook

What we tested successfully

We sent this style of payload:

{
  "phone": "+447000000001",
  "firstName": "John",
  "lastName": "Test",
  "direction": "outbound",
  "status": "completed",
  "duration": 125,
  "recordingUrl": "https://example.com/test-recording.mp3"
}

And GHL successfully:

  • created the contact
  • logged the call
  • showed the call activity on the contact

Best use case

Use this when Vapi is the voice runtime and GHL should reflect the call outcome inside the CRM.


Best default approach for Vapi outbound / external calls

For real Vapi call logging into GHL, the cleanest current direction is:

  1. Vapi finishes the call
  2. Vapi or our bridge sends a webhook to GHL
  3. GHL workflow creates / updates the contact
  4. GHL logs the external call natively

This gives us:

  • simpler architecture
  • less Marketplace complexity
  • reduced developer billing risk
  • cleaner CRM visibility

What to keep as fallback

We should still keep the Python bridge available as fallback:

tools/vapi_to_ghl_bridge.py

Why:

  • the new workflow path is now validated for basic call logging
  • but the bridge is still useful for:
  • richer transcript storage
  • custom notes
  • custom business logic
  • backup if GHL workflow behavior changes

So the bridge should not be deleted yet.


Next Step: Structured Clinic Context For Bhavisha

The bigger goal is not just call logging.

The real system goal is:

  • store structured observations about each clinic inside GoHighLevel
  • pass those fields into the Vapi agent
  • make each Bhavisha call feel like a short spoken diagnostic audit
  • test whether better context increases conversion rates for cold outreach

This is now the recommended next build step.

Design rules

These are the rules we want to keep:

  • max 8 fields
  • each field has exactly one job
  • each field must be speakable in under 10 words
  • if a field is empty, Bhavisha ignores it completely
  • every field must be fillable by a human researcher or an AI scraper in under 5 minutes per clinic

These rules are important because they force the context to stay usable in real calls.

Proposed field schema

This section is now historical.

Use it only to understand the older Vapi-first model.

As of 2026-04-04, the recommended canonical schema for JCT Advisory outbound prep is the newer GHL contact schema documented in:
- vapi-outbound-routing-system.md
- jct-ghl-vapi-alignment-audit-2026-04-04.md

Deprecated as source-of-truth fields:
- vapi_observation
- vapi_leak
- vapi_angle

The current recommended schema is:

GHL Field Name Vapi Variable Purpose Example
decision_maker {{decisionMaker}} Best person to ask for by name Sarah Johnson
booking_system {{bookingSystem}} What booking system they use Phorest
primary_issue {{primaryIssue}} The single sharpest diagnostic angle no_online_booking
meta_ads {{metaAds}} Observable Meta ads state Past ads, pixel on site
google_ads {{googleAds}} Observable Google Ads state Active
team_structure {{structure}} Solo vs team context Team
worth_mentioning {{notes}} Nuance or second-order detail Google reviews mention slow callbacks

Important note:

  • if clinic_type already exists in GHL, treat it as an existing reusable field, not a brand-new one
  • if firstName or contact name already covers the decision-maker use case well enough, decision_maker can be reconsidered later

Live field lock for the current JCT Advisory build:
- decision_maker
- booking_system
- primary_issue
- meta_ads
- google_ads
- team_structure
- worth_mentioning

Do not rename these without updating:
- the GHL enrichment and push scripts
- the Vapi personalization mappers
- the Bhavisha prompt docs

How Bhavisha should use the fields

The purpose is not to dump all the data into the script.

The purpose is to make the call feel informed, precise, and calm.

Current recommended usage logic:

  • If {{decisionMaker}} is present, Bhavisha can ask for the person by name.
  • If {{bookingSystem}} is present, Bhavisha can anchor the observation in that system or flow.
  • If {{primaryIssue}} is present, Bhavisha should frame the conversation around that one issue rather than stacking multiple claims.
  • If {{metaAds}} or {{googleAds}} are present and relevant, Bhavisha may use them as supporting context, not as the main opener.
  • If {{notes}} is present, Bhavisha should use it sparingly and only if it helps credibility.
  • If a field is empty, Bhavisha should ignore it completely.
  • Bhavisha should never say she "researched" the clinic. The specificity should do the work.

Prompt behavior rules

These are the operating rules the prompt should follow:

  • use at most one strong detail early
  • do not stack observation and proofPoint in the opening
  • if observation exists, prefer it as the first specific detail
  • if the call is going well, keep the detail light and diagnostic
  • if the person pushes back, proofPoint can be used once as grounding
  • if toneHint says busy owner-operated, be shorter and softer
  • if toneHint says premium, slow-burn, avoid sounding transactional
  • if angle exists, keep the conversation focused around that single problem frame

How the fields get populated

There are two valid paths.

Path A: Manual now

Camille or another researcher fills 3-4 fields per clinic before calling:

  • vapi_observation
  • vapi_leak
  • vapi_angle
  • vapi_tone_hint

This should take about 3 minutes per clinic.

This is the right immediate operational path.

Path B: Automated later

A clinic intelligence scraper fills the full schema from:

  • website
  • Google Business Profile / reviews
  • social links
  • booking path

That would reduce the manual prep work later.

What we should build next

The immediate next implementation should be:

  1. create the custom fields in GHL
  2. update vapi_place_personalized_call.py to pull those fields from GHL
  3. map them into Bhavisha's Vapi variable values
  4. update the prompt so it uses the variables conditionally and lightly
  5. test on a small batch of clinics

What success looks like

A good version of this system should produce calls that:

  • sound specific without sounding creepy
  • feel like a short spoken audit, not a generic sales pitch
  • stay consistent across many clinics
  • can be improved by refining field quality rather than rewriting the whole agent

Practical implementation order

Recommended build order:

  1. Keep call logging working first
  2. Add the field schema in GHL
  3. Add field extraction to vapi_place_personalized_call.py
  4. Update Bhavisha prompt rules
  5. Run 10-20 test calls with structured clinic context
  6. Compare results against generic calling

This is the right way to test the real hypothesis:

does precise diagnostic context, delivered consistently at scale, improve cold-call conversion with clinics?


Practical Best Practices

1. Keep the webhook payload simple

Start with the smallest useful payload:

  • phone
  • firstName
  • lastName
  • direction
  • status
  • duration
  • recordingUrl

Do not overcomplicate the first version.

2. Test with fake data first

Before using real Vapi traffic:

  • send a simple webhook manually
  • confirm contact creation
  • confirm call logging
  • confirm the activity looks correct in GHL

This saves a lot of time.

3. Publish before testing

Draft workflows may look configured but still not behave as expected.

Always:

  • save
  • publish
  • then test

4. Check Execution Logs before checking the contact

The fastest debugging order is:

  1. Did the webhook trigger?
  2. Did Create Contact execute?
  3. Did Log External Call execute?
  4. Then inspect the contact record

5. Do not assume a field is mapped correctly just because it “looks filled”

In GoHighLevel, a field can look populated but still point to the wrong data source.

Always ask:

  • is this a value from the webhook payload?
  • or is it a value from the contact object?

That distinction matters a lot.

6. Use the real outbound number in From

If the goal is to reflect the real call source, use the real Vapi outbound number in From.

Important architecture warning:

  • +44 1355 209592 is currently the demo voice number for Riley
  • do not blindly reuse older documentation references for Bhavisha call logging
  • confirm the live prospecting number before treating any From value as authoritative

For Bhavisha / JCT Advisory, the older documented number was:

+441355209592

7. Use lower-case call values

Safer defaults:

  • direction: inbound / outbound
  • status: completed, answered, etc.

Avoid guessing capitalization.

8. Treat workflow UI setup as part of the real system

Even if the API is limited, the workflow UI can still be a robust production tool.

Do not over-prioritize API purity if the UI gives a simpler and more reliable path.


When To Use Which Workflow

Use the Transcript Workflow when:

  • GHL already has the transcript event
  • you want to send transcript data out to your own server
  • you want custom processing outside GHL

Use the External Call Logging Workflow when:

  • Vapi is handling the call
  • you want the call reflected directly in GHL
  • you want native CRM call visibility
  • you want to avoid Marketplace / Conversation Provider complexity

For Vapi + GoHighLevel call logging, the current best practical recommendation is:

  • use GHL Inbound Webhook + Create Contact + Log External Call for native CRM call logging
  • keep the Python bridge as a fallback / enrichment layer
  • do not assume Marketplace / Conversation Provider is necessary unless a later requirement forces it

Demo Inbound Assistant Best Practices (Aurea Demo Clinic)

This section is specifically for the demo clinic inbound experience, not the outbound Bhavisha system.

Live role

  • assistant name: Riley
  • assistant ID: cca2a23d-abcf-45ed-9aca-f54902ae3aaf
  • number: +44 1355 209592
  • purpose: after-hours clinic reception demo

Design rules

  • keep this assistant separate from all outbound prospecting roles
  • it should sound like a premium clinic front desk, not a sales bot
  • it should handle inbound enquiries, basic screening, booking next steps, and message-taking
  • it should not give medical advice
  • it should not directly manage existing appointments beyond taking a callback message

Prompt rules that proved useful

  • separate the Vapi firstMessage from the main system prompt
  • structure the prompt into sections: identity, scope, flow, routing, hard rules
  • ask one question at a time
  • keep replies short enough to sound spoken rather than read
  • write for natural British phone speech, not for chatbot-style completeness
  • avoid rigid exact-line scripting unless legally necessary

Pronunciation and speech handling

For demo assistants, pronunciation should be handled in layers:

  1. prompt clarity
  2. Vapi voice-layer replacements / formatting
  3. transcriber keyterms for brand and treatment names

Current examples worth preserving:

  • Aurea pronunciation support
  • Profhilo pronunciation support
  • polynucleotides pronunciation support
  • Deepgram keyterms for clinic-specific and treatment-specific phrases

Voice configuration

The exact ElevenLabs voice ID for Bhavisha 2 is:

BIS6xSao7QaZrZ0MgsWG

If this voice is reused on demo agents like Riley, document it explicitly in the assistant notes and architecture docs so the live setup stays traceable.

Stress-test scenarios the demo assistant must handle cleanly

  • new treatment enquiry
  • existing appointment / reschedule request
  • price question
  • urgent concern / reaction concern
  • request to speak to the practitioner directly
  • caller says they never received the booking link
  • silent caller
  • wrong number / out-of-scope caller

Next Build Concept: AI Reveal System (2026-03-31)

The idea

Two call cases that turn the AI nature of the system into the product demo itself.

Case 1 — Outbound cold call

Bhavisha opens with one sharp, personalised observation about the clinic before any pitch. The observation is pulled from GHL fields (vapi_observation, vapi_leak). Once the person engages, Bhavisha reveals she is an AI. The reveal is not a confession — it is proof of concept delivered live. The call becomes a demo, not a sales conversation.

Suggested opening:

"Hi, is that [NAME]? I noticed your booking link goes to a contact form rather than a calendar — I'd guess that loses you a few enquiries a week. I'm actually an AI calling on behalf of Aurea Growth — which is the point. Worth a quick chat to see if something like this makes sense for your clinic?"

Case 2 — Missed call callback (inbound)

When a clinic owner calls back the outbound number after a missed call, the AI answers:

"Hi, is that [NAME]? Thanks for calling back. I'm essentially calling to solve the exact problem you're facing right now — catching up with leads you missed while you were busy."

The irony does the selling. They just lived the problem in the last 60 seconds.

Why Case 2 is particularly strong

The clinic owner calls back an unknown number, an AI picks up, and the opening line names the exact problem they just experienced. The product sells itself before any pitch is made. There is no equivalent moment in standard cold outreach.

Design rules for the reveal

  • The reveal must happen after the observation lands and the person engages, not on a timer and not immediately.
  • Trigger: they respond positively or ask a follow-up question — then Bhavisha says it.
  • Too early: they hang up. Too late: it feels like a trick.
  • The reveal line should be short and confident, not apologetic.

What needs to be built

What Change needed
Bhavisha prompt (Case 1) Add reveal logic: after engagement signal, reveal AI identity as capability proof
Case 2 agent Separate Vapi assistant — inbound handler, different opening, different script
GHL workflow Route missed-call-callbacks to the Case 2 agent's phone number
vapi_leak field Already exists — move it into the opening line, not buried later

What already exists

  • GHL custom fields (vapi_observation, vapi_leak, vapi_angle, vapi_tone_hint) are designed for exactly this — feeding the personalised opening
  • vapi_place_personalized_call.py and the batch caller already inject these into Vapi variable values
  • The GHL call logging workflow already handles outcomes

The reveal logic and Case 2 agent are the only new pieces needed.


Build Status: AI Reveal System (updated 2026-04-04)

What has been written

File What it is Status
tools/vapi_cold_prompt_v2.txt Case 1 — outbound cold call with AI reveal Live in Vapi, updated 2026-04-04
tools/vapi_callback_prompt.txt Case 2 — inbound callback handler Live in Vapi, updated 2026-04-04
tools/vapi_batch_caller.py Batch caller CLI Live
tools/vapi_caller_ui.py Browser UI for batch calling Live
tools/vapi_place_personalized_call.py Single call with GHL context Live
tools/push_cold_prospects_to_ghl.py Scrape → score → push to GHL Live

What still needs to be built

Step 1 — Create GHL custom fields
- Create these custom contact fields in GHL (Settings → Custom Fields):
- vapi_observation
- vapi_leak
- vapi_angle
- vapi_tone_hint
- These are what gets filled per clinic before calling

Step 2 — Update vapi_place_personalized_call.py to pull custom fields
- Currently only pulls core contact fields (firstName, companyName, tags)
- Needs to also read the 4 custom fields from GHL and pass them into Vapi variable values

Step 3 — Maintain live Vapi assistants
- Case 1 prompt is already live in Vapi
- Case 2 assistant is already created and live in Vapi
- keep assistant IDs documented when changing prompts or settings
- keep the shared voice ID documented: BIS6xSao7QaZrZ0MgsWG

Step 4 — GHL workflow for inbound callbacks / forwarding
- Create a GHL workflow: Inbound Call → (if contact tagged outbound-call-completed) → route to Case 2 Vapi assistant
- GHL needs to forward the inbound call to the Case 2 Vapi number
- Alternatively: configure the Vapi phone number directly as the inbound handler
- Current status: still blocked / unresolved with GoHighLevel as of 2026-04-04

Step 5 — Test the full loop
- Push 5 cold prospects via push_cold_prospects_to_ghl.py
- Fill vapi_observation and vapi_leak on each manually
- Run batch caller via the UI
- Confirm GHL logs the outcomes and applies tags
- Call back the outbound number from a test phone
- Confirm Case 2 agent answers with the callback opening
- Confirm both assistants can check live availability and create a real appointment on calendar K5k7NPpz9lKadHgt7V5x

Full loop (when all steps are complete)

push_cold_prospects_to_ghl.py
    → 30 ICP-matched clinics pushed to GHL with tags + angle notes
    ↓
Enrich each contact in GHL (vapi_observation, vapi_leak, vapi_angle, vapi_tone_hint)
    ↓
Aurea Caller UI → Start Calling
    → Bhavisha calls each clinic (Case 1 prompt)
    → Opens with sharp observation, reveals AI after engagement
    → GHL logs outcome, applies tags (voicemail / no-answer / completed / callback-requested)
    ↓
Clinic owner calls back the outbound number
    → Case 2 agent picks up
    → "I'm solving the exact problem you're experiencing right now"
    → Books discovery call

Live state now

  • Bhavisha Case 1 assistant is live
  • Bhavisha Case 2 assistant is live
  • both assistants now use the Bhavisha 2 ElevenLabs voice
  • both assistants were updated on 2026-04-04 with improved settings and prompt behavior
  • both assistants now have GoHighLevel tools attached in Vapi for contact lookup / creation, calendar availability, and calendar event creation
  • both assistants are now instructed to book directly into Aurea Growth | Intro Call (K5k7NPpz9lKadHgt7V5x) when the prospect agrees to a meeting
  • the forwarding problem in GoHighLevel is still unresolved and remains under discussion with GHL

Known Open Questions

These are still worth verifying later:

  • whether all real Vapi call outcomes map cleanly into GHL status values
  • how best to handle inbound vs outbound in production payloads
  • whether recording URLs always display the way we want in GHL
  • whether transcript content should still be stored separately as notes or pushed elsewhere
  • whether long-term production load introduces any workflow edge cases

Short Version

If someone asks, “What did we learn?” the short answer is:

We proved that Vapi call data can be pushed into GoHighLevel using a simple inbound webhook workflow. GHL can create the contact and log the call natively, which means we likely do not need the Marketplace / Conversation Provider route for this use case. The old transcript push and Python bridge still matter, but the simpler native workflow is now the preferred first option for call logging.

Autogenerated navigation links to improve Obsidian connectivity.

  • [[docs/vapi-outbound-routing-system|Aurea AI Outbound Routing System]]
  • [[docs/jct-ghl-vapi-alignment-audit-2026-04-04|JCT Advisory GHL ↔ Vapi Alignment Audit]]
  • [[docs/ghl-post-call-renderer-workflow-blueprint|GHL Post-Call Renderer Workflow Blueprint]]
  • [[docs/aurea-post-call-renderer-design|Aurea Post-Call Renderer Design]]
  • [[docs/aurea-gtm|Aurea Growth — Go-to-Market Strategy]]
  • [[aurea-growth-encyclopedia/wiki/index|Aurea Growth Encyclopedia]]

Activation Prompt

You are in GHL Contacts Push mode. Read the full SOP before starting a batch. Source quality upstream determines push quality downstream — validate CSV against ICP criteria before pushing. Push cadence: max 50 contacts per batch. Tag every contact correctly on push. Check the GHL subaccount after each batch to confirm contacts landed with correct tags and no duplicates.

Aurea Growth SOP — GHL Contacts Push (UK Clinic Cold Outbound)

Last updated: 2026-05-15

This SOP covers how to discover UK aesthetics clinic leads, qualify them against Aurea ICP criteria, and push them as contacts into the GHL JCT subaccount for cold calling by the VAPI outbound stack.

Core principle: source pool quality upstream determines push quality downstream. A large, well-labelled CSV means weekly batches run in seconds. A thin CSV means the push stalls at 50 and Claude Code has to scramble to find more.


0. Overview

Two scripts do all the work:

Script What it does
tools/discover_uk_clinic_leads.py Scrapes UK clinics via Google Places and other sources; appends net-new rows to aurea-lead-generator/aurea_leads.csv
tools/push_cold_prospects_to_ghl.py Reads the CSV, filters against exclusion lists, scores ICP fit, previews candidates, and pushes approved contacts to GHL

Run discovery first to top up the pool; run push second to move the batch into GHL.


1. Source Pool — aurea_leads.csv

Location: aurea-lead-generator/aurea_leads.csv

Columns used by the push script:

Column Notes
name Clinic name — cleaned and scored for ICP signals
phone UK mobile preferred; freephone/0800 skipped
address Must contain , UK or United Kingdom
rating Google rating — minimum 4.0
review_count Integer — hard cap at 500 (above = likely corporate)
website Used for CRM notes only

Target pool size: 5,000+ rows before exclusions. With a clean pool this large, a 300-contact weekly push reliably finds 1,500+ candidates after filters, so the script is not the bottleneck.

Replenish when: dry-run preview drops below ~400 candidates, or the batch fails to reach 100 pushed contacts in under 5 minutes.


2. Running Discovery

# Top up the source pool — run from Claude Code Workflow root
python3 tools/discover_uk_clinic_leads.py

The script writes net-new rows into aurea_leads.csv. It deduplicates by phone before writing so re-running is safe.

Signs the pool needs a top-up:

  • Dry-run preview shows fewer than 300 candidates
  • Push stalls before reaching the target count
  • Most candidates come from the same 3–4 cities

To broaden coverage: ask Claude Code to expand the city list in discover_uk_clinic_leads.py or add new source query terms (e.g. "skin clinic", "anti-ageing clinic", "laser clinic").


3. ICP Filters — Who Gets In

Contacts are excluded if any of the following are true:

Rule Value
Rating below minimum < 4.0
Review count over cap > 500 (corporate scale)
No UK address , UK or United Kingdom not in address
Freephone / 0800 number Excluded — signals call centre, not owner
No phone at all Skipped
Non-ICP keyword in name See list below
No ICP leadership signal No aesthetics / doctor / nurse / founder signal
Already in GHL Matched by phone number in ghl_contact_log.json
Previously pushed Matched in cold_prospects_pushed_*.json in .tmp/
Active client or prospect Matched in clients/00-INDEX.md or clients/*.md
Mailman contacted Matched in local Mailman records or the Mailman tracker sheet

Non-ICP keyword exclusions (name contains any of these → skip):

massage, therapy, nail, wax, brow, lash, threading, tanning, hair, barber, training, academy, spmu, permanent makeup, microblading, glamour, association, directory, tattoo removal, mole removal, NHS, hospital, university

ICP leadership signals (at least one must be present to pass):

  • aesthetics / aesthetic
  • medispa / medi spa
  • skin & laser / laser & skin
  • cosmetic clinic
  • injectables
  • botox / fillers
  • anti-wrinkle
  • profhilo / polynucleotide
  • Dr. prefix or Nurse prefix in name
  • by [first name] pattern
  • Known first name pattern (e.g. Sarah, Julie, Laura, Anna, Nadia…)

4. ICP Scoring

After filtering, candidates are ranked by ICP score. Higher = prioritised for the batch.

Signal Points
Mobile number (not landline) +3.0
Personal name / founder signal +2.0
Rating ≥ 4.9 +1.5
Rating ≥ 4.7 +0.75
Reviews 30–400 (sweet spot) +1.0 to +1.6
Reviews > 400 -0.5

The top N scores are selected for the push batch. Default batch size is 300.


5. Exclusion Refresh

Before every push the script auto-refreshes:

  1. ghl_contact_log.json — pulls all current contacts from the GHL API and caches phone numbers locally
  2. Previously pushed batches — all cold_prospects_pushed_*.json files in .tmp/
  3. Client + prospect files — scans clients/00-INDEX.md and all clients/*.md
  4. Mailman records — scans Mailman Copies/ and the Mailman Google Sheet

This means the same clinic cannot be pushed twice even across multiple sessions.


6. Running the Push

Step 1 — Dry run (required first)

python3 tools/push_cold_prospects_to_ghl.py --dry-run

Review the output. Check:
- Count of candidates found
- Exclusion breakdown (missing signal, rating, phone, GHL duplicate…)
- Sample clinic names — any obvious non-ICP entries?
- Total pushable vs target count

Dry-run preview is saved to .tmp/cold_prospects_preview_2026-XX-XX.json.

Step 2 — Live push

python3 tools/push_cold_prospects_to_ghl.py

For UK-only batches (standard):

python3 tools/push_cold_prospects_to_ghl.py --uk-only

The --uk-only flag enforces +44 phone formatting and skips any non-UK address rows.

Step 3 — Verify

After the push:
1. Check the script output for any errors
2. Open GHL → Contacts → filter by tag cold-prospect-2026-q2 — verify count matches pushed count
3. The push script auto-refreshes ghl_contact_log.json after completing


7. Tags Applied

Every pushed contact receives:

Tag Purpose
new-lead Flags this as a raw prospect, not a warmed contact
cold-prospect-2026-q2 Quarter-stamped for cohort tracking; auto-computed from push date

US-based contacts (non-standard) also receive: usa

Do not manually tag contacts with these — the script handles it.


8. Output Files

File What it is
.tmp/cold_prospects_pushed_YYYY-MM-DD.json First batch pushed on that date
.tmp/cold_prospects_pushed_YYYY-MM-DD_b2.json Second batch on the same date (top-up)
.tmp/cold_prospects_preview_YYYY-MM-DD.json Dry-run output — candidates before any push
.tmp/ghl_contact_log.json Cached GHL contact list, refreshed before and after every push

These files are cumulative exclusion history. Do not delete them — they prevent re-pushing the same clinics.


9. Weekly Push Cadence

Day Action
Monday Run discover_uk_clinic_leads.py to top up pool if dry-run preview < 400
Tuesday Run dry-run, review candidates
Wednesday Run live push, verify in GHL
Thursday onward VAPI outbound stack dials the new batch automatically

10. Troubleshooting

Push stalls below target count

  1. Check exclusion breakdown in the dry-run output
  2. If "missing founder/doctor/nurse signal" is the top reason: check that the ICP leadership signals list is current — aesthetics clinic names evolve and new keyword patterns need to be added
  3. If "already in GHL" is the top reason: run discover_uk_clinic_leads.py to expand the source pool with new cities or search terms
  4. If "non-UK address" is the top reason: check for rows with no address — these were probably scraped from a non-UK source; remove them from the CSV or re-run discovery with tighter geo constraints

GHL API errors

  • Check GHL_PRIVATE_TOKEN and GHL_LOCATION_ID in .env
  • If 429 (rate limit): the script has built-in back-off; wait 5 minutes and re-run with --dry-run first to confirm the API is responding
  • If a contact errors mid-batch: the script logs the failure and continues; the output JSON shows which contacts were successfully pushed vs errored

Duplicate pushed to GHL

The push script deduplicates by phone number against the cached ghl_contact_log.json. If a duplicate still appears:
1. Run python3 tools/push_cold_prospects_to_ghl.py --dry-run — check if phone number format differs (e.g. 07700900000 vs +447700900000)
2. Manually delete the duplicate from GHL
3. Add the normalised phone to .tmp/ghl_contact_log.json if needed

Non-clinic slipped in

Some directories or associations pass the name filter. To remove:
1. Note the exact name (e.g. British Association of Medical Aesthetic Nurses)
2. Add it to the NON_ICP_KEYWORDS list or the chain-block list in push_cold_prospects_to_ghl.py
3. Delete the contact from GHL manually
4. Remove from the output JSON to keep records clean


11. Environment Variables Required

Set in .env at the workspace root:

Variable Description
GHL_PRIVATE_TOKEN GHL private API token for the JCT subaccount
GHL_LOCATION_ID GHL location/subaccount ID

To verify they are set:

grep "GHL_" .env

Client Landing Pages & CRO

/client-landing-page-builder local

End-to-end Aurea Growth landing page builder for clinic/client campaigns.

When: Default orchestration skill for any client/prospect landing page, campaign page, GHL form test, or Cloudflare-hosted page.

/page-cro local

Conversion audit and optimisation for marketing pages.

When: After the page offer and structure exist. Tightens above-the-fold clarity, proof, objections, CTA logic, and section order.

/form-cro local

Optimise lead-capture and qualification forms that are not signup flows.

When: Use on GHL forms, embedded forms, suitability checks, consultation request forms, and multi-step lead capture.

/analytics-tracking local

Set up or audit analytics, events, conversions, and measurement plans.

When: Before shipping paid-traffic pages. Define CTA clicks, form-load events, submissions, thank-you conversions, and GA4/GTM contracts.

/seo-audit local

Review metadata, headings, crawlability, local SEO, and search-intent fit.

When: Use before publishing treatment pages, client-domain pages, or any page expected to rank or affect Quality Score.

/schema-markup local

Add, fix, or optimise structured data for services, FAQs, local businesses, and offers.

When: Use on clinic/service pages after copy is stable. Validate JSON-LD before deploy.

/ab-test-setup local

Plan, design, and implement A/B tests and experiment tracking.

When: Use when traffic volume justifies variants or when creating a deliberate test plan for headline, CTA, proof, or form friction.

/optimize system

Diagnose and fix UI performance across loading speed, rendering, images, and animation cost.

When: Use before deploying paid-traffic pages, especially when third-party forms, large images, or animation are involved.

Premium Design & UI

/landing-page local

Design high-converting single-offer landing pages.

When: When building or rewriting a campaign page, treatment page, or any single-CTA page.

/ui-ux-pro-max local

Reference catalogue of colour palettes, font pairings, UX guidelines, and section patterns.

When: Phase 1 of any build. Load early to choose palette, fonts, and UX patterns before writing code.

/impeccable system

Production-grade frontend design guardrails.

When: Run alongside ui-ux-pro-max in Phase 1. Sets quality rules and catches generic LLM design habits.

/high-end-visual-design system

High-end agency visual direction: typography, spacing, art direction, and premium restraint.

When: Use when a page needs to feel luxury, clinical-premium, boutique, editorial, or expensive.

/design-taste-frontend system

Senior UI/UX taste layer for avoiding default-looking interfaces.

When: Use on client-facing frontend work when the page must feel intentional, polished, and less template-like.

/redesign-existing-projects system

Upgrade existing websites/apps by auditing weak design and applying a premium redesign pass.

When: Use when improving an existing page rather than starting from a blank file.

/typeset system

Fix font choices, hierarchy, sizing, line-height, and readability.

When: After the first draft exists. Run if typography looks flat, inconsistent, too large, or not premium enough.

/layout system

Improve spacing, visual rhythm, section composition, and scan paths.

When: Phase 3 refinement when sections feel samey, cards dominate the page, or hierarchy is weak.

/colorize system

Add strategic colour to designs that feel monochromatic, flat, or cold.

When: Use carefully on premium clinic pages: add warmth and distinction without making the palette loud.

/distill system

Strip unnecessary complexity from noisy or overbuilt designs.

When: Use when a page has too many cards, claims, badges, effects, CTAs, or repeated sections.

/quieter system

Tone down visually aggressive designs while preserving quality and intent.

When: Use for luxury, medical, or clinic pages that feel too salesy, loud, shiny, or synthetic.

/animate system

Add purposeful scroll entrances, hover states, and micro-interactions.

When: Use after structure and copy are locked. Keep motion subtle on clinical/luxury pages.

/delight system

Add memorable moments of personality and interaction.

When: Use sparingly on client pages when the brand can support a tasteful signature moment. Avoid gimmicks.

/adapt system

Adapt layouts across screen sizes, devices, and contexts.

When: Required QA pass before client review. Check mobile sticky CTAs, form embeds, visual crop, and tap targets.

/critique system

UX audit for hierarchy, cognitive load, trust, and anti-patterns.

When: Use after implementation and before polish. Best for honest critique of whether the page feels premium and converts.

/audit system

Technical quality checks across accessibility, performance, theming, responsive design, and code quality.

When: Use before deployment or client handoff, especially after third-party embeds or responsive changes.

/polish system

Final quality pass fixing alignment, spacing, consistency, and micro-detail issues.

When: Last pass before sending to client or deploying. Catches the little things that make work feel finished.

Marketing, Copy & Strategy

/copywriting local

Write or rewrite marketing copy for pages, offers, ads, emails, and sales assets.

When: Required for every client-facing page. Tightens offer clarity, headlines, proof, objections, CTAs, and voice.

/copy-editing local

Improve existing marketing copy without rebuilding the entire message.

When: Use when the page already has decent copy but needs clarity, tone, concision, or premium restraint.

/clarify system

Improve unclear UX copy, labels, microcopy, and instructions.

When: Use for forms, CTAs, reassurance copy, FAQ labels, error states, and any copy causing decision friction.

/marketing-psychology local

Apply psychology and behavioural science: anchoring, framing, social proof, scarcity, and objections.

When: Use alongside copywriting on conversion pages, especially high-ticket consultations and suitability gates.

/pricing-strategy local

Improve pricing, packaging, anchoring, and monetisation strategy.

When: Use when showing course values, deposits, consultation fees, packages, or pay-5-get-6 style offers.

/customer-research local

Conduct, analyse, or synthesise customer research.

When: Use before major page rewrites when reviews, calls, surveys, or client notes can reveal actual patient language.

/product-marketing-context local

Create or update product/service marketing context documents.

When: Use for repeat clients or multi-page builds so positioning, offers, objections, and proof stay consistent.

/ai-seo local

Optimise content for AI search engines and LLM citations.

When: Use when building authoritative treatment pages, clinic service pages, or content designed to be cited by AI search.

/cold-email local

Write B2B cold emails and follow-up sequences that get replies.

When: Outbound prospecting and client acquisition sequences.

/email-sequence local

Create or optimise warm/lifecycle email sequences and drip campaigns.

When: Use for GHL nurture, missed-call, reactivation, appointment reminder, and post-lead follow-up sequences.

/ad-creative local

Generate, iterate, and scale ad creative for Meta and Google campaigns.

When: Use when turning landing-page positioning into ad hooks, headlines, descriptions, or creative briefs.

/paid-ads local

Strategy and execution for paid advertising campaigns on Google and Meta.

When: Campaign planning, targeting, Quality Score diagnosis, budget allocation, or ad performance troubleshooting.

/sales-enablement local

Create sales collateral, objection handling, one-pagers, and pitch assets.

When: Use when the landing page needs a matching sales aid, client pitch, objection sheet, or consultation script.

Automation, AI & GHL

/vapi-prompt-builder local

Build production-quality VAPI AI assistant prompts from scratch.

When: Setting up a new VAPI voice agent: inbound, outbound, qualification, booking, or follow-up.

/n8n-workflow-patterns system

Proven workflow architectural patterns from real n8n workflows.

When: When designing a new n8n automation before building nodes.

/n8n-node-configuration system

Operation-aware n8n node configuration guidance.

When: When configuring node operations, credentials, resources, and common property traps.

/n8n-code-javascript system

Write JavaScript code in n8n Code nodes.

When: When writing or debugging JS inside an n8n Code node.

/n8n-code-python system

Write Python code in n8n Code nodes.

When: When writing or debugging Python inside an n8n Code node.

/n8n-expression-syntax system

Validate n8n expression syntax and fix common errors.

When: When an n8n expression throws an error or behaves unexpectedly.

/n8n-validation-expert system

Interpret n8n validation errors and guide fixes.

When: When the n8n editor or MCP validation reports node, credential, expression, or workflow errors.

/n8n-mcp-tools-expert system

Expert guide for using n8n MCP tools effectively.

When: When building, validating, or inspecting n8n workflows via MCP tools.

/aurea-clinic-outbound-enrichment local

Prepare and enrich JCT Advisory cold clinic prospects for outbound calling.

When: Before a batch of clinic outbound calls or when enriching prospects for Bhavisha/JCT workflows.

Operations & Shipping

/cloudflare:wrangler plugin

Cloudflare Wrangler deployment and management guidance.

When: Use when deploying Cloudflare Pages/Workers, checking projects, or debugging Pages deploys.

/cloudflare:web-perf plugin

Measure and improve Core Web Vitals and runtime performance.

When: Use after deployment when the live page needs performance inspection or Lighthouse/Core Web Vitals work.

/security-review local

Audit files or scripts for security vulnerabilities.

When: Before deploying scripts that handle user data, API keys, external inputs, redirects, forms, or webhooks.

/session-handoff system

Generate a session handoff summary for context continuity.

When: At the end of a long build/deploy session so the next session can pick up cleanly.

/browser-use:browser plugin

Use the in-app browser to inspect, navigate, test, and screenshot local or live pages.

When: Use after frontend changes to inspect the current tab, click CTAs, test forms, and verify responsive behaviour.

/review system

Review code changes for correctness, style, regressions, and risk.

When: Use for PR-style checks or when reviewing another agent/contractor's edits before merge.

All Source Documents

These are the source markdown files in docs/. Edit them directly, then run python build.py and redeploy to update the site.

TitleFileUpdatedRead timeDescription
AUREA COMMERCIAL GUIDE — v6Aurea Growth V6.md2026-05-149 min readGrowth-Led Positioning, Offer Logic, Qualification, and GTM Doctrine
Agentic Architecture Upgrade Plan — 2026-03-28agentic-architecture-upgrade-plan-2026-03-28.md2026-04-2011 min read> Added by Codex on 2026-03-28 for durable handoff to Claude Code / Cloud Code. Purpose: assess how to integrate external repos and patterns into the current JC
Aurea Ad Creative Contextaurea-ad-creative-context.md2026-04-084 min readSystem context for use with /ad-creative and /paid-ads skills. Feed this file when generating Meta ad briefs, Google Ads copy, or any paid creative for clinic c
Aurea Copywriting Contextaurea-copywriting-context.md2026-04-083 min readSystem context for use with the /copywriting skill. Feed this file when reviewing or writing any Aurea-facing copy — Mailman letters, GHL email templates, SMS f
Aurea Growth — Go-to-Market Strategyaurea-gtm.md2026-04-2026 min read> Last updated: 2026-04-14
Aurea Growth — Meta Ad Copy (All 10 Videos)aurea-meta-ad-copy.md2026-03-3014 min readCampaign: Click-to-WhatsApp | Messaging objective | UK clinic owners
Aurea Growth — Meta Campaign Strategyaurea-meta-campaign-strategy.md2026-04-2055 min read> Status: Phase 1 Click-to-WhatsApp paused on 2026-03-29. Instant Form relaunch published on 2026-04-06, paused on 2026-04-09 for form correction, relaunched on
Aurea Post-Call Renderer Designaurea-post-call-renderer-design.md2026-04-206 min readLast updated: 2026-04-04
Aurea Recruitment Sourcing Referenceaurea-recruitment-sourcing-reference-2026-04-16.md2026-05-0212 min readLast updated: 2026-05-02
Aurea Growth — UGC Script Packaurea-ugc-3-core-angles-instant-form.md2026-04-2010 min read> Status: working draft
Aurea Voice Agent Architecture: Clinic Prospectingaurea_voice_agent_architecture.md2026-04-208 min readLast updated: 2026-04-02
Clinic Omnichannel AI Stack — Decision Playbookclinic-omnichannel-ai-stack-playbook.md2026-05-1119 min readLast updated: 2026-05-06
David / Smile Wanted — Company-Only Target Listdavid-smilewanted-consolidated-list-2026-04-16.md2026-04-164 min readDate: 2026-04-16
Field Growth Rep Interview Deckfield-growth-rep-interview-deck-v1.md2026-04-206 min readThis deck is for interviewing and onboarding Field Growth Rep candidates for Aurea Growth.
Gemini Batch API Referencegemini-batch-api-reference.md2026-04-203 min readSource: user-provided reference saved on 2026-03-29.
Aurea Growth SOP — GHL Contacts Push (UK Clinic Cold Outbound)ghl-contacts-push-sop.md2026-05-157 min readLast updated: 2026-05-15
GHL Post-Call Renderer Workflow Blueprintghl-post-call-renderer-workflow-blueprint.md2026-04-209 min readLast updated: 2026-04-07
GHL Voice Agent Knowledge Bases — JCT Advisoryghl-voice-agent-knowledge-bases-2026-04-14.md2026-04-143 min readLast updated: 2026-04-14
GHL Voice Agent Prompts + Knowledge Base — JCT Advisoryghl-voice-agent-prompts-2026-04-14.md2026-04-2021 min readLast updated: 2026-04-14 (post live number tests)
GHL Voice Agents Mapping — JCT Advisoryghl-voice-agents-mapping-2026-04-14.md2026-04-149 min readLast updated: 2026-04-14 (post live number tests)
GoHighLevel Workflow Auditghl-workflow-audit-2026-04-09.md2026-04-208 min readDate: 2026-04-09
JCT Advisory GHL Voice Audit — 2026-04-14jct-advisory-ghl-voice-audit-2026-04-14.md2026-04-2013 min readLast updated: 2026-04-14 (after disposition confirmation and live number retest)
John Tossou — Independent Buyer-Side Advisoryjct-advisory-one-pager.md2026-04-184 min readFor CMOs, founders, and commercial leaders making decisions they only get to make once.
JCT Advisory GHL ↔ Vapi Alignment Auditjct-ghl-vapi-alignment-audit-2026-04-04.md2026-04-208 min readDate: 2026-04-04
John Tossou - Master Experience Documentjohn-tossou-master-experience.md2026-05-0127 min readPurpose: central source of truth for John's professional experience, metrics, clients, positioning angles, and CV-safe notes. This document is meant to consolid
johntossou-brand-strategyjohntossou-brand-strategy.md2026-05-0616 min readname: johntossou.com — personal brand consulting vehicle
John Tossou — LinkedIn Profile Copyjohntossou-linkedin.md2026-05-022 min readLast updated: 2026-05-02
Jordan Platten — Reference Strategyjordan-unfair-ai-cold-outreach-reference.md2026-04-2013 min readDocument de reference dedie a la strategie exposee dans la video sur le systeme d'outreach AI capable de generer des rendez-vous de vente de maniere largement a
Mailman Visual Upgrade Log — 2026-03-28mailman-visual-upgrade-log-2026-03-28.md2026-03-282 min read- User asked to exploit the UI UX Pro Max repo ideas to improve the visual quality of Mailman letters before batching the week's outputs.
Meta UGC Hook Best Practicesmeta-ugc-hook-best-practices.md2026-04-2014 min readDate: 2026-04-03
Norman Daliva - Candidate Filenorman-daliva-candidate-file.md2026-05-088 min readLast updated: 2026-05-08 (post-onboarding call)
Independent Contractor Agreementnorman-daliva-cold-caller-contractor-agreement.md2026-05-0611 min readJCT Advisory Ltd trading as Aurea Growth - Cold Caller / Appointment Setter
Phone Number Architecturephone-number-architecture.md2026-04-206 min readLast updated: 2026-04-14
Pre-Ship Checklistpre-ship-checklist.md2026-04-081 min readRun this before pushing any script change to production — Cloudflare Worker, Flask app, webhook receivers, or any tool that touches external APIs.
Private Intake Demo — Project Hubprivate-intake-demo.md2026-04-2021 min readStatus: V1 complete (Aisha). Generator pipeline rebuilt with Claude Vision. Reliability hardening completed on 2026-04-02. Gemini visual-analysis experiments ad
Proposal Template Best Practicesproposal-template-best-practices.md2026-04-207 min readThis document captures the best practices learned from building proposals for:
Stop Vibe Coding. Start Getting Customers. — Reference Strategystop-vibe-coding-start-getting-customers-reference.md2026-04-0912 min readDocument de reference dedie a la these exposee dans cet episode sur la facon de transformer des projets AI "vibe coded" en business capables d'obtenir de vrais
Field Growth Rep — Candidate Briefstreet-team-candidate-brief.md2026-04-2010 min readYou walk into target clinics, speak to the owner or clinic manager, show them something specific about their own booking flow, and book a short call with John.
Independent Contractor Agreementstreet-team-independent-contractor-template.md2026-04-206 min readAurea Growth - Street Team Operator
UGC AI Generation Best Practicesugc-ai-generation-best-practices-2026-03-29.md2026-04-209 min readDate: 2026-03-29
Universal Directory Setupuniversal-directory-setup.md2026-04-201 min readThis dashboard creates a single interactive HTML index for the full Aurea Growth root.
Upwork Brief — UGC Actors for Aurea Meta Campaignupwork-ugc-meta-actors-brief.md2026-04-204 min readI am looking for UGC-style actors/creators to record short face-to-camera Meta ad creatives for an aesthetics-clinic campaign.
Vapi + GoHighLevel Best Practicesvapi-ghl-best-practices-2026-03-31.md2026-05-0429 min readPlain-English reference for what we learned while testing Vapi with GoHighLevel / LeadConnector.
Aurea AI Outbound Routing Systemvapi-outbound-routing-system.md2026-04-2045 min readLast updated: 2026-04-14
Aurea Growth SOP — Building Websites & Landing Pages with Codex/Claude Code, GHL Logic, and External Hostingwebsite-landing-page-ghl-external-hosting-sop.md2026-05-1535 min readLast updated: 2026-05-13 (revised 2026-05-15: added smart-quote HTML attribute bug to Section 7 and Technical QA; revised 2026-05-13: added GHL iframe performan