Track lead-to-MQL conversion rate by source and report to Slack using a Claude Code skill

low complexityCost: Usage-based
Compatible agents

This skill works with any agent that supports the Claude Code skills standard, including Claude Code, Claude Cowork, OpenAI Codex, and Google Antigravity.

Prerequisites
  • One of the agents listed above
  • HubSpot private app with crm.objects.contacts.read scope
  • Slack bot with chat:write permission, added to the target channel
  • Lifecycle stages configured in HubSpot (Lead, Marketing Qualified Lead)
Environment Variables
# HubSpot private app token (Settings > Integrations > Private Apps)
HUBSPOT_ACCESS_TOKEN=your_value_here
# Slack bot token starting with xoxb- (chat:write scope required)
SLACK_BOT_TOKEN=your_value_here
# Slack channel ID starting with C (right-click channel > View channel details)
SLACK_CHANNEL_ID=your_value_here

Why a Claude Code skill?

The other approaches in this guide are deterministic: they run the same conversion report every time. An Claude Code skill is different. You tell Claude what you want in plain language, and the skill gives it enough context to do it reliably.

That means you can say:

  • "Post this week's lead-to-MQL conversion report to Slack"
  • "Show me just paid source conversion for the last 30 days"
  • "Which source had the best MQL conversion rate this month?"

The skill contains workflow guidelines, API reference materials, and a message template that the agent reads on demand. When you invoke the skill, Claude reads these files, writes a script on the fly, runs it, and reports results. If you ask for something different next time — a different time window, a specific source filter, campaign-level breakdown — the agent adapts without you touching any code.

How it works

The skill directory has three parts:

  1. SKILL.md — workflow guidelines telling the agent what steps to follow, which env vars to use, and what pitfalls to avoid
  2. references/ — HubSpot API patterns (contacts search endpoint, lifecycle stage filtering, source property details) so the agent calls the right APIs with the right parameters
  3. templates/ — a Slack Block Kit template so conversion reports are consistently formatted across runs

When invoked, the agent reads SKILL.md, consults the reference and template files as needed, writes a Python script, executes it, and reports what it posted. The reference files act as guardrails — the agent knows exactly which endpoints to hit and what the responses look like, so it doesn't have to guess.

What is a Claude Code skill?

An Claude Code skill is a reusable command you add to your project that Claude Code can run on demand. Skills live in a .claude/skills/ directory and are defined by a SKILL.md file that tells the agent what the skill does, when to run it, and what tools it's allowed to use.

In this skill, the agent doesn't run a pre-written script. Instead, SKILL.md provides workflow guidelines and points to reference files — API documentation, message templates — that the agent reads to generate and execute code itself. This is the key difference from a traditional script: the agent can adapt its approach based on what you ask for while still using the right APIs and message formats.

Once installed, you can invoke a skill as a slash command (e.g., /conversion-report), or the agent will use it automatically when you give it a task where the skill is relevant. Skills are portable — anyone who clones your repo gets the same commands.

Step 1: Create the skill directory

mkdir -p .claude/skills/conversion-report/{templates,references}

This creates the layout:

.claude/skills/conversion-report/
├── SKILL.md                          # workflow guidelines + config
├── templates/
│   └── slack-conversion-report.md    # Block Kit template for Slack messages
└── references/
    └── hubspot-contacts-api.md       # HubSpot API patterns

Step 2: Write the SKILL.md

Create .claude/skills/conversion-report/SKILL.md:

---
name: conversion-report
description: Generate a lead-to-MQL conversion report by source from HubSpot and post it to Slack
disable-model-invocation: true
allowed-tools: Bash, Read
---
 
## Goal
 
Query HubSpot for leads created in the last 7 days, identify which ones reached MQL status, calculate conversion rates by source, and post a formatted report to Slack.
 
## Configuration
 
Read these environment variables:
 
