For the Coop team, to run in Claude Code

One prompt. Every DJ's promo email.

Paste this into Claude Code and it builds the whole DJ promo playbook as a pipeline: your fan exports and festival lineups in, a verified, ranked list of promo emails out, loaded into Mixmax. It starts with the DJs already on your lists, and re-runs for every release.

claude ~/coop-promo-engine
> Build the Coop DJ Promo Engine from dj-promo-engine.md  Asking for EDMTRAIN_API_KEY, HUNTER_API_KEY, SCRAPECREATORS_API_KEY ... 01 fans       your gate, SMS and follower exports, deduped 02 lineups    UKG, bassline and bass house bills, 12 months + announced 03 soundcloud everyone matched to a real profile 04 emails     promo addresses from bios and link pages 05 score      warm + touring in the next 90 days first 06 review     avatar contact sheets checked by eye 07 verify     Hunter, 70% confidence and up 08 export     output/promo_list.csv  +  Mixmax sequence> python run_release.py "LONDON DUB" <dropbox> ukg 

What it does

Eight stages, each one re-runnable.

It's the six-step tutorial from artistinfluence.com, automated, plus the rules we learned scraping at scale. Stages that call an API save everything they fetch, so changing a rule never costs another call.

It never invents an address. Every email in the final list comes from a real source, and the list says which one.

  1. Ingest the fans you already haveEvery Laylo, vault.fm, Hypeddit, droploud, ffm.to and Bandcamp export, plus the SoundCloud followers of every Coop artist. Deduped, with phone numbers dropped.
  2. Pull lineups by APIEDMTrain shows and festivals in UKG, bassline, bass house, dubstep and wonk, the last 12 months plus everything announced.
  3. Match everyone to SoundCloudStraight from the handle when a gate captured it, otherwise a strict name match with a follower floor.
  4. Find the promo emailSoundCloud bio and link pages first, then Instagram, then Facebook, then an LLM lookup that only counts if the address is on a real page. Promo addresses only, no agents or managers.
  5. Flag the DJs and rank themMixes, "DJ" in the bio, real shows. Warm and touring in the next 90 days is priority A. Anyone who downloaded a Coop DJ tool goes first.
  6. Check it by eyeAvatar contact sheets, 36 to a page. Label pages, promoters and fan accounts get past text filters, and a glance at the photos catches them.
  7. VerifyHunter's Email Verifier on every address, keeping 70% confidence and up.
  8. Hand offA send-ready CSV, an optional Airtable base, and a three-touch Mixmax sequence with the tutorial's template. run_release.py redoes it for every release without emailing anyone twice.

What you'll need

Three keys to run it, two to automate the send.

Claude Code asks for each one on the first run and saves them to a local .env file that never leaves your machine.

EDMTrainShow and festival lineups
EDMTRAIN_API_KEY=••••••••••••Required
HunterBulk email verification
HUNTER_API_KEY=••••••••••••Required
ScrapeCreatorsInstagram bios and contact emails
SCRAPECREATORS_API_KEY=••••••••Required
AirtablePush the list to a base
AIRTABLE_TOKEN=pat••••••••Optional
MixmaxLoad contacts into a sequence
MIXMAX_API_TOKEN=••••••••••••Optional
SoundCloudProfiles, bios, followers, tracks
No key neededFree

The master prompt

Copy it, paste it, let it run.

Open an empty folder, start Claude Code, and paste the whole thing. Put your fan exports in input/fans/ as CSVs first. It works through one stage at a time and shows you the results before moving on.

mkdir coop-promo-engine && cd coop-promo-engine && claude
dj-promo-engine.md108 lines
Download .md
# Coop DJ Promo Engine: master prompt for Claude Code

Paste everything below the line into Claude Code, in an empty folder, and let it run. It builds a pipeline that finds touring DJs who fit a Coop release, pulls their real promo email, verifies it, and hands you a send-ready list for Mixmax. It starts with the fans you already have.

What you need before you start:

| Key | Where to get it | What it's for | Cost |
|---|---|---|---|
| `EDMTRAIN_API_KEY` | edmtrain.com/developer | Festival and show lineups by genre and city | Free |
| `HUNTER_API_KEY` | hunter.io, API settings | Bulk email verification | Paid plan for volume |
| `SCRAPECREATORS_API_KEY` | scrapecreators.com | Instagram bios and contact emails | Pay per call |
| `AIRTABLE_TOKEN` + `AIRTABLE_BASE_ID` | airtable.com/create/tokens | Optional: push the final list to a base | Free tier works |
| `MIXMAX_API_TOKEN` | Mixmax settings, Integrations | Optional: load contacts into a sequence | Mixmax plan |

