Tutorial March 31, 2026 10 min read

How to Automate Microsoft Clarity Reports with AI

Most teams install Clarity, check the dashboard once, and then forget about it. The data keeps flowing, but nobody looks at it. Here's how to build an automated system that collects Clarity data, feeds it to an LLM, and sends you a weekly email with actual, actionable insights.

Why Manual Clarity Reporting Wastes Time

The typical Clarity workflow looks like this: someone on the team logs into the dashboard every few weeks, scrolls through heatmaps and session recordings for 30 minutes, and walks away with a vague sense that "things seem okay." No notes. No comparisons. No action items.

This approach has three problems:

  1. Inconsistency: Without a regular schedule, you miss regressions until they become obvious in conversion data
  2. No historical context: The dashboard shows the current state, not trends over time
  3. Analysis paralysis: Heatmaps and recordings are overwhelming without a framework for prioritization

Automated reporting solves all three. Data is collected every day, analyzed every week, and delivered with specific recommendations -- no dashboard login required.

Architecture Overview

The automated pipeline has four components:

Daily (cron):     Clarity API  -->  SQLite/Postgres
Weekly (cron):    SQLite  -->  LLM Analysis  -->  Email Report

Components:
1. collect.py   - Fetches daily metrics from Clarity API
2. clarity.db   - Stores per-page metrics over time
3. report.py    - Queries 7 days, sends to LLM, emails the report
4. cron         - Schedules both scripts

Step 1: Daily Data Collection

The collection script runs once daily and stores that day's metrics. This is the foundation -- without historical data, the AI has nothing to compare against.

import requests
import sqlite3
import os
from datetime import date

def collect():
    # Initialize database
    conn = sqlite3.connect("clarity.db")
    conn.execute("""
        CREATE TABLE IF NOT EXISTS metrics (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            date TEXT NOT NULL,
            page_url TEXT,
            sessions INTEGER,
            users INTEGER,
            scroll_depth REAL,
            active_time REAL,
            dead_clicks INTEGER,
            rage_clicks INTEGER,
            quick_backs INTEGER,
            excessive_scrolls INTEGER,
            UNIQUE(date, page_url)
        )
    """)

    # Fetch from Clarity API
    response = requests.post(
        "https://www.clarity.ms/export-data/api/v1/project-live-insights",
        headers={
            "Authorization": f"Bearer {os.environ['CLARITY_API_TOKEN']}",
            "Content-Type": "application/json"
        },
        json={
            "projectId": os.environ["CLARITY_PROJECT_ID"],
            "numOfDays": 1
        }
    )
    response.raise_for_status()
    results = response.json().get("results", [])

    # Store each page's metrics
    today = date.today().isoformat()
    for row in results:
        conn.execute("""
            INSERT OR IGNORE INTO metrics
            (date, page_url, sessions, users, scroll_depth,
             active_time, dead_clicks, rage_clicks,
             quick_backs, excessive_scrolls)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        """, (
            today, row.get("url"), row.get("totalSessionCount"),
            row.get("distinctUserCount"), row.get("scrollDepth"),
            row.get("activeTime"), row.get("deadClickCount"),
            row.get("rageClickCount"), row.get("quickBackCount"),
            row.get("excessiveScrollCount")
        ))

    conn.commit()
    print(f"Collected {len(results)} pages for {today}")

if __name__ == "__main__":
    collect()

Tip: Use INSERT OR IGNORE with a unique constraint on (date, page_url). This makes the script idempotent -- running it twice in one day won't create duplicates.

Step 2: Weekly Report Generation

The report script queries 7 days of stored data, formats it into a structured prompt, sends it to an LLM, and emails the result.

Query the Data

import sqlite3
from datetime import date, timedelta

def get_weekly_data():
    conn = sqlite3.connect("clarity.db")
    week_ago = (date.today() - timedelta(days=7)).isoformat()

    # Per-page summary for the week
    rows = conn.execute("""
        SELECT
            page_url,
            SUM(sessions) as total_sessions,
            AVG(scroll_depth) as avg_scroll,
            SUM(dead_clicks) as dead_clicks,
            SUM(rage_clicks) as rage_clicks,
            SUM(quick_backs) as quick_backs,
            AVG(active_time) as avg_active_time
        FROM metrics
        WHERE date >= ?
        GROUP BY page_url
        ORDER BY total_sessions DESC
        LIMIT 20
    """, (week_ago,)).fetchall()

    return rows

Analyze with an LLM

The key to useful AI analysis is a well-structured prompt. Don't just dump numbers -- provide context about what each metric means and what you want the AI to look for.

from openai import OpenAI

