# Telegram Contact Extractor - Emails & Admin Handles (`scrapersdelight/telegram-channel-contact-extractor`) Actor

You pay only for channels where a contact is actually found. Pulls the email, phone, website and contact @handle a public Telegram channel publishes in its bio and recent posts, each with the evidence it came from. Measured on 40 real business channels: 50% published a contact route. No login.

- **URL**: https://apify.com/scrapersdelight/telegram-channel-contact-extractor.md
- **Developed by:** [Scrapers Delight](https://apify.com/scrapersdelight) (community)
- **Categories:** Social media, Lead generation, Marketing
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.50 / 1,000 per channel with a contact founds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Telegram Contact Extractor — Emails, Phones & Admin Handles

Finds the ways to **actually reach** a public Telegram channel: the email, phone number, website and
the `@handle` the channel names for ads or business — pulled from its bio and its 20 most recent
posts, each tagged with the evidence it was read from.

**You pay only for channels where a contact was found.** Channels that publish nothing are listed in
the run summary and cost you nothing.

No login. No phone number. No API key. No Telegram account of any kind.

***

### The measured hit rate

Run against **40 real business channels** (crypto, forex, NFT, marketing agencies, dropshipping,
Dubai real estate — discovered, not cherry-picked), 2026-09-15:

| | channels | rate |
|---|---|---|
| **At least one contact found** | 26 / 40 | **65%** |
| …with an explicit contact label next to it | 18 / 40 | 45% |
| Published nothing — **not charged** | 14 / 40 | 35% |

That 65% is the number to plan a budget against. It is not 100%, and an extractor that claimed it
would be lying: a third of business channels simply publish no way to reach them.

### What one row looks like

```json
{
  "channel": "agency605",
  "channelTitle": "605 Agency",
  "subscribers": 782,
  "contactCount": 2,
  "hasExplicitContact": true,
  "primaryContact": "@duvduvgap_manager",
  "primaryContactType": "telegram",
  "telegramContacts": ["@duvduvgap_manager", "@agency_605"],
  "emails": [], "phones": [], "websites": [],
  "contacts": [
    { "type": "telegram", "value": "@duvduvgap_manager", "source": "post",
      "confidence": "explicit",
      "evidence": "ro'yxatda o'tish uchun managerga bog'laning: @duvduvgap_manager" },
    { "type": "telegram", "value": "@agency_605", "source": "bio",
      "confidence": "inferred",
      "evidence": "Aloqa uchun: @agency_605" }
  ],
  "postsScanned": 7
}
```

That is a real row from the measured run, and it shows the design working on a channel that is not
in English: the post handle is `explicit` because "managerga" matched a contact cue, while the bio
handle is `inferred` — "Aloqa uchun" is Uzbek for "for contact" and is not in the cue list, so the
extractor stayed conservative and labelled it rather than guessing. Both are real contacts; the
confidence field tells you which one the Actor is sure about.

| field | what it is |
|---|---|
| `primaryContact` | The best single contact, already ranked — work the list top-down |
| `hasExplicitContact` | `true` when a contact word sat next to it. **Filter on this for your highest-intent list** |
| `emails` · `phones` · `websites` · `telegramContacts` | Each type on its own, ready to paste into a CRM |
| `contacts[].evidence` | The surrounding text it was read from, so you can see *why* it was called a contact |
| `contacts[].confidence` | `explicit` (labelled) or `inferred` (present, unlabelled) |
| `contacts[].source` | `bio` or `post` |

### Why it isn't just a regex for `@`

Every Telegram post mentions somebody. A naive extractor returns an `@handle` for **100%** of
channels and almost all of it is worthless — other channels, cross-promos, people being quoted.

So a handle in the **post stream** only counts when a contact word sits next to it — `contact`,
`for ads`, `manager`, `admin`, `sales`, `реклама`, `по вопросам`, `contacto`, `werbung`, `iletişim`
and others, because these channels are not all in English. In the **bio** a handle counts on its own
(that's the channel's own contact block), except the channel's own handle — which is not a way to
reach anybody. Phone numbers are accepted only in international `+` format, because a bare run of
digits in a crypto post is a price, not a number you can call.

Every one of those rules is verified offline against real captured pages before each release.

### Three different kinds of nothing

The run summary keeps them apart, because they mean different things for your list — and **none of
them is charged**:

- `noContactFound` — a real channel that publishes no contact route
- `noPublicPreview` — private, preview disabled, or not a channel at all
- `unreachable` — we could not fetch it after 3 attempts on 3 separate proxy sessions

A channel is only ever reported as "no contact" when the page was actually read.

### Pricing

Pay per event: **$0.0015 per channel with a contact found**, and **no run-start fee**.

At the measured 65% hit rate that works out to **$0.000975 per channel scanned** — under the cheapest
per-channel rate in the Telegram category, which also charges a $0.003 start fee on top. Scanning
1,000 channels costs about **$0.98** and returns roughly 650 contactable ones.

Nothing found means nothing charged, for the whole run.

### Source

`t.me/s/<channel>` is Telegram's own public web preview, served to any visitor. The host publishes no
`robots.txt` at all. These contact details are published by channel owners specifically so people can
reach them — but you are responsible for how you use them, which for outreach means the marketing
and data-protection rules wherever you are sending from (GDPR, CAN-SPAM, PECR and equivalents).

# Actor input Schema

## `channels` (type: `array`):

Handles or links. `bumbuagency`, `@bumbuagency` and `https://t.me/bumbuagency` all work. Duplicates are removed, so a channel is never charged twice in a run.

## `scanRecentPosts` (type: `boolean`):

Many channels put "for ads: @manager" in a post rather than the bio. Costs nothing extra - the posts are already on the page the bio came from.

## `includeInferred` (type: `boolean`):

A handle sitting in the bio with no contact wording is usually the owner, but can be a cross-promoted channel. On, they are included and tagged `inferred`; off, you get only contacts with explicit wording next to them.

## `onlyChannelsWithContacts` (type: `boolean`):

On (default), channels where nothing was found are listed in the run summary and NOT charged - you pay only for usable rows. Turn off to get a row for every channel.

## `maxConcurrency` (type: `integer`):

How many channels to scan at once. Raising this cuts run time and therefore what the run costs you. 8 is a good default.

## `proxyConfiguration` (type: `object`):

Apify Proxy. Datacenter is sufficient.

## Actor input object example

```json
{
  "channels": [
    "bumbuagency",
    "Promo_NFT",
    "MedicalMarketingAgency"
  ],
  "scanRecentPosts": true,
  "includeInferred": true,
  "onlyChannelsWithContacts": true,
  "maxConcurrency": 8,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `items` (type: `string`):

One row per channel where a contact was found: best contact, plus every email, phone, website and Telegram handle, each with the evidence snippet it was read from and whether it was explicitly labelled as a contact.

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

RUN\_SUMMARY: channels requested vs delivered vs charged, the measured contact hit rate, which channels published no contact (not charged), which had no public preview, and which were unreachable.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/telegram-channel-contact-extractor").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/telegram-channel-contact-extractor").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 '{}' |
apify call scrapersdelight/telegram-channel-contact-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapersdelight/telegram-channel-contact-extractor"
        }
    }
}
```

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/S98QFTNuOlW0haeZq/builds/U8ZhiBP8ir4V2OcFh/openapi.json
