Skip to content
ToolsNow
Guides All tools

How to extract emails, URLs and numbers from unstructured text

Pattern matching finds shapes, not verified data. Where extraction goes wrong, why phone numbers are the hardest case, and how to handle the results responsibly.

Published

You have a forwarded email thread, an exported log, or a page of notes, and you need the addresses out of it. The job sounds trivial and mostly is — but the difference between a useful list and a misleading one comes down to understanding what pattern matching can and cannot tell you.

Matching a shape is not verifying a thing

Every extraction tool, including the one here, finds text that looks like a particular kind of value. That is all it does. It cannot tell you:

  • whether an email address receives mail,
  • whether a phone number connects to anyone,
  • whether a URL resolves, or is safe,
  • whether a number is a price, an ID, or a date fragment.

The output is a list of candidates. Treating it as verified data is where things go wrong — most visibly when someone extracts addresses from a document and sends to all of them.

Emails: the RFC trap

The formal grammar for email addresses (RFC 5322) is notoriously permissive. It admits quoted local parts with spaces, comments in brackets, and nesting that no mail provider on earth accepts. The “correct” regular expression for it is thousands of characters long, and worse, it is a well-known catastrophic-backtracking hazard: certain inputs make it run effectively forever.

The practical answer is to match the addresses people actually use — letters, digits and a handful of punctuation, an @, then a domain with at least one dot — and accept that you are trading theoretical completeness for a pattern that is fast and predictable. That is the trade the extraction tool makes.

Common false positives worth knowing about:

  • Version strings and handles: user@2x or a Twitter-style @name next to a word can form something address-shaped.
  • Trailing punctuation: “email me at [email protected].” — the full stop is sentence punctuation, not part of the domain, and a naive pattern keeps it.
  • Concatenated text from a badly converted PDF can fuse two addresses.

The genuinely hard part of URL extraction is knowing where to stop. A URL can legitimately contain brackets, commas and full stops, and sentences also end with those characters. Consider:

See https://example.com/docs/(v2), or https://example.org/a.

The first URL genuinely contains brackets. The second ends before the full stop. No pattern gets this universally right; the workable heuristic is to strip trailing sentence punctuation and to balance brackets — keeping a closing bracket only when there is an opening one to match it.

Bare www. hosts are worth catching too, since plenty of text omits the scheme. But be careful with what you then do: a URL extracted from an untrusted document should never be rendered as a live link by default. Reading the destination first is the entire point.

Phone numbers: the hardest case, honestly

There is no universal phone-number format. The ITU standard (E.164) caps numbers at 15 digits, national formats vary from 7 upward, and grouping conventions differ wildly — spaces, dots, hyphens, brackets, all optional.

That means any pattern broad enough to catch real numbers internationally will also catch:

  • order and invoice references,
  • dates written as long digit runs,
  • ID and account numbers,
  • product codes.

The honest approach is to accept the 7–15 digit range, say clearly that some matches will not be phone numbers, and let the user filter. A tool claiming to “validate” phone numbers from free text is claiming something it cannot do without a country context and a numbering-plan database.

If your data has a consistent format — a single country, a known prefix — a custom pattern will beat any general one. That is what the custom regex field is for.

Handling duplicates

Deduplication seems simple until you hit case. [email protected] and [email protected] almost certainly reach the same mailbox: the domain is case-insensitive by specification, and effectively every provider treats the local part that way too. So folding case is usually right for email and domains.

It is usually wrong for identifiers. SKU-a1 and SKU-A1 may well be different products. That is why case-insensitive comparison is a separate toggle rather than an assumption.

Keeping a count alongside each unique value is more useful than a plain unique list — it tells you which address appears throughout a thread and which appeared once in a signature.

Regex, briefly

A few pieces cover most extraction work:

  • \d a digit, \w a word character, \s whitespace
  • + one or more, * zero or more, ? optional
  • {3} exactly three, {2,4} two to four
  • [A-Z] a character set, [^,] anything but a comma
  • \b a word boundary — the fix for matching “cat” inside “category”
  • () a group you can refer back to

Two habits prevent most pain. First, anchor with \b where a match should not start mid-word. Second, avoid nesting quantifiers — patterns like (a+)+b are the classic recipe for catastrophic backtracking, where a non-matching input takes exponentially long. If a pattern hangs the page, that is almost always why.

Privacy, which matters more here than usual

The input to this kind of tool is very often a customer list, a support thread or a server log. Those are exactly the things that should not be pasted into an unknown website — the tool sees the whole document, not just the matches.

The extraction tool here runs entirely in the page, so nothing you paste is uploaded. That is worth checking for any tool you use on this kind of data: open the network tab and confirm nothing leaves.

And once you have the list, the ordinary obligations apply. An address appearing in a document you were sent is not consent to market to it.