def analyze_with_ai(data_rows):
    client = OpenAI()

    # Format data as a readable table
    data_text = "Page | Sessions | Scroll% | Dead Clicks | Rage Clicks | Quick Backs | Active Time\n"
    data_text += "-" * 90 + "\n"
    for row in data_rows:
        data_text += f"{row[0]} | {row[1]} | {row[2]:.0f}% | {row[3]} | {row[4]} | {row[5]} | {row[6]:.1f}s\n"

    prompt = f"""Analyze this week's Microsoft Clarity data for a website.
Focus on:
1. Pages with high frustration signals (rage clicks, dead clicks, quick-backs)
2. Pages with unusually low scroll depth (users not engaging)
3. Week-over-week changes if visible
4. Specific, actionable recommendations (not generic advice)

Data:
{data_text}

Respond with:
- Executive Summary (2-3 sentences)
- Top Issues (ranked by impact)
- Recommendations (specific actions with expected impact)
- Pages Performing Well (positive signals)"""

    response = client.chat.completions.create(
        model="gpt-4.1-mini",
        messages=[
            {"role": "system", "content": "You are a UX analyst specializing in web analytics. Be specific and actionable. Avoid generic advice."},
            {"role": "user", "content": prompt}
        ],
        temperature=0.3
    )

    return response.choices[0].message.content

Info: Use a low temperature (0.2-0.4) for analytical tasks. This keeps the LLM focused on the data rather than generating creative but unfounded interpretations. GPT-4.1-mini works well for this -- you don't need the full GPT-4 for structured data analysis.

Send the Email

Use any transactional email service. Here's an example with Resend:

import requests
import os

def send_report(analysis_text, data_rows):
    # Convert analysis to HTML
    html_body = f"""
    <html>
    <body style="font-family: sans-serif; max-width: 600px; margin: 0 auto;">
        <h1>Weekly Clarity Report</h1>
        <pre style="white-space: pre-wrap;">{analysis_text}</pre>
        <hr>
        <p style="color: #666; font-size: 12px;">
            Generated automatically from {len(data_rows)} pages of Clarity data.
        </p>
    </body>
    </html>
    """

    requests.post(
        "https://api.resend.com/emails",
        headers={
            "Authorization": f"Bearer {os.environ['RESEND_API_KEY']}",
            "Content-Type": "application/json"
        },
        json={
            "from": "reports@yourdomain.com",
            "to": os.environ["REPORT_EMAIL"],
            "subject": f"Clarity Weekly Report - {date.today().isoformat()}",
            "html": html_body
        }
    )

Step 3: Schedule with Cron

Two cron entries handle the entire pipeline:

# Collect data daily at 7 AM
0 7 * * * cd /path/to/project && source .env && python collect.py >> logs/collect.log 2>&1

# Generate and send weekly report every Monday at 8 AM
0 8 * * 1 cd /path/to/project && source .env && python report.py >> logs/report.log 2>&1

Warning: Always redirect cron output to a log file. Silent failures are the most common reason automated pipelines stop working. Check your logs weekly, or better yet, add error alerting.

Making the AI Analysis Actually Useful

The difference between a useless AI report and a valuable one is entirely in the prompt. Here are the patterns that work:

Include Context

Tell the LLM what your site does, what your key conversion pages are, and what you changed recently. "Rage clicks on /checkout increased 40%" is more useful when the AI knows you just redesigned the checkout flow.

Request Specific Formats

Ask for prioritized lists, not paragraphs. "Rank issues by estimated impact on conversions" gives you a clear action list. "Analyze the data" gives you a wall of text.

Provide Benchmarks

If you have previous weeks' data, include it in the prompt. "Last week's rage clicks on /pricing: 45. This week: 120" lets the AI flag the 167% increase rather than just reporting the current number.

DIY vs. ClarityInsights

Building this pipeline yourself is entirely feasible -- the code above is production-ready with minor modifications. But there are trade-offs:

Aspect DIY Pipeline ClarityInsights
Setup time 2-4 hours 5 minutes
Maintenance You handle API changes, errors, server uptime Managed
AI prompt quality Depends on your prompt engineering Tuned for Clarity data specifically
Week-over-week comparison Build it yourself Built-in
Multi-project Duplicate scripts per project Add projects in dashboard
Cost Server + OpenAI API (~$2/month) Subscription
Customization Full control Predefined report format

If you're a developer comfortable with Python and cron, the DIY route works well for a single project. If you manage multiple sites, or if you want polished reports without maintaining infrastructure, ClarityInsights handles the entire pipeline.

Going Further

Once the basic pipeline is running, consider these enhancements:

Stop analyzing Clarity data manually

ClarityInsights sends you AI-powered weekly reports with per-page analysis, frustration signals, and prioritized recommendations.

Join the Waitlist