- `HUBSPOT_ACCESS_TOKEN` — HubSpot private app token (required)
- `SLACK_BOT_TOKEN` — Slack bot token starting with xoxb- (required)
- `SLACK_CHANNEL_ID` — Slack channel ID starting with C (required)
 
Default lookback window: 7 days (last Monday to this Monday). The user may request a different window.
 
## Workflow
 
1. Validate that all required env vars are set. If any are missing, print which ones and exit.
2. Compute the date range: 7-day lookback from today midnight UTC, in milliseconds.
3. Search for all contacts created in the period using the HubSpot CRM Search API. See `references/hubspot-contacts-api.md` for the endpoint and request format.
4. Search for MQLs from the same period by adding a `lifecyclestage = marketingqualifiedlead` filter.
5. Group both sets by `hs_analytics_source`. Calculate per-source and overall conversion rates.
6. Post the report to Slack using the Block Kit format in `templates/slack-conversion-report.md`.
7. Print a summary of total leads, MQLs, and overall conversion rate.
 
## Important notes
 
- HubSpot lifecycle stage values are **lowercase with no spaces**: use `marketingqualifiedlead`, not `Marketing Qualified Lead`.
- Timestamps must be in Unix **milliseconds** (13 digits). Passing seconds (10 digits) returns zero results with no error.
- The Search API returns max 100 results per request. Use the `after` cursor from `paging.next.after` to paginate if needed.
- Contacts without an `hs_analytics_source` property (manually created, API imports) should be grouped as `UNKNOWN`.
- Filter on `createdate` for when the lead was created, not `hs_timestamp`.
- Use the `requests` library for HTTP calls and `slack_sdk` for Slack. Install them with pip if needed.

Understanding the SKILL.md

Unlike the script-based approach, this SKILL.md doesn't contain a Run: command pointing to a script. Instead, it provides:

SectionPurpose
GoalTells the agent what outcome to produce
ConfigurationWhich env vars to read and what defaults to use
WorkflowNumbered steps with pointers to reference files
Important notesNon-obvious context that prevents common mistakes

The allowed-tools: Bash, Read setting lets the agent both read reference files and execute code. The agent writes its own script based on the workflow steps and reference materials.

Step 3: Add reference files

templates/slack-conversion-report.md

Create .claude/skills/conversion-report/templates/slack-conversion-report.md:

# Slack Conversion Report Template
 
Use this Block Kit structure for the weekly lead-to-MQL conversion report.
 
## Block Kit JSON
 
```json
{
  "channel": "<SLACK_CHANNEL_ID>",
  "text": "Weekly Lead-to-MQL Conversion Report",
  "blocks": [
    {
      "type": "header",
      "text": {
        "type": "plain_text",
        "text": "📈 Weekly Lead-to-MQL Conversion Report"
      }
    },
    {
      "type": "section",
      "fields": [
        {
          "type": "mrkdwn",
          "text": "*Total Leads*\n<total_leads>"
        },
        {
          "type": "mrkdwn",
          "text": "*Total MQLs*\n<total_mqls>"
        },
        {
          "type": "mrkdwn",
          "text": "*Overall Conversion*\n<overall_rate>%"
        }
      ]
    },
    {
      "type": "divider"
    },
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "*Conversion by Source*\n<source_lines>"
      }
    },
    {
      "type": "context",
      "elements": [
        {
          "type": "mrkdwn",
          "text": "Last 7 days | Generated <date_string>"
        }
      ]
    }
  ]
}
```
 
## Source line format
 
Each source line should follow this format:
`• *SOURCE_NAME*: X leads → Y MQLs (Z%)`
 
Sort sources by lead count descending.
 
## Notes
 
- The top-level `text` field is required by Slack as a fallback for notifications.
- Sources without an `hs_analytics_source` property should be labeled `UNKNOWN`.

references/hubspot-contacts-api.md

Create .claude/skills/conversion-report/references/hubspot-contacts-api.md:

# HubSpot Contacts Search API Reference
 
## Search for contacts
 