SoundCloud needs no key. The engine reads SoundCloud's public web API the same way the website does.

Your fan exports go in `./input/fans/` as CSV: Laylo, vault.fm, Hypeddit, droploud, ffm.to, Bandcamp, anything with an email, a name, or a SoundCloud handle. Column names can be anything; the engine maps them.

---

You are building the Coop DJ Promo Engine, a local Python pipeline that produces a verified, prioritized list of DJ promo emails for Coop Records releases. Coop's catalog is mostly UK garage, bassline and bass house at 130 to 150 BPM, with some dubstep, wonk bass and future bass from its partner labels. Work in this folder. Use Python 3.11+, `httpx` with async, `pandas`, `rapidfuzz` and `Pillow`. Put secrets in `.env` and never print them.

## How to work

1. Ask me for the API keys in the table at the top of this file, one at a time, and write them to `.env`. Skip Airtable and Mixmax if I say so. Create `.gitignore` with `.env`, `cache/` and `input/`.
2. Build the pipeline as separate stages, each one a script that reads the previous stage's output and writes its own file under `./data/`. Every stage must be re-runnable without repeating paid calls.
3. **Split fetching from deciding.** Stages that call an API save the raw JSON to `./cache/<source>/<id>.json` and checkpoint every 200 records. Stages that classify, score or filter only read the cache. That way, changing a rule never costs another API call.
4. Run each stage, show me the counts, and show me 10 sample rows before moving on.
5. Never invent an email address. Every email in the final list must come from a real source (a bio, a link page, a fan export, a Hunter result) and carry that source in a column.

## Stage 1: ingest the fans you already have (`01_fans.py`)

- Read every CSV in `./input/fans/`. Detect the email, name, phone, SoundCloud URL or handle, Instagram handle, city and signup-date columns by header and by sampling values.
- Normalize: lowercase emails, strip SoundCloud URLs to the permalink, dedupe on email, then on SoundCloud handle. Keep a `sources` column listing every export a person appeared in.
- Drop phone numbers from the output. They are for Laylo SMS, not for promo email.
- Also pull the SoundCloud followers of each Coop artist and label account I name (ask me for the list; start with `cooprecords`, `danielallantunes`, `tiarowastaken`, `bgzaudio`, `vincent_official`). Use `https://api-v2.soundcloud.com/users/{id}/followers?limit=200&linked_partitioning=1&client_id=...` and follow `next_href` until it ends.
- Output `data/01_fans.csv`.

**How to get a SoundCloud `client_id`:** fetch `https://soundcloud.com/`, find the `<script src="https://a-v2.sndcdn.com/assets/...js">` URLs, fetch them in order, and regex `client_id:"([A-Za-z0-9]{32})"`. Cache it; refetch only when a call returns 401. Resolve any profile URL with `https://api-v2.soundcloud.com/resolve?url=<profile url>&client_id=...`. SoundCloud handles 16 parallel workers at about 60 requests a second without rate limits, so use a semaphore of 16.

## Stage 2: lineups (`02_lineups.py`)

- Use the EDMTrain API (`https://edmtrain.com/api/events?client=<key>`) to pull every event in the US and UK for the last 12 months plus everything announced. Keep events where any artist's genre or the event name matches: UK garage, UKG, bassline, speed garage, bass house, house, dubstep, 140, wonk, riddim, future bass.
- For festivals and multi-artist bills, keep every artist on the lineup.
- Dedupe artists by normalized name. For each artist keep: shows in the last 12 months, the next show date and city, and the festivals they played.
- Output `data/02_lineup_artists.csv`.

## Stage 3: match everyone to SoundCloud (`03_soundcloud.py`)

- Fans with a SoundCloud handle: resolve it directly.
- Everyone else (lineup artists, fans with only a name): search `https://api-v2.soundcloud.com/search/users?q=<name>&limit=10`. Accept a match only if `rapidfuzz` name similarity is 90 or higher and the account has at least 500 followers, or if the account links back to an Instagram or website we already have for them. Otherwise mark `sc_match = none`.
- For each matched profile, save the full user JSON plus `/users/{id}/web-profiles` (their link list) and their 20 latest tracks to the cache.
- Output `data/03_matched.csv` with `sc_id`, `permalink`, `followers`, `bio`, `links`, `avatar_url`.

