# YouTube Channel Business Email Finder (`garnet_puppet/youtube-channel-email-finder`) Actor

Turn a list of YouTube channels - or a single topic keyword - into business email addresses. Every row shows where the address came from, with verified addresses kept separate from pattern guesses. Channels with no address still return a row, and you are never billed for those.

- **URL**: https://apify.com/garnet\_puppet/youtube-channel-email-finder.md
- **Developed by:** [Mehmet](https://apify.com/garnet_puppet) (community)
- **Categories:**
- **Stats:** 5 total users, 2 monthly users, 89.5% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $30.00 / 1,000 verified channel emails

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

Give this Actor a list of YouTube channels, or a search keyword, and it returns each
channel's business email address when one exists and can be found without signing in.
One row of output is one channel; a channel with no address found still returns a row,
and you are not billed for it.

*This Actor is independent and not affiliated with, endorsed by, or sponsored by
YouTube or Google LLC.*

### What it does

Paste channel URLs, `@handles`, channel IDs, or video/playlist links (the channel is
derived automatically) into **Channels**, or use **Search keywords** to find channels
by topic instead, up to **Max channels per keyword** each (default 50). For every
channel resolved, the Actor checks five places for a business email address, in a
fixed order, stopping as soon as it finds one it accepts. Each row carries identity
fields, subscriber and view counts, the best address
found (or `null`), and enough detail about where it came from to judge it yourself.

### What it finds - and what it doesn't

On a fresh, uncurated 100-channel sample, this Actor found a usable business email for
**62 channels out of 100**. Of that, 49 points were a verified source (About page,
linked website, video descriptions, or community posts) and 13 points were a pattern
guess confirmed only by a mail record on the domain. That 62 percent is measured, not a
target, and it is never rounded up here or anywhere else.

There is a ceiling above it, also measured. YouTube's own channel data shows **69
percent of channels have a business email address configured** - but hides it behind a
sign-in wall (`signInForBusinessEmail`: "Sign in to see email address"). The gap
between 62 and 69 is that wall. A scraper that solves CAPTCHAs and signs in reaches
those extra addresses; this Actor does neither, so it does not close the gap. When the
gate is the only reason a channel came back empty, the record says so with
`businessEmailGated: true`, so you know to follow up by hand if it matters to you.

What gets thrown out matters just as much. The ownership filter rejected 83 addresses
that looked like hits but were not the channel's own: 59 belonged to intermediary
platforms the channel merely mentioned, 12 were on domains with no plausible tie to the
channel, 10 were free-mail addresses found in someone else's page text, and 2 were
literal placeholders like `example@`. Coverage fell across three measurement passes -
65, then 63, then 62 percent - each drop closing a filter hole that handed out an
address that was not the channel's own.

Worth noting: one real run, a "woodworking" search of 10 channels, found an email for
9 in 50 seconds - 90 percent, well above 62. One small sample, not a rate to expect.

### How it finds emails

Sources are tried in a fixed order, cheapest and most reliable first, stopping at a
channel the moment a verified address turns up:

1. **About description and link section** - free, the panel is already fetched for
   every channel. `verified`.
2. **Linked website**, if set and its `robots.txt` allows it. A page-body address
   counts only if it is on the site's own domain or comes from a structural marker such
   as `mailto:` or JSON-LD contact data. `verified`.
3. **Descriptions of the 15 most recent videos**, fetched in one RSS request.
   `verified`.
4. **A pattern guess**: `contact@<the channel's own domain>`, kept only if that
   domain's mail record (MX) accepts mail. The one source with no confirmed address
   behind it, so it is labelled `guessed`.
5. **Community posts**, tried only when nothing above found anything. `verified`.

Stopping at the first accepted address (verified, or a step 4 guess) does not lower
coverage - it only changes which address is reported first, never whether one is found.

### Pricing

You are charged when this Actor finds a business email for a channel, and never
charged when nothing turns up - that row still comes back, with `email: null`. A
channel is charged **at most once**, no matter how many addresses it yields; anything
past the first is included free in `allEmails`. Verified and guessed addresses are
billed at different rates, guessed priced lower for its lower certainty. Exact
per-event prices are on this Actor's Pricing tab in Apify Console.

### Output

One record per resolved channel. Key fields:

| Field | What it holds |
|---|---|
| `channelId`, `channelUrl`, `channelName`, `handle` | Channel identity |
| `subscriberCount`, `viewCount`, `videoCount`, `country`, `joinedDate` | Lead details, from the same free panel as the email |
| `email` | Best address found, or `null` |
| `emailConfidence` | `verified` or `guessed`, or `null` |
| `emailSource` | `channel-about`, `channel-links`, `linked-site`, `video-description`, `community-post`, `pattern-guess`, or `null` |
| `emailIsRoleAddress` | Whether `email` is a role inbox (`info@`, `support@`) rather than a person |
| `allEmails`, `emailCount` | Every address found, comma-separated, and how many |
| `businessEmailGated` | `true` when YouTube has a business email on file but hides it behind sign-in |
| `linkedSite`, `siteSkippedReason` | The channel's website, and why it was skipped (`robots`, `budget`, `unreachable`, or `null`) |
| `mxVerified` | Whether the guessed address's domain has a working mail record |
| `inputUrl`, `scrapedAt`, `contentHash` | Source input, scrape time, and a fingerprint excluding `scrapedAt` so real changes are detectable |

A real record, from a live run of this Actor against a "woodworking" search (Run
`Wjrh6hTe7T4hBhAuf`, dataset `bOaIgPlkdyEG61loP`):

```json
{
  "channelId": "UCQ_bmgSrYsQS0LboA_tZpEw",
  "channelUrl": "https://www.youtube.com/channel/UCQ_bmgSrYsQS0LboA_tZpEw",
  "channelName": "Bourbon Moth Woodworking",
  "handle": "@Bourbonmoth",
  "subscriberCount": "1.25M subscribers",
  "viewCount": "145,874,649 views",
  "videoCount": "286 videos",
  "country": "United States",
  "joinedDate": "Joined May 8, 2015",
  "email": "iamupset@bourbonmoth.com",
  "emailConfidence": "verified",
  "emailSource": "linked-site",
  "emailIsRoleAddress": false,
  "allEmails": "iamupset@bourbonmoth.com",
  "emailCount": 1,
  "businessEmailGated": false,
  "linkedSite": "http://www.bourbonmoth.com/",
  "siteSkippedReason": null,
  "mxVerified": false,
  "inputUrl": "ytsearch:woodworking",
  "scrapedAt": "2026-08-14T10:32:09.821Z",
  "contentHash": "66dabf9f7be40255a716c2fe6c2a3740023cb8caff205dfdccfd4b18d1f7d47c"
}
```

The same run also guessed `contact@theweekendwoodworker.com` for a channel with no
published address: `emailConfidence: "guessed"`, `mxVerified: true`.

### Free preview

Turn on **Free preview** and the Actor resolves your channel list and reports how many
would be billed, without charging anything. Preview only checks the free source (the
About panel already fetched for every channel) - it does not visit linked sites, read
video descriptions, guess a pattern address, or check community posts. The number
reported is a floor: a real run on the same channels can only find as many or more,
never fewer.

### Limits and honesty

- **No CAPTCHA solving, no sign-in.** The 69 percent ceiling above is real, and this
  Actor stops 7 points short of it on purpose rather than automate a login wall.
- **No SMTP verification.** Confirming a mailbox exists means opening a connection on
  port 25 and talking to the mail server. That port is closed on this platform -
  measured, not assumed - so no Actor here can verify a mailbox this way, including
  competitors that imply they do. A `guessed` address is checked only to the level an
  MX record can prove: the domain accepts mail, not "this mailbox exists" - why it is
  priced lower.
- **`robots.txt` is read and obeyed on every website visited.** A disallowed site is
  skipped entirely (`siteSkippedReason: "robots"`), and the other four sources still
  run for that channel.

### Frequently asked questions

**Why does this find fewer addresses than some competitors claim to?**
Because those numbers use a different method, not a better one. YouTube shows a
channel's business email only after sign-in, behind a wall that also expects a CAPTCHA.
A scraper that automates logged-in sessions reaches the extra 7 points between this
Actor's 62 percent and the 69 percent ceiling; this Actor does neither.

**What does "guessed" mean, exactly?**
`contact@` was tried at the channel's own domain, and its mail record says it accepts
mail - nothing more. Nobody has confirmed the inbox exists or that anyone reads it;
SMTP verification, which would confirm the mailbox itself, is not possible on this
platform (see Limits above). Guessed addresses are labelled `guessed` in
`emailConfidence` and billed at a lower rate.

**Can the same channel be charged twice?**
No. A channel is billed at most once per run, the moment its best address is found,
regardless of how many addresses it has; extras ride along free in
`allEmails`. A channel with no address found is never charged, and with **Incremental
mode** on, a channel already resolved earlier is skipped entirely rather than
re-scraped and re-billed.

# Actor input Schema

## `urls` (type: `array`):

Channel URLs, @handles, or channel IDs. Video and playlist URLs resolve to their channel.

## `searchKeywords` (type: `array`):

Find channels by keyword instead of pasting a list. See "Max channels per keyword" below for how many each keyword contributes.

## `maxChannelsPerSearch` (type: `integer`):

Upper bound on how many channels each keyword contributes. Default 50.

## `includeLinkedSite` (type: `boolean`):

Visit the website linked on the channel and look for a contact address. We read robots.txt first and skip any site that disallows it.

## `includeVideoDescriptions` (type: `boolean`):

Read the descriptions of the channel's 15 most recent videos. One request per channel.

## `allowGuessed` (type: `boolean`):

When a channel has its own website but no address is published, try contact@<domain> and keep it only if the domain accepts mail (MX record). These are labelled 'guessed' and billed at the lower rate.

## `deepScan` (type: `boolean`):

Last resort for channels where nothing else was found. Reads community posts. Slower, and it resolves roughly 1 in 39 otherwise-empty channels.

## `incremental` (type: `boolean`):

Remembers channels whose email was already found and skips them on later runs, so you are never billed twice for the same channel. Channels with no email found are always retried.

## `previewOnly` (type: `boolean`):

Resolve the channel list and report how many would be billed, without charging anything and without scraping emails.

## Actor input object example

```json
{
  "urls": [
    "https://www.youtube.com/@mkbhd",
    "https://www.youtube.com/@ChigzTech"
  ],
  "searchKeywords": [],
  "maxChannelsPerSearch": 50,
  "includeLinkedSite": true,
  "includeVideoDescriptions": true,
  "allowGuessed": true,
  "deepScan": true,
  "incremental": true
}
```

# Actor output Schema

## `results` (type: `string`):

One item per channel: the best business email found, where it came from, and the channel identity fields.

## `runSummary` (type: `string`):

Counts for the whole run: what was delivered, what was skipped, what failed, and any warnings. Read this to see whether the run did what you expected.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "urls": [
        "https://www.youtube.com/@mkbhd",
        "https://www.youtube.com/@ChigzTech"
    ],
    "searchKeywords": [],
    "maxChannelsPerSearch": 50,
    "includeLinkedSite": true,
    "includeVideoDescriptions": true,
    "allowGuessed": true,
    "deepScan": true,
    "incremental": true,
    "previewOnly": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("garnet_puppet/youtube-channel-email-finder").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "urls": [
        "https://www.youtube.com/@mkbhd",
        "https://www.youtube.com/@ChigzTech",
    ],
    "searchKeywords": [],
    "maxChannelsPerSearch": 50,
    "includeLinkedSite": True,
    "includeVideoDescriptions": True,
    "allowGuessed": True,
    "deepScan": True,
    "incremental": True,
    "previewOnly": False,
}

# Run the Actor and wait for it to finish
run = client.actor("garnet_puppet/youtube-channel-email-finder").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "urls": [
    "https://www.youtube.com/@mkbhd",
    "https://www.youtube.com/@ChigzTech"
  ],
  "searchKeywords": [],
  "maxChannelsPerSearch": 50,
  "includeLinkedSite": true,
  "includeVideoDescriptions": true,
  "allowGuessed": true,
  "deepScan": true,
  "incremental": true,
  "previewOnly": false
}' |
apify call garnet_puppet/youtube-channel-email-finder --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,garnet_puppet/youtube-channel-email-finder"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/sIDYsm9MW9czHR7KW/builds/ztCzcNDgraoChuDjh/openapi.json