Use the CRM Search API to find contacts within a date range, optionally filtered by lifecycle stage.
 
**Request:**
 
```
POST https://api.hubapi.com/crm/v3/objects/contacts/search
Authorization: Bearer <HUBSPOT_ACCESS_TOKEN>
Content-Type: application/json
```
 
**Body (all leads in period):**
 
```json
{
  "filterGroups": [
    {
      "filters": [
        {
          "propertyName": "createdate",
          "operator": "GTE",
          "value": "<start_timestamp_ms>"
        },
        {
          "propertyName": "createdate",
          "operator": "LT",
          "value": "<end_timestamp_ms>"
        }
      ]
    }
  ],
  "properties": ["hs_analytics_source", "lifecyclestage", "createdate"],
  "limit": 100
}
```
 
**Body (MQLs only — add lifecycle stage filter):**
 
```json
{
  "filterGroups": [
    {
      "filters": [
        {
          "propertyName": "createdate",
          "operator": "GTE",
          "value": "<start_timestamp_ms>"
        },
        {
          "propertyName": "createdate",
          "operator": "LT",
          "value": "<end_timestamp_ms>"
        },
        {
          "propertyName": "lifecyclestage",
          "operator": "EQ",
          "value": "marketingqualifiedlead"
        }
      ]
    }
  ],
  "properties": ["hs_analytics_source", "lifecyclestage", "createdate"],
  "limit": 100
}
```
 
- `value` for timestamps is a Unix timestamp in **milliseconds** (multiply seconds by 1000).
- `limit` max is 100. If there are more results, use the `after` cursor from `paging.next.after` to paginate.
- Lifecycle stage values are **lowercase with no spaces**: `marketingqualifiedlead`, not `Marketing Qualified Lead`.
 
**Response shape:**
 
```json
{
  "total": 124,
  "results": [
    {
      "id": "12345",
      "properties": {
        "hs_analytics_source": "ORGANIC_SEARCH",
        "lifecyclestage": "marketingqualifiedlead",
        "createdate": "2026-03-01T10:30:00.000Z"
      }
    }
  ],
  "paging": {
    "next": {
      "after": "100"
    }
  }
}
```
 
## Common source values
 
HubSpot's `hs_analytics_source` property uses these standard values:
- `ORGANIC_SEARCH` — search engine traffic
- `PAID_SEARCH` — paid search ads
- `DIRECT_TRAFFIC` — direct visits
- `REFERRALS` — referral links
- `SOCIAL_MEDIA` — social media traffic
- `EMAIL_MARKETING` — email campaigns
- `OTHER_CAMPAIGNS` — other tracked campaigns
- `OFFLINE` — offline sources
- Null/missing — contacts created manually or via API without tracking

Step 4: Test the skill

Invoke the skill conversationally:

/conversion-report

Claude will read the SKILL.md, check the reference files, write a script, install any missing dependencies, run it, and report the results. A typical run looks like:

Fetching leads created in last 7 days... found 741 contacts
Fetching MQLs from same period... found 98 MQLs
Calculating conversion by source...
Posting report to Slack...
Done. 741 leads → 98 MQLs (13.2% overall conversion).
Top source: Referral at 36.8% conversion.

What the Slack report looks like

What you'll get
#marketing-ops
Conversion Botapp9:41 AM

📈 Weekly Lead-to-MQL Conversion Report

Period: Feb 24 – Mar 2

Organic Search — 124 leads → 31 MQLs (25.0%)

Paid Ads — 487 leads → 34 MQLs (7.0%)

Referral — 38 leads → 14 MQLs (36.8%)

Webinar — 92 leads → 19 MQLs (20.7%)

Total: 741 leads → 98 MQLs (13.2%)

Because the agent generates code on the fly, you can also make ad hoc requests:

  • "Show me just paid source conversion for the last 30 days" — the agent adjusts the lookback and filters by source
  • "Which source had the best MQL conversion this month?" — the agent changes the ranking metric
  • "Break down PAID_SEARCH by UTM campaign" — the agent adds hs_analytics_source_data_1 to the query
