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:
| 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:
| 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
- Trim whitespace. Remove leading, trailing and embedded spaces.
- Convert to lowercase. Email addresses are functionally case-insensitive.
- Fix common domain typos. gmal.com to gmail.com, yaho.com to yahoo.com.
- Remove invalid characters. Commas, semicolons, angle brackets that sometimes appear in copied email addresses.
- 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
- Trim whitespace and fix encoding.
- Remove legal suffixes: Inc., Inc, Incorporated, LLC, Ltd, Ltd., Limited, Corp, Corp., Corporation, Co., Company, PLC, GmbH, AG, SA, SRL.
- Remove leading articles: "The" at the beginning.
- Standardise punctuation: Remove trailing periods, standardise ampersands (& vs "and").
- Apply title case (with exceptions for known abbreviations: IBM, AWS, SaaS).
- 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
- Strip non-numeric characters (parentheses, dashes, spaces, dots).
- Detect country from prefix or context.
- Add country code if missing.
- 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.