The short answer, then the honest context: the pattern below catches typos, and a confirmation email catches everything else. That combination is what production systems actually do.
^[\w.+-]+@[\w-]+\.[\w.-]+$
# read it: one-or-more of [letters/digits/_/./+/-] @
# domain chunk . rest of domain (dots allowed)
# anchored ^...$ because validation must match the WHOLE string
# Python
import re
EMAIL_RE = re.compile(r"^[\w.+-]+@[\w-]+\.[\w.-]+$")
def looks_like_email(s):
return bool(EMAIL_RE.match(s.strip()))
// JavaScript
const EMAIL_RE = /^[\w.+-]+@[\w-]+\.[\w.-]+$/;
const looksLikeEmail = s => EMAIL_RE.test(s.trim());
✓ should match:
[email protected]
[email protected] ← the + matters! (below)
[email protected]
✗ should NOT match:
ada@example (no TLD)
@example.com (no local part)
ada @example.com (space)
ada@@example.com
"ada"@example..com
[email protected] is valid and deliberate — people use +tags to track who leaked their address. Forms that reject + are the #1 way developers annoy technical users. The pattern above allows it; keep it that way.
# unanchored + word boundaries — finding, not validating:
[\w.+-]+@[\w-]+\.[\w.-]+
re.findall(r"[\w.+-]+@[\w-]+\.[\w.-]+", big_text)
# validation = anchored ^...$. extraction = unanchored. never mix.