Article content and detailed guides remain in English. The selected language applies to controls and quick instructions.

Back to articles

Data Normalization Workflows for Contact Lists

On this page

What Data Normalisation Means

Data normalisation is the process of transforming data into a consistent, standard format. Raw contact data from different sources uses different formats, abbreviations, capitalisations and structures. Normalisation makes it all consistent so your systems can process it correctly.

Before normalisation:

Email Name Company Phone
JOHN.DOE@EXAMPLE.COM john doe Example Corp. (555) 123-4567
jane.smith@example.com Jane SMITH example corporation 555.123.4568
BOB@example.com bob jones jr Example Corp +15551234569

After normalisation:

Email First name Last name Company Phone
john.doe@example.com John Doe Example Corp +15551234567
jane.smith@example.com Jane Smith Example Corp +15551234568
bob@example.com Bob Jones Jr Example Corp +15551234569

Why It Matters

CRM deduplication

Your CRM's deduplication relies on matching records. If one record says "Example Corp." and another says "example corporation," the CRM may treat them as different companies, creating duplicate records.

Email personalisation

Personalisation tokens pull field values into email templates. If the first name field contains "JOHN" instead of "John," the email reads "Hi JOHN," which looks like a data error to the recipient.

Segmentation

Segmenting by company size, industry or geography requires consistent field values. If some records use "US" and others use "United States" or "USA," your segment filter misses records.

Reporting

Reports that group by company, industry or location produce fragmented results when the same entity has multiple spellings.

Deliverability

Malformed email addresses cause bounces. Normalising email format catches errors before sending.

Email Normalisation

Steps

  1. Trim whitespace. Remove leading, trailing and embedded spaces.
  2. Convert to lowercase. Email addresses are functionally case-insensitive.
  3. Fix common domain typos. gmal.com to gmail.com, yaho.com to yahoo.com.
  4. Remove invalid characters. Commas, semicolons, angle brackets that sometimes appear in copied email addresses.
  5. Validate syntax. Check for exactly one @ sign, a non-empty local part and a valid domain.
def normalise_email(email):
    if not email:
        return None

    email = email.strip().lower()
    email = email.replace(' ', '')

    # Remove angle brackets from copied addresses
    email = email.strip('<>').strip()

    # Common domain fixes
    fixes = {
        'gnail.com': 'gmail.com',
        'gmial.com': 'gmail.com',
        'gmal.com': 'gmail.com',
        'yaho.com': 'yahoo.com',
        'hotmal.com': 'hotmail.com',
        'outlok.com': 'outlook.com',
    }

    parts = email.split('@')
    if len(parts) != 2:
        return None

    local, domain = parts
    domain = fixes.get(domain, domain)

    return f"{local}@{domain}"

See Normalizing Email Formats.

For bulk email extraction and deduplication from messy source files, upload them to Email Extractor. It handles extraction, deduplication and case normalisation across 19 file formats.

Name Normalisation

Splitting full names

Source data often contains a single "Name" field instead of separate first and last name fields.

Common patterns:

Input First Last
John Doe John Doe
Doe, John John Doe
Dr. John Doe Jr. John Doe Jr.
John van der Berg John van der Berg
Mary-Jane Watson-Parker Mary-Jane Watson-Parker

Challenges:

  • Prefixes: Dr., Mr., Mrs., Ms., Prof.
  • Suffixes: Jr., Sr., III, MD, PhD, Esq.
  • Compound last names: van der Berg, de la Cruz, O'Brien, McDonald.
  • Hyphenated names.
  • Single-word names (mononyms).
  • Non-Western name ordering (family name first in Chinese, Japanese, Korean, Hungarian).

Approach:

import re

PREFIXES = {'mr', 'mrs', 'ms', 'dr', 'prof', 'sir', 'rev'}
SUFFIXES = {'jr', 'sr', 'ii', 'iii', 'iv', 'md', 'phd', 'esq',
            'dds', 'dvm', 'cpa'}

def split_name(full_name):
    if not full_name:
        return None, None

    full_name = full_name.strip()

    # Handle "Last, First" format
    if ',' in full_name:
        parts = full_name.split(',', 1)
        last = parts[0].strip()
        first = parts[1].strip()
        return title_case(first), title_case(last)

    words = full_name.split()

    # Remove prefixes
    while words and words[0].lower().rstrip('.') in PREFIXES:
        words.pop(0)

    # Remove suffixes
    suffix_parts = []
    while words and words[-1].lower().rstrip('.') in SUFFIXES:
        suffix_parts.insert(0, words.pop())

    if len(words) == 0:
        return None, None
    elif len(words) == 1:
        return title_case(words[0]), None
    else:
        first = words[0]
        last = ' '.join(words[1:])
        if suffix_parts:
            last += ' ' + ' '.join(suffix_parts)
        return title_case(first), title_case(last)

Capitalisation

Rules:

  • Standard capitalisation: Title Case (John Doe).
  • Preserve particles: van, de, von, del, di are typically lowercase (Ludwig van Beethoven).
  • Preserve internal capitalisation: McDonald, MacLeod, O'Brien.
  • Handle all-caps and all-lowercase input.
PARTICLES = {'van', 'von', 'de', 'del', 'della', 'di', 'du',
             'la', 'le', 'den', 'der', 'het', 'ten', 'ter'}

MC_PATTERN = re.compile(r'^(mc|mac)(\w)', re.IGNORECASE)