## Stage 4: find the promo email (`04_emails.py`)

Search in this order and stop at the first promo address:

1. **SoundCloud bio and links.** Regex every email in the bio and web profiles. Also read link pages the DJ lists (Linktree, Beacons, their own site) once, and regex emails from the HTML.
2. **Instagram,** only for people still without one: ScrapeCreators profile endpoint for the handle in their SoundCloud links. Read `public_email`, `business_email` and the bio.
3. **Facebook page About section,** only if a Facebook link exists.
4. **LLM lookup,** last. Ask for the artist's publicly listed promo email and require the page it appears on. Keep it only if you can fetch that page and find the address on it.

Classify each address by the words around it and in the address itself:
- **Keep:** promo, promos, demos, music, submissions, tracks, or a personal address the DJ lists "for promos".
- **Drop:** booking, bookings, agent, agency, management, mgmt, press, info@ at a big agency domain. Agents and managers don't pass promos to the DJ.

Output `data/04_emails.csv` with `email`, `email_source`, `email_context` (the 80 characters around it) and `email_type`.

## Stage 5: flag the DJs and rank them (`05_score.py`)

For everyone with an email, decide if they're a DJ worth sending to:
- **DJ signals:** "DJ" in the name or bio; tracks longer than 20 minutes (mixes); shows in stage 2; a Resident Advisor or EDMTrain link.
- **Timing:** shows in the next 90 days go to the top. Keep the next show date and city.
- **Warmth:** anyone from `01_fans.csv` is warm. Anyone who downloaded a Coop DJ tool (from a gate export) is the warmest.
- **Priority:** A = warm and touring in the next 90 days. B = warm or touring. C = everyone else with DJ signals. Drop anyone with no DJ signal.
- Tag genre from their recent track tags and region from their show cities.
- Output `data/05_scored.csv`.

## Stage 6: check it by eye (`06_contact_sheet.py`)

Text filters get a lot wrong on lists of people: label pages, promoters, event brands and fan accounts all slip through. Render the A and B lists as contact sheets: 36 avatars per JPEG, 6 across, each cell stamped with a number, the name and follower count. Open them for me, and let me reply with the numbers to drop (`x 4, 17, 22`). Refuse to continue until every cell on a sheet is accounted for.

## Stage 7: verify (`07_verify.py`)

- Run every remaining email through Hunter's Email Verifier (`https://api.hunter.io/v2/email-verifier?email=...&api_key=...`), 5 at a time, caching results.
- Keep scores of 70 or higher. Keep `accept_all` domains at 70 or higher, flagged. Drop `invalid` and `disposable`.
- Output `data/07_verified.csv`.

## Stage 8: hand off (`08_export.py`)

- Write `output/promo_list.csv` with: first name, artist name, email, priority, warm (yes/no), fan source, genre, region, next show date, next show city, SoundCloud URL, email source.
- If Airtable keys exist, upsert to a `DJ Promo` table keyed on email, with single-select fields for priority, genre and region.
- If a Mixmax token exists, add the A and B lists to a sequence I name, with these merge fields: `{{First Name}}`, `{{Next Show City}}`, `{{Release}}`.
- Print a summary: counts by priority, how many came from fan data vs lineups, and how many emails were found at each search step.

## The email

Three touches from an official Coop domain, never @gmail: first send, a follow-up after one week, and a last follow-up a week after that. First touch:

> Hey {{First Name}}, I hope all is well! Huge fan of your sets and wanted to send over some heaters for consideration in your sets. Tagged MP3s here: (Dropbox link). Thanks for the time and consideration!

For warm contacts, add one line before the link: "You grabbed one of our free downloads before, so this one's for you first." Add a one-line opt-out at the bottom of every email.

## Re-running it for every release

Make `run_release.py "<release name>" "<dropbox link>" <genre>` that re-runs stages 2 and 5 to 8 (lineups change weekly; stages 1, 3 and 4 only fetch what's new), filters to the release's genre, and loads a fresh Mixmax sequence. Keep a `sent.csv` so nobody gets the same release twice.

Built from what we use on our own lead engines: SoundCloud's public API at 16 parallel requests, fetching kept separate from deciding, and a photo check because text filters alone got 41% of a people list wrong on our last scrape.