Get Qualified Leads from Luma Events
End-to-end lead prospecting from Luma events.
How to use it
- Hit Copy the whole skill.
- Claude: ⋯ → Download .md, then Customize → Skills → Add → Upload skill.
ChatGPT: make a Project and paste it into Instructions.
Neither? Paste it at the top of a new chat — it works for that chat. - Describe your job in plain words. The AI follows the skill from there.
npx degit gooseworks-ai/goose-skills/skills/lead-generation/composites/get-qualified-leads-from-luma#main ~/.claude/skills/get-qualified-leads-from-lumaFor one project only, change the path to .claude/skills/get-qualified-leads-from-luma.
Not working?
- Check which app you pasted it into — the steps above name the right one.
- Some skills need the paid tier of Claude or ChatGPT.
Paste into Claude, ChatGPT or Cursor.
Show the full text230 lines
Get Qualified Leads from Luma Events
Search Luma for events by topic and location, extract all attendees and hosts, qualify them against your ICP, export to a Google Sheet, and send a Slack alert with the top leads.
This is a 5-step pipeline that chains together luma-event-attendees, lead-qualification, Google Sheets output, and Slack alerting.
Step 0: Clarify Search Parameters
Before doing anything, make sure you have clear answers to these questions. If the user's prompt already covers them, skip ahead. Otherwise, ask:
- Location — Where should events be? (e.g., "San Francisco", "New York", "London")
- Topics/Keywords — What event topics? Suggest 3-5 keyword variations to maximize coverage. For example, if the user says "growth marketing", also suggest: "GTM", "demand gen", "startup growth", "growth hacking", "marketing leadership"
- Timeframe — How recent should the events be? (e.g., "past 2 weeks", "past month", "this quarter"). Default to past 30 days if the user doesn't specify. Luma search can return events from months or years ago, so always confirm a timeframe to avoid stale results.
- Qualification prompt — Does the user have an existing qualification prompt in
skills/lead-qualification/qualification-prompts/? If not, what's their ICP at a high level? (Can uselead-qualificationintake mode to build one) - Slack channel/webhook — Where should the alert go? A webhook URL or Slack channel name?
- How many top leads in the Slack alert? (default: 5)
Present these as a numbered list. The user can answer in one shot.
Step 1: Search Luma and Extract Attendees
Use the luma-event-attendees skill with multiple keyword variations to maximize coverage.
Run parallel searches
Generate 3-5 keyword variations combining the user's topic with their location. Run them all in parallel:
# Run each search variation in parallel
python3 skills/luma-event-attendees/scripts/scrape_event.py --search "AI San Francisco" --output /tmp/luma_search_1.csv
python3 skills/luma-event-attendees/scripts/scrape_event.py --search "Growth Marketing San Francisco" --output /tmp/luma_search_2.csv
python3 skills/luma-event-attendees/scripts/scrape_event.py --search "GTM San Francisco" --output /tmp/luma_search_3.csv
Filter by timeframe
After collecting results, filter out events outside the user's specified timeframe using the event_date column. Luma search returns events from all time periods, so this step is essential to avoid stale leads. If no timeframe was specified, default to the past 30 days.
Deduplicate
Merge and deduplicate by name (case-insensitive). Handle None names gracefully — skip entries with no name.
Save the deduplicated result as a CSV:
/tmp/luma_all_attendees.csv
Report to the user:
- How many total results before dedup
- How many unique people after dedup
- How many have LinkedIn profiles
- How many events were covered
Step 2: Save Attendee Data to CSV
Work with CSVs throughout the pipeline — Google Sheets creation happens only at the end (Step 4) because writing large datasets to Sheets mid-process is slow and error-prone.
The CSV from Step 1 (/tmp/luma_all_attendees.csv) is your working file. Columns should include:
| name | event_role | bio | title | company | linkedin_url | twitter_url | instagram_url | website_url | username | event_name | event_date | event_url |
|---|
Step 3: Qualify Leads
Use the lead-qualification skill (Mode 2: reuse prompt) to qualify all attendees.
Prepare batches
- Read the qualification prompt from the file the user specified (e.g.,
skills/lead-qualification/qualification-prompts/ai-event-attendees-gtm.md) - Split attendees into batches of ~15-20 leads each
- For each lead, include: id (row number), name, event_role, bio, title, company, linkedin_url, event_name
Run parallel qualification
Launch all batches simultaneously using the Task tool with sonnet model subagents:
Task: "Qualify leads batch 1/N"
- Include the full qualification prompt text
- Include the batch of leads as JSON
- Ask for output as JSON array: [{id, name, qualified, confidence, reasoning}]
Task: "Qualify leads batch 2/N"
... (launch ALL at once)
Merge results
- Collect all batch results
- Merge into a single JSON array, preserving original IDs
- Sort qualified leads by confidence (High first, then Medium, then Low)
- Save results:
/tmp/all_qual_results.json— all 195 results/tmp/qualified_leads.json— only qualified leads, sorted by confidence
Report to the user:
- Total leads processed
- Qualified count and percentage
- Breakdown by confidence level (High / Medium / Low)
- Top disqualification reasons
Step 4: Create Google Sheet with Results
Now create the Google Sheet with all data — both raw attendees and qualification results.
Use Rube MCP for Google Sheets
- Use
RUBE_SEARCH_TOOLSto find Google Sheets tools (search for "google sheet create") - Create a new sheet named:
Luma Leads - [Topic] - [Date] - Sheet 1 ("All Attendees"): Write all attendee rows with original columns PLUS:
Qualified— Yes / NoConfidence— High / Medium / LowReasoning— 2-3 sentence explanation
- Sheet 2 ("Qualified Leads"): Only qualified leads, sorted by confidence
Writing strategy for large datasets
The Google Sheets API can be slow for large datasets. Use this approach:
- Write the header row first
- Write data in chunks of 50 rows using batch update operations
- If a chunk fails, retry once before moving on
Fallback
If Rube/Sheets is unavailable, save as CSV:
/tmp/luma_qualified_leads_[date].csv
Present the Google Sheet link (or CSV path) to the user.
Step 5: Send Slack Alert
Send a formatted Slack message with the top N qualified leads (default: 5, or whatever the user specified).
If the user provided a webhook URL
Use Python with urllib.request to POST to the webhook:
import json
import urllib.request
message = {
"blocks": [
{"type": "header", "text": {"type": "plain_text", "text": "Top N Qualified Leads from [Topic] Events"}},
{"type": "section", "text": {"type": "mrkdwn", "text": "_From X attendees across Y events, Z qualified (P%). Here are the top N:_"}},
# For each lead:
{"type": "section", "text": {"type": "mrkdwn", "text": "*1. Name* [Confidence]\n LinkedIn: url\n Bio: ...\n Why: reasoning"}},
{"type": "divider"},
# Link to spreadsheet at the bottom
{"type": "section", "text": {"type": "mrkdwn", "text": "<sheet_url|View full spreadsheet> (X attendees, Y qualified)"}}
]
}
req = urllib.request.Request(webhook_url, data=json.dumps(message).encode(), headers={"Content-Type": "application/json"})
urllib.request.urlopen(req)
If the user wants Slack via Rube MCP
Use RUBE_SEARCH_TOOLS to find Slack tools, then send via SLACK_SEND_MESSAGE or similar.
Message format
The Slack alert should include for each top lead:
- Name and confidence level
- LinkedIn URL (clickable)
- Bio — one-line summary
- Why — the qualification reasoning (truncated to ~150 chars if needed)
End with a link to the full Google Sheet.
Cost Estimate
| Component | Cost |
|---|---|
| Luma scraper (Apify) | $29/mo flat subscription |
| LinkedIn enrichment (optional) | ~$0.03 per 100 leads |
| Google Sheets | Free (via Rube/Composio) |
| LLM qualification | ~$0.10-0.30 per run (depends on batch size) |
| Slack webhook | Free |
Typical run: ~200 attendees across 3-5 search variations costs essentially just the Apify subscription + a few cents in LLM tokens.
Example Prompts
Quick run with existing prompt:
"Find qualified leads from AI and growth events in SF. Use the ai-event-attendees-gtm qualification prompt. Send top 5 to Slack webhook: https://hooks.slack.com/..."
Full specification:
"Search Luma for startup, SaaS, and AI events in New York. Extract all attendees. Qualify them against our Series A founders ICP. Put everything in a Google Sheet and Slack me the top 10."
Minimal (triggers clarifying questions):
"Find me leads from SF tech events"
Troubleshooting
Apify token not set
export APIFY_API_TOKEN="your_token"
# Or check skills/luma-event-attendees/.env
No guests found
Some Luma events have show_guest_list disabled. The Apify scraper can still get featured guests, but full attendee lists may not be available for all events.
Google Sheets writing is slow
This is normal for large datasets. The skill writes in 50-row chunks. If it's too slow or fails, results are always available as CSV in /tmp/.
Slack webhook returns error
Verify the webhook URL is correct and the Slack app is still installed in the workspace. Test with a simple curl:
curl -X POST -H 'Content-Type: application/json' -d '{"text":"test"}' YOUR_WEBHOOK_URL
| 1 | |
| 2 | name get-qualified-leads-from-luma |
| 3 | version 1.0.0 |
| 4 | description > |
| 5 | End-to-end lead prospecting from Luma events. Searches Luma for events by topic and location, |
| 6 | extracts all attendees/hosts, qualifies them against a qualification prompt, outputs results |
| 7 | to a Google Sheet, and sends a Slack alert with top leads. Use this skill whenever someone |
| 8 | wants to find qualified leads from events, prospect event attendees, or run an event-based |
| 9 | lead gen workflow. Also triggers for "find people at events and qualify them" or |
| 10 | "who's attending X events that matches our ICP." |
| 11 | tags [lead-generation] |
| 12 | |
| 13 | |
| 14 | # Get Qualified Leads from Luma Events |
| 15 | |
| 16 | Search Luma for events by topic and location, extract all attendees and hosts, qualify them against your ICP, export to a Google Sheet, and send a Slack alert with the top leads. |
| 17 | |
| 18 | This is a 5-step pipeline that chains together `luma-event-attendees`, `lead-qualification`, Google Sheets output, and Slack alerting. |
| 19 | |
| 20 | ## Step 0: Clarify Search Parameters |
| 21 | |
| 22 | Before doing anything, make sure you have clear answers to these questions. If the user's prompt already covers them, skip ahead. Otherwise, ask: |
| 23 | |
| 24 | **Location** — Where should events be? (e.g., "San Francisco", "New York", "London") |
| 25 | **Topics/Keywords** — What event topics? Suggest 3-5 keyword variations to maximize coverage. For example, if the user says "growth marketing", also suggest: "GTM", "demand gen", "startup growth", "growth hacking", "marketing leadership" |
| 26 | **Timeframe** — How recent should the events be? (e.g., "past 2 weeks", "past month", "this quarter"). Default to **past 30 days** if the user doesn't specify. Luma search can return events from months or years ago, so always confirm a timeframe to avoid stale results. |
| 27 | **Qualification prompt** — Does the user have an existing qualification prompt in `skills/lead-qualification/qualification-prompts/`? If not, what's their ICP at a high level? (Can use `lead-qualification` intake mode to build one) |
| 28 | **Slack channel/webhook** — Where should the alert go? A webhook URL or Slack channel name? |
| 29 | **How many top leads** in the Slack alert? (default: 5) |
| 30 | |
| 31 | Present these as a numbered list. The user can answer in one shot. |
| 32 | |
| 33 | ## Step 1: Search Luma and Extract Attendees |
| 34 | |
| 35 | Use the `luma-event-attendees` skill with **multiple keyword variations** to maximize coverage. |
| 36 | |
| 37 | ### Run parallel searches |
| 38 | |
| 39 | Generate 3-5 keyword variations combining the user's topic with their location. Run them all in parallel: |
| 40 | |
| 41 | |
| 42 | # Run each search variation in parallel |
| 43 | python3 skills/luma-event-attendees/scripts/scrape_event.py --search "AI San Francisco" --output /tmp/luma_search_1.csv |
| 44 | python3 skills/luma-event-attendees/scripts/scrape_event.py --search "Growth Marketing San Francisco" --output /tmp/luma_search_2.csv |
| 45 | python3 skills/luma-event-attendees/scripts/scrape_event.py --search "GTM San Francisco" --output /tmp/luma_search_3.csv |
| 46 | |
| 47 | |
| 48 | ### Filter by timeframe |
| 49 | |
| 50 | After collecting results, filter out events outside the user's specified timeframe using the `event_date` column. Luma search returns events from all time periods, so this step is essential to avoid stale leads. If no timeframe was specified, default to the past 30 days. |
| 51 | |
| 52 | ### Deduplicate |
| 53 | |
| 54 | Merge and deduplicate by name (case-insensitive). Handle `None` names gracefully — skip entries with no name. |
| 55 | |
| 56 | Save the deduplicated result as a CSV: |
| 57 | |
| 58 | |
| 59 | /tmp/luma_all_attendees.csv |
| 60 | |
| 61 | |
| 62 | Report to the user: |
| 63 | How many total results before dedup |
| 64 | How many unique people after dedup |
| 65 | How many have LinkedIn profiles |
| 66 | How many events were covered |
| 67 | |
| 68 | ## Step 2: Save Attendee Data to CSV |
| 69 | |
| 70 | Work with CSVs throughout the pipeline — Google Sheets creation happens only at the end (Step 4) because writing large datasets to Sheets mid-process is slow and error-prone. |
| 71 | |
| 72 | The CSV from Step 1 (`/tmp/luma_all_attendees.csv`) is your working file. Columns should include: |
| 73 | |
| 74 | | name | event_role | bio | title | company | linkedin_url | twitter_url | instagram_url | website_url | username | event_name | event_date | event_url | |
| 75 | |------|-----------|-----|-------|---------|-------------|-------------|---------------|-------------|----------|------------|------------|-----------| |
| 76 | |
| 77 | ## Step 3: Qualify Leads |
| 78 | |
| 79 | Use the `lead-qualification` skill (Mode 2: reuse prompt) to qualify all attendees. |
| 80 | |
| 81 | ### Prepare batches |
| 82 | |
| 83 | Read the qualification prompt from the file the user specified (e.g., `skills/lead-qualification/qualification-prompts/ai-event-attendees-gtm.md`) |
| 84 | Split attendees into batches of ~15-20 leads each |
| 85 | For each lead, include: id (row number), name, event_role, bio, title, company, linkedin_url, event_name |
| 86 | |
| 87 | ### Run parallel qualification |
| 88 | |
| 89 | Launch all batches simultaneously using the Task tool with `sonnet` model subagents: |
| 90 | |
| 91 | |
| 92 | Task: "Qualify leads batch 1/N" |
| 93 | - Include the full qualification prompt text |
| 94 | - Include the batch of leads as JSON |
| 95 | - Ask for output as JSON array: [{id, name, qualified, confidence, reasoning}] |
| 96 | |
| 97 | Task: "Qualify leads batch 2/N" |
| 98 | ... (launch ALL at once) |
| 99 | |
| 100 | |
| 101 | ### Merge results |
| 102 | |
| 103 | Collect all batch results |
| 104 | Merge into a single JSON array, preserving original IDs |
| 105 | Sort qualified leads by confidence (High first, then Medium, then Low) |
| 106 | Save results: |
| 107 | `/tmp/all_qual_results.json` — all 195 results |
| 108 | `/tmp/qualified_leads.json` — only qualified leads, sorted by confidence |
| 109 | |
| 110 | Report to the user: |
| 111 | Total leads processed |
| 112 | Qualified count and percentage |
| 113 | Breakdown by confidence level (High / Medium / Low) |
| 114 | Top disqualification reasons |
| 115 | |
| 116 | ## Step 4: Create Google Sheet with Results |
| 117 | |
| 118 | Now create the Google Sheet with all data — both raw attendees and qualification results. |
| 119 | |
| 120 | ### Use Rube MCP for Google Sheets |
| 121 | |
| 122 | Use `RUBE_SEARCH_TOOLS` to find Google Sheets tools (search for "google sheet create") |
| 123 | Create a new sheet named: `Luma Leads - [Topic] - [Date]` |
| 124 | **Sheet 1 ("All Attendees"):** Write all attendee rows with original columns PLUS: |
| 125 | `Qualified` — Yes / No |
| 126 | `Confidence` — High / Medium / Low |
| 127 | `Reasoning` — 2-3 sentence explanation |
| 128 | **Sheet 2 ("Qualified Leads"):** Only qualified leads, sorted by confidence |
| 129 | |
| 130 | ### Writing strategy for large datasets |
| 131 | |
| 132 | The Google Sheets API can be slow for large datasets. Use this approach: |
| 133 | Write the header row first |
| 134 | Write data in chunks of 50 rows using batch update operations |
| 135 | If a chunk fails, retry once before moving on |
| 136 | |
| 137 | ### Fallback |
| 138 | |
| 139 | If Rube/Sheets is unavailable, save as CSV: |
| 140 | |
| 141 | /tmp/luma_qualified_leads_[date].csv |
| 142 | |
| 143 | |
| 144 | Present the Google Sheet link (or CSV path) to the user. |
| 145 | |
| 146 | ## Step 5: Send Slack Alert |
| 147 | |
| 148 | Send a formatted Slack message with the top N qualified leads (default: 5, or whatever the user specified). |
| 149 | |
| 150 | ### If the user provided a webhook URL |
| 151 | |
| 152 | Use Python with `urllib.request` to POST to the webhook: |
| 153 | |
| 154 | |
| 155 | import json |
| 156 | import urllib.request |
| 157 | |
| 158 | message = { |
| 159 | "blocks": [ |
| 160 | {"type": "header", "text": {"type": "plain_text", "text": "Top N Qualified Leads from [Topic] Events"}}, |
| 161 | {"type": "section", "text": {"type": "mrkdwn", "text": "_From X attendees across Y events, Z qualified (P%). Here are the top N:_"}}, |
| 162 | # For each lead: |
| 163 | {"type": "section", "text": {"type": "mrkdwn", "text": "*1. Name* [Confidence]\n LinkedIn: url\n Bio: ...\n Why: reasoning"}}, |
| 164 | {"type": "divider"}, |
| 165 | # Link to spreadsheet at the bottom |
| 166 | {"type": "section", "text": {"type": "mrkdwn", "text": "<sheet_url|View full spreadsheet> (X attendees, Y qualified)"}} |
| 167 | ] |
| 168 | } |
| 169 | |
| 170 | req = urllib.request.Request(webhook_url, data=json.dumps(message).encode(), headers={"Content-Type": "application/json"}) |
| 171 | urllib.request.urlopen(req) |
| 172 | |
| 173 | |
| 174 | ### If the user wants Slack via Rube MCP |
| 175 | |
| 176 | Use `RUBE_SEARCH_TOOLS` to find Slack tools, then send via `SLACK_SEND_MESSAGE` or similar. |
| 177 | |
| 178 | ### Message format |
| 179 | |
| 180 | The Slack alert should include for each top lead: |
| 181 | **Name** and confidence level |
| 182 | **LinkedIn URL** (clickable) |
| 183 | **Bio** — one-line summary |
| 184 | **Why** — the qualification reasoning (truncated to ~150 chars if needed) |
| 185 | |
| 186 | End with a link to the full Google Sheet. |
| 187 | |
| 188 | ## Cost Estimate |
| 189 | |
| 190 | | Component | Cost | |
| 191 | |-----------|------| |
| 192 | | Luma scraper (Apify) | $29/mo flat subscription | |
| 193 | | LinkedIn enrichment (optional) | ~$0.03 per 100 leads | |
| 194 | | Google Sheets | Free (via Rube/Composio) | |
| 195 | | LLM qualification | ~$0.10-0.30 per run (depends on batch size) | |
| 196 | | Slack webhook | Free | |
| 197 | |
| 198 | **Typical run:** ~200 attendees across 3-5 search variations costs essentially just the Apify subscription + a few cents in LLM tokens. |
| 199 | |
| 200 | ## Example Prompts |
| 201 | |
| 202 | **Quick run with existing prompt:** |
| 203 | > "Find qualified leads from AI and growth events in SF. Use the ai-event-attendees-gtm qualification prompt. Send top 5 to Slack webhook: https://hooks.slack.com/..." |
| 204 | |
| 205 | **Full specification:** |
| 206 | > "Search Luma for startup, SaaS, and AI events in New York. Extract all attendees. Qualify them against our Series A founders ICP. Put everything in a Google Sheet and Slack me the top 10." |
| 207 | |
| 208 | **Minimal (triggers clarifying questions):** |
| 209 | > "Find me leads from SF tech events" |
| 210 | |
| 211 | ## Troubleshooting |
| 212 | |
| 213 | ### Apify token not set |
| 214 | |
| 215 | export APIFY_API_TOKEN="your_token" |
| 216 | # Or check skills/luma-event-attendees/.env |
| 217 | |
| 218 | |
| 219 | ### No guests found |
| 220 | Some Luma events have `show_guest_list` disabled. The Apify scraper can still get featured guests, but full attendee lists may not be available for all events. |
| 221 | |
| 222 | ### Google Sheets writing is slow |
| 223 | This is normal for large datasets. The skill writes in 50-row chunks. If it's too slow or fails, results are always available as CSV in `/tmp/`. |
| 224 | |
| 225 | ### Slack webhook returns error |
| 226 | Verify the webhook URL is correct and the Slack app is still installed in the workspace. Test with a simple curl: |
| 227 | |
| 228 | curl -X POST -H 'Content-Type: application/json' -d '{"text":"test"}' YOUR_WEBHOOK_URL |
| 229 | |
| 230 |