Test with real data

Make sure lifecycle stages are configured in HubSpot and at least some contacts have been created in the lookback window. If no contacts exist, the skill correctly reports "No leads found" — that's not an error.

Step 5: Schedule it (optional)

Option A: Cron + Claude CLI

# Run every Monday at 9 AM
0 9 * * 1 cd /path/to/your/project && claude -p "Run /conversion-report" --allowedTools 'Bash(*)' 'Read(*)'

Option B: GitHub Actions + Claude

name: Weekly Conversion Report
on:
  schedule:
    - cron: '0 14 * * 1'  # 9 AM ET = 2 PM UTC, Mondays only
  workflow_dispatch: {}
jobs:
  report:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: anthropics/claude-code-action@v1
        with:
          prompt: "Run /conversion-report"
          allowed_tools: "Bash(*),Read(*)"
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          HUBSPOT_ACCESS_TOKEN: ${{ secrets.HUBSPOT_ACCESS_TOKEN }}
          SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
          SLACK_CHANNEL_ID: ${{ secrets.SLACK_CHANNEL_ID }}

Option C: Cowork Scheduled Tasks

Claude Desktop's Cowork supports built-in scheduled tasks. Open a Cowork session, type /schedule, and configure the cadence — hourly, daily, weekly, or weekdays only. Each scheduled run has full access to your connected tools, plugins, and MCP servers.

Scheduled tasks only run while your computer is awake and Claude Desktop is open. If a run is missed, Cowork executes it automatically when the app reopens. For always-on scheduling, use GitHub Actions (Option B) instead. Available on all paid plans (Pro, Max, Team, Enterprise).

GitHub Actions cron uses UTC

0 14 * * 1 runs at 2 PM UTC (9 AM ET) on Mondays. GitHub Actions cron may also have up to 15 minutes of delay. For time-sensitive weekly posts, use cron on your own server.

Troubleshooting

When to use this approach

  • You want conversational flexibility — ad hoc queries like "show me paid source conversion this month" alongside weekly reports
  • You want on-demand reports during marketing meetings or budget reviews
  • You're already using Claude Code and want skills that integrate with your workflow
  • You want to run tasks in the background via Claude Cowork while focusing on other work
  • You prefer guided references over rigid scripts — the agent adapts while staying reliable

When to switch approaches

  • You need reliable weekly scheduling with zero manual intervention → use n8n or the code approach
  • You want a no-code setup with a visual builder → use Make
  • You need reports running 24/7 with zero cost and no LLM usage → use the Code + Cron approach

Common questions

Why not just use a script?

A script runs the same way every time. The Claude Code skill adapts to what you ask — different lookback windows, source-specific breakdowns, campaign-level analysis. The reference files ensure it calls the right APIs even when improvising, so you get flexibility without sacrificing reliability.

Does this use Claude API credits?

Yes. The agent reads skill files and generates code each time. Typical cost is $0.01-0.05 per invocation depending on the number of contacts returned and how much the agent needs to read. The HubSpot and Slack APIs themselves are free.

Can I run this skill on a schedule without a server?

Yes. GitHub Actions (Option B in Step 5) runs Claude on a cron schedule using GitHub's infrastructure. The free tier includes 2,000 minutes/month — more than enough for weekly runs.

Can I track conversion to stages beyond MQL?

Yes. Ask the agent to add filters for salesqualifiedlead, opportunity, or customer. The reference file documents the lifecycle stage property format, so the agent knows to use lowercase values without spaces.

Cost

  • Claude API — $0.01-0.05 per invocation (the agent reads files and generates code)
  • HubSpot API — included in all plans, no per-call cost
  • Slack API — included in all plans, no per-call cost
  • GitHub Actions (if scheduled) — free tier includes 2,000 minutes/month

Looking to scale your AI operations?

We build and optimize automation systems for mid-market businesses. Let's discuss the right approach for your team.