Extract emails from text
Paste any text and get every email address in it as a clean list: a sentence that mentions hello@example.com gives that address and a count of 1. Repeats are merged or kept, as you choose. Free in your browser.
- Free
- Runs in your browser
- No sign-up
- Your data stays on your device
Run — free
Worked example
- Text
- Contact us at hello@example.com today.
- Extracted email addresses
- hello@example.com
- Email count
- 1
- Match mode
- Unique
Paste any plain text and get every email-looking address as a clean list. Choose unique mode to drop case-insensitive duplicates while keeping the first spelling you saw, or all mode to keep every match. No network calls and no mailbox checks — pure local extraction.
Unique addresses or every match
Paste notes, an email thread or the text of a web page and the tool reads it from left to right, picking out everything shaped like name@domain.ending. With the default option, addresses that differ only in capital letters count once: the first spelling you wrote is kept, in the order each address first appeared. The other option keeps every occurrence with its original capitals, which is useful when you later need to know how often an address shows up. A sentence like Contact us at hello@example.com today gives one address and a count of 1.
What it does not do, and where it helps
It finds addresses by their shape only. It does not prove that a mailbox exists, it makes no network calls and it does not rewrite an address to look more correct, so an oddity such as two dots in a row before the at sign is listed exactly as written. That makes it a quick way to pull contacts out of pasted notes for a lead list, to collect every person mentioned in a long support ticket, or to build a list of unique addresses before importing them into a spreadsheet.
Loose extractor, not a full RFC validator
The tool scans left to right with a fixed ASCII pattern for local@domain.tld. It captures addresses as they appear, including oddities such as double dots in the local part. It does not prove the mailbox exists and it does not rewrite the string to be stricter.
Unicode and internationalized mail are out of scope
Non-ASCII characters in the local part are not part of the pattern, so a mixed local may yield a partial ASCII match rather than the full original token or an empty result. Full IDN and punycode domains are not supported in v1.
How much text it accepts
Text is required and capped at one hundred thousand characters. The response never echoes the full source text, only the emails array, count and mode.
What you can do with it
CRM and lead lists
Pull addresses out of pasted notes or email threads.
Support triage
Collect every contact mentioned in a long ticket.
Data cleanup
Build a unique list before importing to a sheet.
FAQ
Does it verify that the mailbox exists?
No. It only extracts patterns from text, with no DNS or SMTP.
What is unique mode?
Case-insensitive dedupe keeping the first form and first-seen order.
Are internationalized addresses supported?
Not fully in v1. Non-ASCII locals may produce partial ASCII matches.
How much text can I paste at once?
Up to 100,000 characters. Longer text needs to be split into parts before pasting, and the box cannot be left empty, because there always has to be a text to scan.
Is my original text included in the result?
No. The result only contains the addresses found, how many there are and which option was used, so the rest of what you pasted, such as names or notes around the addresses, is never repeated back.
Related tools
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/text/extract-emails \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"text":"Contact us at hello@example.com today."}'const res = await fetch("https://api.kit.forhosting.com/text/extract-emails", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"text": "Contact us at hello@example.com today."
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/text/extract-emails",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"text": "Contact us at hello@example.com today."
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/text/extract-emails", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"text":"Contact us at hello@example.com today."}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"text":"Contact us at hello@example.com today."}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/text/extract-emails", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"text": "Contact us at hello@example.com today."
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "text.extract_emails",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Limits
max_text_chars | 100000 |
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |
Technical notes
Unique versus all
unique mode collapses matches that differ only by letter case, keeping the first form and the order of first appearance. all mode keeps every occurrence with its original casing, which is useful when you need frequency later in a pipeline.
Limits and typed errors
Unknown modes and non-string text return typed invalid_input.
Integration use cases
Automated checks
Assert extracted arrays in CI fixtures.
Technical questions
Is the full text returned?
No. Only emails, count and mode.
What is the size limit?
100000 characters.
Is it free in the browser?
Yes. The native app uses the API at $0.002.