def title_case(name):
    if not name:
        return name

    words = name.split()
    result = []

    for word in words:
        lower = word.lower()

        if lower in PARTICLES:
            result.append(lower)
        elif lower.startswith("o'") and len(lower) > 2:
            result.append("O'" + lower[2:].capitalize())
        elif MC_PATTERN.match(lower):
            match = MC_PATTERN.match(lower)
            prefix = match.group(1).capitalize()
            rest = lower[len(match.group(1)):]
            result.append(prefix + rest.capitalize())
        else:
            result.append(word.capitalize())

    # First word should always be capitalised
    if result and result[0][0].islower():
        result[0] = result[0].capitalize()

    return ' '.join(result)

Company Name Normalisation

Common variations

The same company appears in data as:

Variation Normalised
Example Corp Example Corp
Example Corp. Example Corp
Example Corporation Example Corp
EXAMPLE CORP Example Corp
example corp Example Corp
Example Corp, Inc. Example Corp
Example Corp Inc Example Corp
The Example Corporation Example Corp

Steps

  1. Trim whitespace and fix encoding.
  2. Remove legal suffixes: Inc., Inc, Incorporated, LLC, Ltd, Ltd., Limited, Corp, Corp., Corporation, Co., Company, PLC, GmbH, AG, SA, SRL.
  3. Remove leading articles: "The" at the beginning.
  4. Standardise punctuation: Remove trailing periods, standardise ampersands (& vs "and").
  5. Apply title case (with exceptions for known abbreviations: IBM, AWS, SaaS).
  6. Match against a known list if you maintain one.
import re

LEGAL_SUFFIXES = re.compile(
    r',?\s*(Inc\.?|Incorporated|LLC|Ltd\.?|Limited|Corp\.?|'
    r'Corporation|Co\.?|Company|PLC|GmbH|AG|SA|SRL|LP|LLP|'
    r'PLLC|PC|PA)\s*$',
    re.IGNORECASE
)

def normalise_company(name):
    if not name:
        return None

    name = name.strip()
    name = LEGAL_SUFFIXES.sub('', name).strip()
    name = re.sub(r'^The\s+', '', name, flags=re.IGNORECASE)
    name = name.strip('.,;')

    # Title case, preserving all-caps abbreviations
    words = name.split()
    result = []
    for word in words:
        if word.isupper() and len(word) <= 5:
            result.append(word)  # Likely an abbreviation
        else:
            result.append(word.capitalize())

    return ' '.join(result)

Phone Number Normalisation

Target format

E.164 international format: +[country code][number], no spaces, dashes or parentheses.

Examples: +15551234567 (US), +442071234567 (UK), +61291234567 (Australia).

Steps

  1. Strip non-numeric characters (parentheses, dashes, spaces, dots).
  2. Detect country from prefix or context.
  3. Add country code if missing.
  4. Validate length for the detected country.
import re

def normalise_phone(phone, default_country='US'):
    if not phone:
        return None

    # Strip non-numeric except leading +
    cleaned = re.sub(r'[^\d+]', '', phone)

    # Handle US/Canada numbers
    if default_country == 'US':
        digits = re.sub(r'\D', '', cleaned)
        if len(digits) == 10:
            return f"+1{digits}"
        elif len(digits) == 11 and digits[0] == '1':
            return f"+{digits}"

    # Already in international format
    if cleaned.startswith('+') and len(cleaned) >= 10:
        return cleaned

    return None  # Could not normalise

Address Normalisation

Components

  • Street address (with abbreviation standardisation: St/Street, Ave/Avenue, Blvd/Boulevard).
  • City.
  • State/Province (full name or abbreviation, standardised).
  • Postal/ZIP code (format varies by country).
  • Country (ISO 3166 two-letter code).

Common standardisations

Raw Normalised
123 Main Street, Suite 100 123 Main St, Ste 100
New York, New York New York, NY
United States US
United Kingdom GB

Tools

For address normalisation at scale, specialised services like Google Maps Geocoding API, SmartyStreets or Melissa Data are more reliable than custom code. They handle edge cases, validate against actual postal databases and return standardised components.

Building the Workflow

Integrated normalisation pipeline

def normalise_contact(record):
    """Apply all normalisation steps to a contact record."""

    # Email
    record['email'] = normalise_email(record.get('email', ''))

    # Name
    if 'name' in record and 'first_name' not in record:
        first, last = split_name(record['name'])
        record['first_name'] = first
        record['last_name'] = last

    if 'first_name' in record:
        record['first_name'] = title_case(record.get('first_name', ''))
    if 'last_name' in record:
        record['last_name'] = title_case(record.get('last_name', ''))

    # Company
    record['company'] = normalise_company(record.get('company', ''))

    # Phone
    record['phone'] = normalise_phone(record.get('phone', ''))

    return record

When to normalise

On import: Normalise every record as it enters your system. This prevents inconsistent data from accumulating.

On merge: When combining lists from multiple sources, normalise before deduplication so matching works correctly.

Periodically: Run normalisation across your entire database quarterly to catch records that entered without normalisation (manual entry, API imports, legacy data).

Automation options

CRM workflows: HubSpot, Salesforce and other CRMs support workflow automation that can normalise fields on creation or update.

Zapier/Make: Create an automation that triggers on new contacts and normalises fields before writing to your CRM.

Custom scripts: For large-scale normalisation, a Python or Node.js script with the functions above, run on a schedule.

See Automating Data Cleaning Workflows.

Extract emails

Explore tools

Verify emails

Check address validity before using your list.

ZeroBounce

Email Verification

Verifies email lists and provides tools for monitoring deliverability.

Useful when list cleaning and sender health belong in one workflow.

Explore ZeroBounce (opens in a new tab)