Growth & Marketing AI
Feeding Website Engagement and AEO Visibility Into Roster Intelligence
Result:
An account-level signals table joining page-view engagement (which pages an identified account's contacts viewed, how recently) with AEO visibility (whether an answer engine referred that traffic at all), refreshed on the same schedule as the ICP and persona scores it sits beside, so a hot ICP fit with zero recent engagement stops looking identical to one where someone from the account read three recipes yesterday.
Problem#
ICP fit and buyer-persona scores both answer who to reach. Neither answers whether now is a good time to reach them. An account can score a 0.9 on fit and still be worth deprioritizing this week if nobody there has looked at the site in three months, and a lower-scoring account can be worth moving up the list if someone there just read four recipes back to back. That signal already exists in a web analytics tool, but it lives as a site-wide traffic number, not attached to any particular account, so it never reaches the roster at all.
Pattern#
- Identify the visitor, not just the session. Identifying a reader at the moment they convert turns an anonymous PostHog session into a person tied to a real email address. The email’s domain is the join key: match it against each account’s known domain to attribute that session’s page views to the account, not just the individual.
- Score engagement the same way as fit and persona. A
signalstable holds one row per account, and a weighted moving average blends recency and volume of that account’s page views: a visit yesterday counts for more than one from last quarter, and three pages in one session count for more than one bounce. This is the same shape as the account-level ICP model and the persona model beside it, applied to a third input. - Add an AEO-visibility column, not just a traffic count. The AEO metadata layer is what makes a page citable by an answer engine in the first place; this pattern just asks whether that citability is converting into referred traffic. Classify each session’s referrer: organic search, a named answer engine, direct, or other, and roll up the answer-engine share per account alongside the raw engagement number. The Search-Console-driven SEO pattern is the lever for improving the organic side of that same referrer mix.
- Refresh on the same schedule as the rest of the roster. A pg_cron job recalculates the signals table hourly or daily, matching the cadence the ICP model already uses, so an account’s signal strength never lags more than a business day behind reality.
- Turn a raw page-view count into a topic-level signal. “This account viewed three pages” is
weaker than “this account viewed three pages about ICP modeling and MCP servers.” A semantic
layer over the site’s own content, one summary, topic list, and embedding per page, makes the
join possible: a page view becomes a set of topics, not just a URL, so the
signalstable can answer which subjects an account is reading, and a similarity search over the embeddings can recommend the next page worth sending them. See the semantic layer that finds related pages by meaning for the exact data shape and the cosine-similarity math behind that search.
Data requirements#
Three things need to exist before this produces anything real:
- Identified sessions: page-view events tied to an email address, not just an anonymous session id (the output of the identify-on-submit pattern above, or any equivalent login/form capture).
- Accounts with a known domain: the same
accountstable the ICP model scores against, with a domain field to match visitor emails against. - Referrer data: whatever the analytics tool already captures for a session’s traffic source, classified into organic search, a named answer engine, direct, or other.
Example prompts#
- “Write a query that joins identified PostHog sessions to accounts by matching the visitor email’s domain against each account’s known domain.”
- “Write a materialized view
account_engagementthat blends session recency and page count per account into a weighted moving average, weighting the last N days more heavily than older activity.” - “Add a referrer classification step that buckets each session into organic search, a named answer engine, direct, or other, and roll up the answer-engine share per account.”
- “Combine the engagement score and the answer-engine share into one
signalstable, one row per account.” - “Set up a pg_cron job to refresh
signalson the same schedule as the existing ICP materialized view.”
The semantic layer, for real#
scripts/build-semantic-layer.mjs is a working implementation of step 5, not just the idea of one.
It reads the site’s own sitemap-index.xml, filters out anything gated (the admin tools, the login
page, and any single-recipient pages behind that same gate), fetches every remaining public page,
and asks Gemini for a short summary and a topic list per page, plus an embedding vector for
similarity search. A scheduled GitHub Actions workflow
(.github/workflows/semantic-layer.yml) runs it weekly against a GEMINI_KEY repo secret and
commits the result to src/data/semantic-layer.json, the same “scheduled script commits a data
file” shape the traffic dashboard already uses. Every public
page on the site gets a row, recipes and every other route alike, so the semantic layer isn’t
scoped to the recipe catalog the way the AEO metadata fields are.
Output#
Wire this in and an account’s ICP fit score stops standing alone. Filtering the roster to
accounts scoring well on both fit and recent engagement, not fit alone, turns “who should I work
this week” into a question the data answers directly, instead of a rep manually cross-referencing
a CRM export against a separate analytics dashboard. Through the roster-intelligence MCP
server’s get_seo_signals tool, that combined number
becomes something an SME can ask for by account name, alongside fit and persona, in the same
conversation.
Variations#
- Weight answer-engine referrals higher than organic search if the AEO positioning matters more to the business than raw traffic volume: two accounts with identical page-view counts aren’t equally validating if only one arrived via a cited answer.
- Fall back to reverse-IP matching for accounts where nobody has ever submitted an identifying form, at the cost of the lower precision that comes with IP-to-company lookups instead of a real email domain.
- Feed content-gap signals back to the content team: an account with high fit and low engagement across every published recipe is a prioritization signal for what to write next, not just a targeting signal for sales.