# Email Extractor from Text: Emails, Phones & URLs from Any Text (`data-done-right/email-extractor-from-text`) Actor

Email extractor from text: extract email addresses, phone numbers (E.164), URLs, domains and social handles from pasted text, PDF, DOCX, XLSX, CSV, HTML and EML files or datasets. Finds obfuscated emails like name \[at] domain \[dot] com. Syntax check only, nothing is sent.

- **URL**: https://apify.com/data-done-right/email-extractor-from-text.md
- **Developed by:** [Data Done Right](https://apify.com/data-done-right) (community)
- **Categories:** Developer tools, Lead generation, Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.12 / 1,000 item extracteds

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?

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

## Email Extractor from Text: Emails, Phones & URLs from Any Text

This **email extractor from text** pulls **email addresses, phone numbers (E.164), URLs, domains and social handles** out of text you already have: pasted text, PDF, Word, Excel, CSV, HTML and email files (EML, MSG), or a text field of an Apify dataset. It also finds emails that people hide from bots, such as `jane.doe [at] acme [dot] com`. Use it to **extract emails from text**, to **extract email addresses** from a document, or as a quick **email extractor** for a pile of notes. It reads only what you give it: it does not crawl websites, send email or check mailboxes.

What you get:

- One row per unique item: type, original value, normalised value (lowercase email, E.164 phone, clean URL), domain, count, source and a context snippet.
- A per-source report (what was found in each text or file, and why a file failed) and a run summary.
- Pay per item extracted, nothing else. Sources that fail or return nothing cost nothing.

To extract emails from websites instead of text you already have, use **Domain List Email Extractor: Emails & Phones from Websites** (see Related tools).

### Privacy and responsible use

You supply the text, files and datasets. Only process data you are allowed to process, and follow the rules that apply to you (GDPR, CAN-SPAM, PECR and the terms of the source). This Actor does not send email, does not contact the addresses it finds, does no SMTP probing and no MX lookup. Email addresses are checked for **syntax only**. IBAN extraction is off by default because bank numbers are sensitive. Downloaded files are read in memory/temporary storage and deleted at the end of the run. The Actor never logs in anywhere, and refuses login-walled social sites and internal addresses.

### What can it extract

| Type | What you get | Normalised as |
|---|---|---|
| `email` | RFC-practical syntax, plus `name [at] domain [dot] com`, `name(at)domain.com`, `name at domain dot com`, `&#64;`, `%40`, `mailto:` links, zero-width characters. `isRole` flags info@, sales@, noreply@ and similar. `obfuscated` says whether it was hidden. | lowercase `local@domain` |
| `phone` | Numbers valid for their country (libphonenumber), with country, national and international format and line type | E.164, for example `+14155552671` |
| `url` | `http://`, `https://` and `www.` links, with trailing punctuation removed | lowercase host, no fragment, no default port |
| `domain` (option) | Domains of the emails and URLs that were found | bare domain, no `www.` |
| `social` and `handle` (option) | Profile links on X/Twitter, Instagram, Facebook, LinkedIn, TikTok, YouTube, GitHub, Telegram, Threads, Pinterest, and plain `@mentions` | `platform:handle` or `@handle` |
| `iban` (option, off by default) | IBANs that pass the country length and mod-97 checksum | compact IBAN |

### How to extract emails from text

1. Open the Actor and paste your text into **Texts to scan**. Each entry is one source (an entry can be a whole email thread).
2. Leave **Extract emails**, **Extract phone numbers** and **Extract URLs** on, or switch on domains, social handles or IBANs.
3. Run it, then download the dataset as JSON, CSV, Excel or HTML. Filter on the `type` column to get only emails.

### How to extract email addresses from a PDF, Word or Excel file

Put the public links in **File URLs**: PDF, DOCX, XLSX, CSV, TXT, HTML, EML or MSG. The Actor downloads each file (up to the size limit), reads its text including table cells, headers and footers, and the links hidden behind words (`mailto:` links in PDFs, Word and HTML are read too). Scanned PDFs without a text layer need OCR first; this Actor does not do OCR and tells you so in the source report.

### How to find obfuscated emails like name \[at] domain \[dot] com

Nothing to configure: de-obfuscation is always on. `jane.doe [at] acme [dot] com`, `jane(at)acme.com`, `jane{at}acme{dot}org`, `anna DOT smith AT corp DOT de` and `sam&#64;adatum.net` come out as normal addresses with `details.obfuscated: true`. A bare `at` is only decoded when the domain also uses a spelled-out `dot`, so ordinary sentences such as "meet me at noon.com" are left alone.

### How to extract emails from a dataset of another Actor

Pick the **Dataset** and set **Dataset field** (default `text`; dotted paths such as `contact.notes` work, and lists of strings are joined). Every dataset item becomes one source, so each row tells you which item it came from.

### Input

Copy-paste input (this is the prefill):

```json
{
  "texts": [
    "Contact our sales team at sales@acme-example.com or call +1 415 555 2671. For support write to support@acme-example.com (office hours 9-5) or phone +44 20 7946 0958. Our CEO Jane Doe can be reached at jane.doe [at] acme-example [dot] com, and press questions go to Press.Team@Acme-Example.com. More info: https://www.acme-example.com/contact"
  ]
}
```

| Field | Default | What it does |
|---|---|---|
| `texts` | prefill | Texts to scan, one source per entry |
| `fileUrls` | empty | Public links to PDF, DOCX, XLSX, CSV, TXT, HTML, EML, MSG files |
| `datasetId`, `datasetField` | empty, `text` | Scan a field of every item of an Apify dataset |
| `extractEmails` | true | Email addresses |
| `extractPhones` | true | Phone numbers in E.164 |
| `extractUrls` | true | Web links |
| `extractDomains` | false | Domains of found emails and URLs |
| `extractSocialHandles` | false | Social profile links and @mentions |
| `extractIbans` | false | IBANs that pass the checksum |
| `defaultRegion` | `US` | 2-letter country for phone numbers without a + prefix (empty: only + numbers) |
| `dedupe` | `global` | `global` one row per unique item across sources, `perSource`, or `off` (every occurrence) |
| `lowercaseEmails` | true | Lowercase the normalised email |
| `skipRoleAccounts` | false | Drop info@, sales@, support@, admin@, noreply@ and similar |
| `domainAllowList` | empty | Keep only emails and URLs on these domains (subdomains included) |
| `domainDenyList` | empty | Drop emails and URLs on these domains |
| `contextChars` | 60 | Characters of surrounding text on each side (0 switches it off) |
| `maxItems` | 1000 | Maximum rows returned and charged |
| `maxSources` | 1000 | Maximum texts, files and dataset items read |
| `maxFileSizeMb` | 25 | Larger files are skipped and reported |
| `maxPdfPages` | 200 | Pages read per PDF |
| `maxCharsPerSource` | 2,000,000 | Longer texts are cut, and the cut is reported |
| `concurrency`, `timeoutSecs` | 4, 30 | File download parallelism and timeout |
| `proxyConfiguration` | none | Optional, only for file URLs |

### Output

One dataset row per item. Real output of the prefill run (shortened to two rows):

```json
[
  {
    "index": 3,
    "type": "email",
    "value": "jane.doe@acme-example.com",
    "normalised": "jane.doe@acme-example.com",
    "domain": "acme-example.com",
    "count": 1,
    "source": "text 1",
    "sourceType": "text",
    "sourceCount": 1,
    "sources": ["text 1"],
    "context": "...phone +44 20 7946 0958. Our CEO Jane Doe can be reached at jane.doe@acme-example.com, and press questions go to Press.Team@Acme-Example.com. Mor...",
    "details": {"localPart": "jane.doe", "tld": "com", "isRole": false, "syntaxValid": true, "obfuscated": true}
  },
  {
    "index": 6,
    "type": "phone",
    "value": "+44 20 7946 0958",
    "normalised": "+442079460958",
    "domain": null,
    "count": 1,
    "source": "text 1",
    "sourceType": "text",
    "sourceCount": 1,
    "sources": ["text 1"],
    "context": "...ite to support@acme-example.com (office hours 9-5) or phone +44 20 7946 0958. Our CEO Jane Doe can be reached at jane.doe@acme-example.c...",
    "details": {"region": "GB", "countryCode": 44, "national": "020 7946 0958", "international": "+44 20 7946 0958", "numberType": "FIXED_LINE"}
  }
]
```

Rows are ordered emails first, then phones, URLs, social handles, domains and IBANs, in order of first appearance. The context snippet shows the text after de-obfuscation.

Two key-value store records come with every run:

- `SUMMARY`: totals by type, items returned and charged, filtered counts, notes about anything that went wrong.
- `SOURCES`: one entry per text, file or dataset item with its status, file type, number of unique items found by type, and the error if it failed (for example `download failed: HTTP 404`).

### How much does it cost

Pay per event: **`item-extracted` = $0.00015 per output row** ($0.15 per 1,000 rows). Sources that fail, are empty or are skipped produce no rows and cost nothing. `maxItems` caps both the rows and the cost.

| Example | Rows | Price |
|---|---|---|
| 250 short texts with about 4 items each | 1,000 | about $0.15 |
| A 300-page PDF directory with 10,000 unique emails and phones | 10,000 | about $1.50 |
| A scraper dataset of 20,000 items with 100,000 unique contacts | 100,000 | about $15 |

Apify platform usage (compute) is included in the event price. With dedupe `global` you only pay for unique items, not for repeats.

### Use cases

- Clean a mailbox export, support thread or meeting notes into a contact list.
- Pull the email addresses and phone numbers out of a PDF directory, a conference attendee list or a Word proposal.
- Read the output of another scraper (a `description` or `bio` field) and add emails and handles to it.
- Decode the "name \[at] domain \[dot] com" addresses on a copied staff page, then filter to the company domain.
- Normalise phone numbers from mixed national formats to E.164 before importing into a CRM.

### FAQ

**Does it verify that an email address exists?**
No. It checks syntax only. There is no MX lookup, no SMTP probing and nothing is ever sent, so a syntactically valid address may still bounce. Run a dedicated verifier before a campaign.

**Does it crawl websites?**
No. It parses text and files you give it. A file URL pointing to an HTML page is downloaded once and parsed as a document, with no links followed and no JavaScript run. To crawl company websites, use Domain List Email Extractor.

**How does it decide a phone number is real?**
It uses Google's libphonenumber rules and keeps only numbers that are valid for their country. Numbers without a + prefix are read with `defaultRegion`. Long IDs, dates and order numbers are mostly ignored, but a digit string that happens to look like a valid number can still be returned; check the `region` and `numberType` fields.

**Which files can it read, and what can it not read?**
PDF (text layer only, no OCR), DOCX, XLSX (values, not formulas), CSV/TXT and other text, HTML, EML and MSG. It does not unpack ZIP archives, read legacy .doc/.xls files or password-protected PDFs, and it does not read images. Failed files are listed in `SOURCES` with the reason.

**Are role accounts and free-mail addresses flagged?**
Role accounts (info@, sales@, noreply@ and about 60 more) get `isRole: true` and can be skipped with `skipRoleAccounts`. Free-mail providers are not flagged; use `domainDenyList` with gmail.com and similar to drop them.

**Is my text stored or sent anywhere?**
No third-party service is called. The text is processed inside the Actor run; items are written to your own dataset. File URLs are downloaded from the public internet from the Actor, and internal and private addresses are blocked.

### Limits

- Syntax check only for emails (see above). IBANs only for the countries in the IBAN registry list built in.
- Names are not extracted; a row is an address, number or link, with the surrounding text in `context`.
- Cut-offs are reported: `maxCharsPerSource` for long texts, `maxPdfPages`, `maxFileSizeMb`, `maxItems`, `maxSources`.
- With dedupe `global`, `source` is the first source and `sources` lists at most 25 sources (`sourceCount` has the real number).

### Use with the Apify API, MCP, n8n and Make

Run it from any tool that can call the Apify API. In n8n use the Apify node (Run Actor, then Get Dataset Items), in Make use the Apify app, and for AI agents use the Apify MCP server, which exposes this Actor with its input schema.

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("<your-username>/email-extractor-from-text").call(run_input={
    "texts": ["Write to jane.doe [at] acme-example [dot] com or call +1 415 555 2671"],
    "skipRoleAccounts": True,
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["type"], item["normalised"])
```

JavaScript:

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('<your-username>/email-extractor-from-text').call({
    fileUrls: ['https://example.com/contacts.pdf'],
    extractDomains: true,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map((i) => `${i.type} ${i.normalised}`));
```

### Related tools

- **Domain List Email Extractor: Emails & Phones from Websites** to find contact details by crawling company websites.
- **Phone Number Validation & Lookup: Format, Carrier, Line Type** to check and enrich the phone numbers you extracted.
- **Merge Datasets & Dedupe: Combine, Filter, Export CSV & Excel** to merge the extracted rows with other datasets and remove duplicates.

# Actor input Schema

## `texts` (type: `array`):

Paste the text(s) to scan, one text per entry (an entry can be a whole email thread, a page of notes or a CSV dump). Each text is one source. Example: Write to jane.doe \[at] acme-example \[dot] com or call +1 415 555 2671.

## `fileUrls` (type: `array`):

Public links to PDF, DOCX, XLSX, CSV, TXT, HTML, EML or MSG files. The file is downloaded and its text is scanned. Public files only, no logins. Example: https://www.rfc-editor.org/rfc/rfc2822.txt

## `datasetId` (type: `string`):

Optional. Pick an Apify dataset (for example the output of a scraper) and the field below is scanned for every item. Example: a dataset ID such as aBcD1234eFgH5678i.

## `datasetField` (type: `string`):

Field of each dataset item that holds the text (or a list of texts). Dotted paths work. Example: description or contact.notes. Default: text.

## `extractEmails` (type: `boolean`):

Find email addresses, including hidden ones such as name \[at] domain \[dot] com, name(at)domain.com, HTML entities and mailto: links. Example: true.

## `extractPhones` (type: `boolean`):

Find phone numbers and return them in E.164 format (for example +14155552671) with country and number type. Uses the default region below for numbers without a + prefix. Example: true.

## `extractUrls` (type: `boolean`):

Find web links starting with http://, https:// or www. and return them normalised. Example: true.

## `extractDomains` (type: `boolean`):

Also return one row per domain, taken from the emails and URLs that were found (not guessed from plain words). Example: true to get a list of companies mentioned.

## `extractSocialHandles` (type: `boolean`):

Also return profile links of X/Twitter, Instagram, Facebook, LinkedIn, TikTok, YouTube, GitHub, Telegram, Threads and Pinterest, and @mentions. Example: true.

## `extractIbans` (type: `boolean`):

Also return bank account numbers in IBAN format that pass the length and checksum test. Off by default because they are sensitive. Only use it on text you may process. Example: false.

## `defaultRegion` (type: `string`):

2-letter country code used to read phone numbers written without a + prefix. Example: US, GB or DE. Leave empty to accept only numbers that start with + or 00.

## `dedupe` (type: `string`):

global: one row per unique item across all sources, with a count and the list of sources. perSource: one row per unique item in each source. off: one row for every occurrence. Example: global.

## `lowercaseEmails` (type: `boolean`):

Lowercase the normalised email address, so Jane.Doe@ACME.com and jane.doe@acme.com count as one. The original text stays in value. Example: true.

## `skipRoleAccounts` (type: `boolean`):

Drop shared mailboxes such as info@, sales@, support@, admin@ and noreply@. Example: true to keep only personal-looking addresses.

## `domainAllowList` (type: `array`):

Optional. Keep only emails and URLs on these domains (subdomains included). Example: acme.com, example.org. Leave empty to keep all.

## `domainDenyList` (type: `array`):

Optional. Drop emails and URLs on these domains (subdomains included). Example: gmail.com, yahoo.com, facebook.com.

## `contextChars` (type: `integer`):

Characters of surrounding text to return on each side of the first match, so you can see where it came from. Use 0 to switch the snippet off. Range 0 to 300. Example: 60.

## `maxItems` (type: `integer`):

Maximum rows to return (and to be charged for) in this run. Further items are counted in the summary but not returned. Example: 1000.

## `maxSources` (type: `integer`):

Maximum texts, files and dataset items to read in this run. Example: 1000.

## `maxFileSizeMb` (type: `number`):

Files larger than this are skipped and reported. Example: 25.

## `maxPdfPages` (type: `integer`):

Only the first pages of a long PDF are read. Example: 200.

## `maxCharsPerSource` (type: `integer`):

A longer text or file is cut to this length before scanning, and the cut is reported. Example: 2000000.

## `concurrency` (type: `integer`):

How many file URLs are downloaded at the same time. Example: 4.

## `timeoutSecs` (type: `number`):

Give up on a file URL after this many seconds. Example: 30.

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

Only used for file URLs. Leave empty to download files directly.

## Actor input object example

```json
{
  "texts": [
    "Contact our sales team at sales@acme-example.com or call +1 415 555 2671. For support write to support@acme-example.com (office hours 9-5) or phone +44 20 7946 0958. Our CEO Jane Doe can be reached at jane.doe [at] acme-example [dot] com, and press questions go to Press.Team@Acme-Example.com. More info: https://www.acme-example.com/contact"
  ],
  "datasetField": "text",
  "extractEmails": true,
  "extractPhones": true,
  "extractUrls": true,
  "extractDomains": false,
  "extractSocialHandles": false,
  "extractIbans": false,
  "defaultRegion": "US",
  "dedupe": "global",
  "lowercaseEmails": true,
  "skipRoleAccounts": false,
  "contextChars": 60,
  "maxItems": 1000,
  "maxSources": 1000,
  "maxFileSizeMb": 25,
  "maxPdfPages": 200,
  "maxCharsPerSource": 2000000,
  "concurrency": 4,
  "timeoutSecs": 30
}
```

# Actor output Schema

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

All extracted emails, phones and URLs (the default dataset), as JSON, CSV, Excel and more.

## `records` (type: `string`):

Key-value store records: SUMMARY (totals, notes) and SOURCES (one line per text, file or dataset item).

# 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 = {
    "texts": [
        "Contact our sales team at sales@acme-example.com or call +1 415 555 2671. For support write to support@acme-example.com (office hours 9-5) or phone +44 20 7946 0958. Our CEO Jane Doe can be reached at jane.doe [at] acme-example [dot] com, and press questions go to Press.Team@Acme-Example.com. More info: https://www.acme-example.com/contact"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("data-done-right/email-extractor-from-text").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 = { "texts": ["Contact our sales team at sales@acme-example.com or call +1 415 555 2671. For support write to support@acme-example.com (office hours 9-5) or phone +44 20 7946 0958. Our CEO Jane Doe can be reached at jane.doe [at] acme-example [dot] com, and press questions go to Press.Team@Acme-Example.com. More info: https://www.acme-example.com/contact"] }

# Run the Actor and wait for it to finish
run = client.actor("data-done-right/email-extractor-from-text").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 '{
  "texts": [
    "Contact our sales team at sales@acme-example.com or call +1 415 555 2671. For support write to support@acme-example.com (office hours 9-5) or phone +44 20 7946 0958. Our CEO Jane Doe can be reached at jane.doe [at] acme-example [dot] com, and press questions go to Press.Team@Acme-Example.com. More info: https://www.acme-example.com/contact"
  ]
}' |
apify call data-done-right/email-extractor-from-text --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,data-done-right/email-extractor-from-text"
        }
    }
}
```

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/cQqz9ZhZzyQLduFjU/builds/JW2MuevKFr2D7AtfH/openapi.json
