International Email Validation: Handling Global Domains and Characters
Navigate the complexities of international email validation, including IDN support, regional providers, and character encoding challenges.
Table of Contents
Table of Contents
International Email Validation: Handling Global Domains and Characters
Global applications must handle email addresses from around the world, each with unique characteristics, encoding requirements, and validation challenges. Proper international email validation ensures inclusive user experiences across all markets.
Global Email Challenges
International email validation presents unique technical and cultural challenges.
Character Encoding Issues
- Unicode support in email addresses
- Punycode conversion for IDN domains
- UTF-8 handling in local parts
- Normalization and canonicalization
Regional Variations
- Different email format preferences
- Local domain popularity variations
- Cultural naming conventions
- Regional privacy requirements
Internationalized Domain Names
IDN enables email addresses with non-ASCII characters in domain names.
Punycode Conversion
International domains are converted to ASCII-compatible encoding:
- münchen.de becomes xn--mnchen-3ya.de
- 测试.中国 becomes xn--0zwm56d.xn--fiqs8s
- العربية.امارات becomes xn--mgbayh7gpa.xn--mgbai9azgqp6j
Implementation Considerations
- Automatic punycode conversion
- Display vs. processing formats
- Validation of converted domains
- User experience optimization
Regional Email Providers
Different regions have dominant email providers with unique characteristics.
Asian Markets
- QQ Mail (China) - Numeric usernames common
- Naver (South Korea) - Hangul character support
- Yahoo Japan - Local domain preferences
- Yandex (Russia) - Cyrillic character handling
European Markets
- GMX (Germany) - Strict validation rules
- Mail.ru (Russia) - Regional domain focus
- Orange (France) - Carrier-based email
- Tiscali (Italy) - ISP-provided email
#### Implementation Considerations
- Automatic punycode conversion
- Display vs. processing formats
- Validation of converted domains
- User experience optimization
Practical Implementation Examples
Unicode and IDN Email Processing
// Comprehensive Unicode and IDN email validation system
interface UnicodeValidationResult {
isValid: boolean
punycodeDomain: string
displayDomain: string
localPart: string
issues: string[]
recommendations: string[]
}
interface RegionalEmailConfig {
region: string
commonProviders: string[]
localPartPatterns: RegExp[]
domainRestrictions: string[]
specialRules: string[]
}
class InternationalEmailValidator {
private regionalConfigs: Map<string, RegionalEmailConfig> = new Map()
private unicodeNormalization: boolean = true
constructor() {
this.initializeRegionalConfigs()
}
async validateInternationalEmail(email: string): Promise<UnicodeValidationResult> {
const result: UnicodeValidationResult = {
isValid: false,
punycodeDomain: '',
displayDomain: '',
localPart: '',
issues: [],
recommendations: []
}
try {
// Parse email address
const [localPart, domain] = email.toLowerCase().split('@')
result.localPart = localPart
if (!localPart || !domain) {
result.issues.push('Invalid email format')
return result
}
// Validate local part (username)
const localPartValidation = await this.validateLocalPart(localPart, domain)
if (!localPartValidation.isValid) {
result.issues.push(...localPartValidation.issues)
}
// Process domain - handle both ASCII and IDN
const domainValidation = await this.processInternationalDomain(domain)
result.punycodeDomain = domainValidation.punycode
result.displayDomain = domainValidation.display
result.issues.push(...domainValidation.issues)
// Overall validation
result.isValid = result.issues.length === 0
if (!result.isValid) {
result.recommendations = this.generateRecommendations(result.issues, domain)
}
return result
} catch (error) {
result.issues.push(`Processing error: ${error}`)
return result
}
}
private async validateLocalPart(localPart: string, domain: string): Promise<{ isValid: boolean; issues: string[] }> {
const issues: string[] = []
// Basic length checks
if (localPart.length === 0) {
issues.push('Local part cannot be empty')
}
if (localPart.length > 64) {
issues.push('Local part too long (max 64 characters)')
}
// Character validation
if (!/^[!-:<-~-ÿĀ-ſƀ-ɏɐ-ʯʰ-˿̀-ͯͰ-ϿЀ-ӿԀ-ԯ-֏--ۿ܀-ݏݐ-ݿހ-߀-߿ࠀ-ࡀ-ࡠ-ࡿࢀ-࢟ࢠ-ࣿऀ-ॿঀ---૿--ఀ-౿ಀ-ഀ-ൿ---ༀ-က-႟Ⴀ-ჿᄀ-ᇿሀ-ᎀ-Ꭰ-᐀-ᙿ -ᚠ-ᜀ-ᜟᜠ-ᝀ-ᝠ-ក-᠀-ᤀ-᥏ᥐ-ᦀ-᧟᧠-᧿ᨀ-᨟ᨠ-᪰-ᬀ-ᮀ-ᮿᯀ-᯿ᰀ-ᱏ᱐-᱿ᲀ-᳀-᳐-ᴀ-ᵿᶀ-ᶿ᷀-᷿Ḁ-ỿἀ- -⁰-₠-⃐-℀-⅏⅐-←-⇿∀-⋿⌀-⏿␀-⑀-①-⓿─-╿▀-▟■-◿☀-⛿✀-➿⟀-⟯⟰-⟿⠀-⣿⤀-⥿⦀-⧿⨀-⫿⬀-⯿Ⰰ-ⱟⱠ-ⱿⲀ-⳿ⴀ-ⴰ-⵿ⶀ-ⷠ-ⷿ⸀-⺀-⼀-⿰- -〿-ゟ゠-ヿ-ㄯ-㆐-㆟ㆠ-ㆿ㇀-ㇰ-ㇿ㈀-㋿㌀-㏿㐀-䶿䷀-䷿一-鿿ꀀ-꒐-ꓐ-꓿ꔀ-Ꙁ-ꚟꚠ-꜀-ꜟ꜠-ꟿꠀ-꠰-ꡀ-ꢀ-꣠-ꣿ꤀-꤯ꤰ-꥟ꥠ-ꦀ-꧟ꧠ-ꨀ-꩟ꩠ-ꩿꪀ-꫟ꫠ--ꬰ-ꭰ-ꮿꯀ-가-ힰ-�-��--�-豈-ff-ﭏﭐ-﷿︀-️︐-︠-︯︰-﹏﹐-ﹰ--]*$/.test(localPart)) {
issues.push('Invalid characters in local part')
}
// Check for consecutive dots
if (localPart.includes('..')) {
issues.push('Consecutive dots not allowed in local part')
}
// Leading/trailing dots
if (localPart.startsWith('.') || localPart.endsWith('.')) {
issues.push('Local part cannot start or end with dot')
}
// Regional provider specific rules
const providerRules = this.getProviderSpecificRules(domain)
for (const rule of providerRules) {
if (!rule.pattern.test(localPart)) {
issues.push(`Local part violates ${rule.provider} rules: ${rule.description}`)
}
}
return { isValid: issues.length === 0, issues }
}
private async processInternationalDomain(domain: string): Promise<{
punycode: string
display: string
issues: string[]
}> {
const result = {
punycode: domain,
display: domain,
issues: [] as string[]
}
try {
// Check if domain contains non-ASCII characters
if (/[^ -~]/.test(domain)) {
// This is an IDN domain
result.display = domain
try {
// Convert to punycode for processing
result.punycode = this.toPunycode(domain)
// Validate punycode format
if (!result.punycode.startsWith('xn--')) {
result.issues.push('Invalid punycode format')
}
// Validate punycode domain structure
const punycodeValidation = await this.validatePunycodeDomain(result.punycode)
if (!punycodeValidation.isValid) {
result.issues.push(...punycodeValidation.issues)
}
} catch (error) {
result.issues.push(`Punycode conversion failed: ${error}`)
}
} else {
// ASCII domain - validate directly
const asciiValidation = await this.validateASCIIDomain(domain)
if (!asciiValidation.isValid) {
result.issues.push(...asciiValidation.issues)
}
}
return result
} catch (error) {
result.issues.push(`Domain processing error: ${error}`)
return result
}
}
private toPunycode(domain: string): string {
// Simplified punycode conversion for demo
// In production, use a proper IDNA/punycode library
// Check if already punycode
if (domain.startsWith('xn--')) {
return domain
}
// For demo, simulate punycode conversion
// Real implementation would use: https://github.com/bestiejs/punycode.js
return `xn--${btoa(domain).replace(/[^a-z0-9]/gi, '').toLowerCase()}`
}
private async validatePunycodeDomain(domain: string): Promise<{ isValid: boolean; issues: string[] }> {
const issues: string[] = []
// Basic punycode validation
if (!/^xn--[a-z0-9]+$/.test(domain)) {
issues.push('Invalid punycode format')
}
if (domain.length > 253) {
issues.push('Domain too long')
}
// Check for valid TLD
const tld = domain.split('.').pop()
if (!tld || tld.length < 2) {
issues.push('Invalid top-level domain')
}
return { isValid: issues.length === 0, issues }
}
private async validateASCIIDomain(domain: string): Promise<{ isValid: boolean; issues: string[] }> {
const issues: string[] = []
// Basic domain validation
if (domain.length > 253) {
issues.push('Domain too long')
}
if (!/^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/.test(domain)) {
issues.push('Invalid domain format')
}
// Check for valid TLD
const labels = domain.split('.')
if (labels.length < 2) {
issues.push('Domain must have at least one subdomain')
}
const tld = labels[labels.length - 1]
if (tld.length < 2 || tld.length > 63) {
issues.push('Invalid TLD length')
}
return { isValid: issues.length === 0, issues }
}
private getProviderSpecificRules(domain: string): Array<{ provider: string; pattern: RegExp; description: string }> {
// Extract provider from domain
const provider = this.extractProviderFromDomain(domain)
// Provider-specific validation rules
const rules: Record<string, Array<{ pattern: RegExp; description: string }>> = {
'qq.com': [
{ pattern: /^[0-9]+$/, description: 'QQ Mail typically uses numeric usernames' }
],
'naver.com': [
{ pattern: /^[ᄀ-ᇿ-가-]+$/, description: 'Naver supports Hangul characters' }
],
'yandex.ru': [
{ pattern: /^[Ѐ-ӿ]+$/, description: 'Yandex supports Cyrillic characters' }
]
}
return (rules[provider] || []).map(rule => ({
provider,
...rule
}))
}
private extractProviderFromDomain(domain: string): string {
return domain.split('.').slice(-2).join('.')
}
private generateRecommendations(issues: string[], domain: string): string[] {
const recommendations: string[] = []
if (issues.some(issue => issue.includes('Unicode'))) {
recommendations.push('Consider implementing Unicode normalization (NFKC)')
recommendations.push('Use punycode for server-side processing')
}
if (issues.some(issue => issue.includes('domain'))) {
recommendations.push('Verify domain exists using DNS lookup')
recommendations.push('Check for typos in domain name')
}
if (domain.includes('xn--')) {
recommendations.push('Display Unicode version to users for clarity')
recommendations.push('Store both punycode and display versions')
}
return recommendations
}
private initializeRegionalConfigs(): void {
const configs: RegionalEmailConfig[] = [
{
region: 'china',
commonProviders: ['qq.com', '163.com', '126.com', 'sina.com'],
localPartPatterns: [/^[0-9]+$/, /^[一-鿿]+$/],
domainRestrictions: ['.cn', '.com', '.net'],
specialRules: ['Numeric usernames common', 'Chinese characters supported']
},
{
region: 'korea',
commonProviders: ['naver.com', 'daum.net', 'gmail.com'],
localPartPatterns: [/^[ᄀ-ᇿ-가-]+$/],
domainRestrictions: ['.kr', '.com'],
specialRules: ['Hangul character support', 'Short usernames common']
},
{
region: 'russia',
commonProviders: ['yandex.ru', 'mail.ru', 'gmail.com'],
localPartPatterns: [/^[Ѐ-ӿ]+$/],
domainRestrictions: ['.ru', '.com'],
specialRules: ['Cyrillic character support', 'Patronymic naming common']
}
]
configs.forEach(config => {
this.regionalConfigs.set(config.region, config)
})
}
// Unicode normalization utilities
normalizeUnicode(text: string, form: 'NFC' | 'NFD' | 'NFKC' | 'NFKD' = 'NFKC'): string {
// In production, use a proper Unicode normalization library
// For demo, return text as-is
return text
}
// Detect script/language of email address
detectScript(email: string): string {
const scripts = {
latin: /[ -]/,
cyrillic: /[Ѐ-ӿ]/,
chinese: /[一-鿿]/,
korean: /[ᄀ-ᇿ-가-]/,
arabic: /[-ۿ]/,
devanagari: /[ऀ-ॿ]/
}
for (const [script, pattern] of Object.entries(scripts)) {
if (pattern.test(email)) {
return script
}
}
return 'unknown'
}
// Get region-specific recommendations
getRegionalRecommendations(email: string): string[] {
const script = this.detectScript(email)
const recommendations: string[] = []
switch (script) {
case 'chinese':
recommendations.push('Consider QQ Mail and 163.com providers')
recommendations.push('Numeric usernames are common')
break
case 'korean':
recommendations.push('Consider Naver and Daum providers')
recommendations.push('Hangul characters are supported')
break
case 'cyrillic':
recommendations.push('Consider Yandex and Mail.ru providers')
recommendations.push('Cyrillic characters are supported')
break
}
return recommendations
}
}
// Usage example
const internationalValidator = new InternationalEmailValidator()
// Validate international email
app.post('/api/validate-international-email', async (req, res) => {
try {
const { email } = req.body
if (!email) {
return res.status(400).json({ error: 'Email required' })
}
const result = await internationalValidator.validateInternationalEmail(email)
const script = internationalValidator.detectScript(email)
const regionalRecommendations = internationalValidator.getRegionalRecommendations(email)
res.json({
validation: result,
script: script,
regionalRecommendations,
timestamp: new Date().toISOString()
})
} catch (error) {
console.error('International email validation error:', error)
res.status(500).json({ error: 'Validation failed' })
}
})
// Unicode normalization endpoint
app.post('/api/normalize-unicode', async (req, res) => {
try {
const { text, form = 'NFKC' } = req.body
if (!text) {
return res.status(400).json({ error: 'Text required' })
}
const normalized = internationalValidator.normalizeUnicode(text, form)
res.json({
original: text,
normalized,
form,
timestamp: new Date().toISOString()
})
} catch (error) {
console.error('Unicode normalization error:', error)
res.status(500).json({ error: 'Normalization failed' })
}
})
console.log('International email validation system initialized')Regional Email Provider Validation
// Specialized validation for regional email providers
interface ProviderValidationRule {
provider: string
region: string
localPartPattern: RegExp
domainPattern: RegExp
specialValidations: string[]
confidence: number
}
interface ProviderValidationResult {
provider: string
isValid: boolean
confidence: number
issues: string[]
suggestions: string[]
}
class RegionalEmailProvider {
private validationRules: Map<string, ProviderValidationRule> = new Map()
constructor() {
this.initializeProviderRules()
}
async validateProviderEmail(email: string): Promise<ProviderValidationResult[]> {
const results: ProviderValidationResult[] = []
const [localPart, domain] = email.toLowerCase().split('@')
if (!localPart || !domain) {
return results
}
// Find applicable providers
const applicableProviders = this.findApplicableProviders(domain)
for (const provider of applicableProviders) {
const rule = this.validationRules.get(provider)
if (!rule) continue
const result = await this.validateAgainstRule(email, localPart, domain, rule)
results.push(result)
}
return results
}
private async validateAgainstRule(
email: string,
localPart: string,
domain: string,
rule: ProviderValidationRule
): Promise<ProviderValidationResult> {
const issues: string[] = []
const suggestions: string[] = []
// Local part validation
if (!rule.localPartPattern.test(localPart)) {
issues.push(`Local part doesn't match ${rule.provider} pattern`)
suggestions.push(`Use pattern: ${rule.localPartPattern.source}`)
}
// Domain validation
if (!rule.domainPattern.test(domain)) {
issues.push(`Domain doesn't match ${rule.provider} requirements`)
}
// Special validations
for (const validation of rule.specialValidations) {
const specialValidation = await this.performSpecialValidation(email, validation)
if (!specialValidation.isValid) {
issues.push(specialValidation.issue)
suggestions.push(specialValidation.suggestion)
}
}
return {
provider: rule.provider,
isValid: issues.length === 0,
confidence: rule.confidence,
issues,
suggestions
}
}
private async performSpecialValidation(email: string, validationType: string): Promise<{
isValid: boolean
issue: string
suggestion: string
}> {
switch (validationType) {
case 'check_mx_records':
return await this.checkMXRecords(email)
case 'check_smtp_features':
return await this.checkSMTPFeatures(email)
case 'validate_local_conventions':
return this.validateLocalConventions(email)
default:
return { isValid: true, issue: '', suggestion: '' }
}
}
private async checkMXRecords(email: string): Promise<{ isValid: boolean; issue: string; suggestion: string }> {
const [, domain] = email.split('@')
// In production, perform actual MX record lookup
// For demo, simulate MX record check
const mockMXRecords: Record<string, boolean> = {
'qq.com': true,
'163.com': true,
'naver.com': true,
'yandex.ru': true
}
const hasMX = mockMXRecords[domain] || false
if (!hasMX) {
return {
isValid: false,
issue: 'MX records not found',
suggestion: 'Verify domain exists and has mail servers'
}
}
return { isValid: true, issue: '', suggestion: '' }
}
private async checkSMTPFeatures(email: string): Promise<{ isValid: boolean; issue: string; suggestion: string }> {
// In production, test SMTP connection and features
// For demo, simulate SMTP feature check
return {
isValid: true,
issue: '',
suggestion: 'SMTP features verified'
}
}
private validateLocalConventions(email: string): { isValid: boolean; issue: string; suggestion: string } {
const [localPart] = email.split('@')
// Check for common local convention violations
if (localPart.includes('..')) {
return {
isValid: false,
issue: 'Consecutive dots not allowed',
suggestion: 'Remove consecutive dots from local part'
}
}
if (localPart.startsWith('.') || localPart.endsWith('.')) {
return {
isValid: false,
issue: 'Local part cannot start or end with dot',
suggestion: 'Remove leading/trailing dots'
}
}
return { isValid: true, issue: '', suggestion: '' }
}
private findApplicableProviders(domain: string): string[] {
const providers: string[] = []
for (const [provider, rule] of this.validationRules.entries()) {
if (rule.domainPattern.test(domain)) {
providers.push(provider)
}
}
return providers
}
private initializeProviderRules(): void {
const rules: ProviderValidationRule[] = [
{
provider: 'qq.com',
region: 'china',
localPartPattern: /^[0-9a-zA-Z一-鿿._-]{1,20}$/,
domainPattern: /^qq.com$/,
specialValidations: ['check_mx_records', 'validate_local_conventions'],
confidence: 0.95
},
{
provider: '163.com',
region: 'china',
localPartPattern: /^[0-9a-zA-Z一-鿿._-]{1,18}$/,
domainPattern: /^163.com$/,
specialValidations: ['check_mx_records'],
confidence: 0.90
},
{
provider: 'naver.com',
region: 'korea',
localPartPattern: /^[ᄀ-ᇿ-가-a-zA-Z0-9._-]{1,20}$/,
domainPattern: /^naver.com$/,
specialValidations: ['check_mx_records', 'check_smtp_features'],
confidence: 0.92
},
{
provider: 'yandex.ru',
region: 'russia',
localPartPattern: /^[Ѐ-ӿa-zA-Z0-9._-]{1,30}$/,
domainPattern: /^(yandex|mail).ru$/,
specialValidations: ['check_mx_records'],
confidence: 0.88
},
{
provider: 'gmx.de',
region: 'germany',
localPartPattern: /^[a-zA-Z0-9._-]{1,64}$/,
domainPattern: /^gmx.de$/,
specialValidations: ['check_mx_records', 'validate_local_conventions'],
confidence: 0.93
}
]
rules.forEach(rule => {
this.validationRules.set(rule.provider, rule)
})
}
}
// Multi-provider validation service
class MultiProviderEmailValidator {
private regionalProvider: RegionalEmailProvider
private internationalValidator: InternationalEmailValidator
private providerCache: Map<string, ProviderValidationResult[]> = new Map()
constructor() {
this.regionalProvider = new RegionalEmailProvider()
this.internationalValidator = new InternationalEmailValidator()
}
async validateWithProviders(email: string): Promise<{
international: UnicodeValidationResult
providers: ProviderValidationResult[]
bestProvider?: string
overallConfidence: number
}> {
// Check cache first
const cacheKey = email.toLowerCase()
if (this.providerCache.has(cacheKey)) {
const cached = this.providerCache.get(cacheKey)!
if (Date.now() - 60 * 60 * 1000 < cached[0]?.timestamp || 0) { // 1 hour cache
return cached[1]
}
}
// Perform validations
const international = await this.internationalValidator.validateInternationalEmail(email)
const providers = await this.regionalProvider.validateProviderEmail(email)
// Determine best provider
const validProviders = providers.filter(p => p.isValid)
const bestProvider = validProviders.length > 0
? validProviders.reduce((best, current) =>
current.confidence > best.confidence ? current : best
).provider
: undefined
// Calculate overall confidence
const providerConfidences = providers.map(p => p.confidence)
const avgProviderConfidence = providerConfidences.length > 0
? providerConfidences.reduce((a, b) => a + b, 0) / providerConfidences.length
: 0
const overallConfidence = Math.min(100,
(international.isValid ? 70 : 30) + (avgProviderConfidence * 30)
)
const result = {
international,
providers,
bestProvider,
overallConfidence
}
// Cache result
this.providerCache.set(cacheKey, [Date.now(), result])
return result
}
// Get provider statistics
getProviderStats(): Record<string, {
totalValidations: number
successRate: number
avgConfidence: number
}> {
// In production, maintain and return provider statistics
return {}
}
}
// Initialize multi-provider validator
const multiProviderValidator = new MultiProviderEmailValidator()
// Enhanced validation endpoint with provider analysis
app.post('/api/validate-email-providers', async (req, res) => {
try {
const { email } = req.body
if (!email) {
return res.status(400).json({ error: 'Email required' })
}
const result = await multiProviderValidator.validateWithProviders(email)
res.json({
validation: result,
timestamp: new Date().toISOString()
})
} catch (error) {
console.error('Multi-provider validation error:', error)
res.status(500).json({ error: 'Validation failed' })
}
})
// Provider statistics endpoint
app.get('/api/provider-stats', (req, res) => {
const stats = multiProviderValidator.getProviderStats()
res.json({
stats,
timestamp: new Date().toISOString()
})
})
console.log('Regional email provider validation system initialized')Multi-Layer Validation Strategy
// Comprehensive multi-layer email validation strategy
interface ValidationLayer {
name: string
priority: number // 1-10, higher = more important
timeout: number // milliseconds
weight: number // 0-1, contribution to final score
validator: (email: string) => Promise<ValidationResult>
}
interface ValidationResult {
layer: string
isValid: boolean
score: number // 0-100
issues: string[]
metadata: Record<string, any>
}
interface ComprehensiveValidationResult {
email: string
overallScore: number
isValid: boolean
layerResults: ValidationResult[]
topIssues: string[]
recommendations: string[]
processingTime: number
}
class MultiLayerEmailValidator {
private layers: ValidationLayer[] = []
private performanceMetrics: Map<string, number[]> = new Map()
constructor() {
this.initializeValidationLayers()
}
async validateEmail(email: string): Promise<ComprehensiveValidationResult> {
const startTime = Date.now()
const layerResults: ValidationResult[] = []
try {
// Run all validation layers in parallel with timeout
const validationPromises = this.layers.map(async (layer) => {
const layerStartTime = Date.now()
try {
const result = await Promise.race([
layer.validator(email),
new Promise<ValidationResult>((_, reject) =>
setTimeout(() => reject(new Error(`Layer ${layer.name} timeout`)), layer.timeout)
)
])
// Track performance
const layerTime = Date.now() - layerStartTime
this.recordLayerPerformance(layer.name, layerTime)
return { ...result, layer: layer.name }
} catch (error) {
console.error(`Layer ${layer.name} failed:`, error)
// Return failed result for this layer
return {
layer: layer.name,
isValid: false,
score: 0,
issues: [`Layer ${layer.name} error: ${error}`],
metadata: {}
}
}
})
// Wait for all layers to complete
const results = await Promise.allSettled(validationPromises)
// Process results
for (const result of results) {
if (result.status === 'fulfilled') {
layerResults.push(result.value)
}
}
// Calculate overall score
const weightedScore = this.calculateWeightedScore(layerResults)
const isValid = weightedScore > 70 && !layerResults.some(l => l.issues.includes('Critical error'))
// Extract top issues
const allIssues = layerResults.flatMap(l => l.issues)
const topIssues = this.prioritizeIssues(allIssues)
// Generate recommendations
const recommendations = this.generateComprehensiveRecommendations(layerResults, isValid)
const processingTime = Date.now() - startTime
return {
email,
overallScore: weightedScore,
isValid,
layerResults,
topIssues,
recommendations,
processingTime
}
} catch (error) {
console.error('Multi-layer validation error:', error)
return {
email,
overallScore: 0,
isValid: false,
layerResults: [],
topIssues: ['Validation system error'],
recommendations: ['Retry validation or contact support'],
processingTime: Date.now() - startTime
}
}
}
private calculateWeightedScore(results: ValidationResult[]): number {
let totalWeightedScore = 0
let totalWeight = 0
for (const result of results) {
const layer = this.layers.find(l => l.name === result.layer)
if (layer) {
totalWeightedScore += result.score * layer.weight
totalWeight += layer.weight
}
}
return totalWeight > 0 ? totalWeightedScore / totalWeight : 0
}
private prioritizeIssues(issues: string[]): string[] {
// Prioritize issues by severity
const criticalPatterns = ['Critical error', 'Invalid format', 'Domain not found']
const highPatterns = ['Unicode issue', 'Provider mismatch', 'Suspicious pattern']
const mediumPatterns = ['Performance warning', 'Minor format issue']
const critical = issues.filter(issue => criticalPatterns.some(p => issue.includes(p)))
const high = issues.filter(issue => highPatterns.some(p => issue.includes(p)))
const medium = issues.filter(issue => mediumPatterns.some(p => issue.includes(p)))
return [...critical, ...high, ...medium].slice(0, 5)
}
private generateComprehensiveRecommendations(results: ValidationResult[], isValid: boolean): string[] {
const recommendations: string[] = []
if (!isValid) {
recommendations.push('Email address appears invalid')
// Layer-specific recommendations
for (const result of results) {
if (!result.isValid && result.issues.length > 0) {
switch (result.layer) {
case 'format':
recommendations.push('Check email format (local@domain.com)')
break
case 'unicode':
recommendations.push('Consider Unicode normalization')
break
case 'provider':
recommendations.push('Verify email provider exists')
break
case 'security':
recommendations.push('Review for suspicious patterns')
break
}
}
}
} else {
recommendations.push('Email validation passed')
}
return recommendations
}
private recordLayerPerformance(layerName: string, executionTime: number): void {
if (!this.performanceMetrics.has(layerName)) {
this.performanceMetrics.set(layerName, [])
}
const times = this.performanceMetrics.get(layerName)!
times.push(executionTime)
// Keep only last 100 measurements
if (times.length > 100) {
times.shift()
}
}
private initializeValidationLayers(): void {
this.layers = [
{
name: 'format',
priority: 10,
timeout: 1000,
weight: 0.3,
validator: this.basicFormatValidator.bind(this)
},
{
name: 'unicode',
priority: 9,
timeout: 2000,
weight: 0.25,
validator: this.unicodeValidator.bind(this)
},
{
name: 'provider',
priority: 8,
timeout: 5000,
weight: 0.2,
validator: this.providerValidator.bind(this)
},
{
name: 'security',
priority: 7,
timeout: 3000,
weight: 0.15,
validator: this.securityValidator.bind(this)
},
{
name: 'performance',
priority: 6,
timeout: 2000,
weight: 0.1,
validator: this.performanceValidator.bind(this)
}
]
}
private async basicFormatValidator(email: string): Promise<ValidationResult> {
const issues: string[] = []
if (!email.includes('@')) {
issues.push('Missing @ symbol')
}
const [localPart, domain] = email.split('@')
if (!localPart || !domain) {
issues.push('Invalid email structure')
}
if (localPart.length > 64) {
issues.push('Local part too long')
}
if (domain.length > 253) {
issues.push('Domain too long')
}
return {
layer: 'format',
isValid: issues.length === 0,
score: issues.length === 0 ? 100 : Math.max(0, 100 - issues.length * 20),
issues,
metadata: { localPart, domain }
}
}
private async unicodeValidator(email: string): Promise<ValidationResult> {
const issues: string[] = []
let score = 100
// Check for Unicode normalization issues
const normalized = this.normalizeUnicode(email)
if (normalized !== email) {
issues.push('Unicode normalization recommended')
score -= 10
}
// Check for IDN domain
if (/[^ -~]/.test(email)) {
issues.push('International characters detected')
// This is not necessarily an issue, just informational
}
return {
layer: 'unicode',
isValid: issues.length === 0,
score,
issues,
metadata: { hasUnicode: /[^ -~]/.test(email) }
}
}
private async providerValidator(email: string): Promise<ValidationResult> {
const [, domain] = email.split('@')
const issues: string[] = []
let score = 80
// Check against known providers
const providerResults = await this.regionalProvider.validateProviderEmail(email)
if (providerResults.length === 0) {
issues.push('No provider validation available')
score -= 20
} else {
const bestResult = providerResults.reduce((best, current) =>
current.confidence > best.confidence ? current : best
)
if (!bestResult.isValid) {
issues.push(...bestResult.issues)
score = Math.min(score, bestResult.confidence * 100)
} else {
score = Math.max(score, bestResult.confidence * 100)
}
}
return {
layer: 'provider',
isValid: issues.length === 0,
score,
issues,
metadata: { providerResults }
}
}
private async securityValidator(email: string): Promise<ValidationResult> {
const issues: string[] = []
let score = 100
// Check for suspicious patterns
if (email.includes('..')) {
issues.push('Suspicious consecutive dots')
score -= 30
}
if (email.length > 100) {
issues.push('Unusually long email address')
score -= 15
}
// Check for suspicious keywords
const suspiciousKeywords = ['admin', 'test', 'spam', 'abuse']
if (suspiciousKeywords.some(keyword => email.toLowerCase().includes(keyword))) {
issues.push('Suspicious keywords detected')
score -= 25
}
return {
layer: 'security',
isValid: issues.length === 0,
score,
issues,
metadata: { suspiciousKeywords: suspiciousKeywords.filter(k => email.includes(k)) }
}
}
private async performanceValidator(email: string): Promise<ValidationResult> {
const startTime = Date.now()
const issues: string[] = []
let score = 100
// Simulate performance check
await new Promise(resolve => setTimeout(resolve, 100))
const processingTime = Date.now() - startTime
if (processingTime > 500) {
issues.push('Slow validation performance')
score -= 20
}
return {
layer: 'performance',
isValid: issues.length === 0,
score,
issues,
metadata: { processingTime }
}
}
private normalizeUnicode(text: string): string {
// Simplified Unicode normalization
// In production, use proper normalization library
return text.normalize('NFKC')
}
// Performance monitoring
getLayerPerformance(): Record<string, {
avgExecutionTime: number
successRate: number
totalExecutions: number
}> {
const performance: Record<string, any> = {}
for (const [layerName, times] of this.performanceMetrics.entries()) {
const avgTime = times.reduce((a, b) => a + b, 0) / times.length
const successCount = times.filter(t => t < 2000).length // Success if < 2s
performance[layerName] = {
avgExecutionTime: avgTime,
successRate: (successCount / times.length) * 100,
totalExecutions: times.length
}
}
return performance
}
}
// Initialize multi-layer validator
const multiLayerValidator = new MultiLayerEmailValidator()
// Comprehensive validation endpoint
app.post('/api/validate-email-comprehensive', async (req, res) => {
try {
const { email } = req.body
if (!email) {
return res.status(400).json({ error: 'Email required' })
}
const result = await multiLayerValidator.validateEmail(email)
res.json({
validation: result,
timestamp: new Date().toISOString()
})
} catch (error) {
console.error('Comprehensive validation error:', error)
res.status(500).json({ error: 'Validation failed' })
}
})
// Layer performance monitoring endpoint
app.get('/api/validation-performance', (req, res) => {
const performance = multiLayerValidator.getLayerPerformance()
res.json({
performance,
timestamp: new Date().toISOString()
})
})
console.log('Multi-layer email validation system initialized')Performance Optimization Strategies
// Performance optimization for international email validation
interface CacheEntry {
result: ComprehensiveValidationResult
timestamp: number
accessCount: number
}
interface PerformanceMetrics {
totalValidations: number
averageResponseTime: number
cacheHitRate: number
errorRate: number
regionalDistribution: Record<string, number>
}
class OptimizedInternationalValidator {
private cache: Map<string, CacheEntry> = new Map()
private metrics: PerformanceMetrics = {
totalValidations: 0,
averageResponseTime: 0,
cacheHitRate: 0,
errorRate: 0,
regionalDistribution: {}
}
private maxCacheSize: number = 50000
constructor() {
this.startMetricsCollection()
}
async validateEmailOptimized(email: string): Promise<ComprehensiveValidationResult> {
const startTime = Date.now()
this.metrics.totalValidations++
// Check cache first
const cacheKey = this.generateCacheKey(email)
const cached = this.getFromCache(cacheKey)
if (cached) {
this.metrics.cacheHitRate = this.updateCacheHitRate(true)
this.recordResponseTime(Date.now() - startTime)
return { ...cached.result, cached: true }
}
try {
// Determine region for optimization
const region = this.detectEmailRegion(email)
// Use region-specific validation strategy
const result = await this.validateByRegion(email, region)
// Cache result
this.addToCache(cacheKey, result)
// Update metrics
this.metrics.cacheHitRate = this.updateCacheHitRate(false)
this.metrics.regionalDistribution[region] =
(this.metrics.regionalDistribution[region] || 0) + 1
this.recordResponseTime(Date.now() - startTime)
return result
} catch (error) {
this.metrics.errorRate = this.updateErrorRate(true)
this.recordResponseTime(Date.now() - startTime)
throw error
}
}
private generateCacheKey(email: string): string {
// Create consistent cache key
return btoa(email.toLowerCase().trim()).slice(0, 16)
}
private getFromCache(key: string): CacheEntry | null {
const entry = this.cache.get(key)
if (entry && Date.now() - entry.timestamp < 60 * 60 * 1000) { // 1 hour TTL
entry.accessCount++
return entry
}
if (entry) {
this.cache.delete(key) // Remove expired entry
}
return null
}
private addToCache(key: string, result: ComprehensiveValidationResult): void {
// Implement LRU-like cache management
if (this.cache.size >= this.maxCacheSize) {
this.evictLeastRecentlyUsed()
}
this.cache.set(key, {
result,
timestamp: Date.now(),
accessCount: 1
})
}
private evictLeastRecentlyUsed(): void {
let oldestKey = ''
let oldestAccessCount = Infinity
for (const [key, entry] of this.cache.entries()) {
if (entry.accessCount < oldestAccessCount) {
oldestAccessCount = entry.accessCount
oldestKey = key
}
}
if (oldestKey) {
this.cache.delete(oldestKey)
}
}
private detectEmailRegion(email: string): string {
const [, domain] = email.toLowerCase().split('@')
// Regional domain patterns
const regionalPatterns: Record<string, RegExp[]> = {
china: [/qq.com$/, /163.com$/, /126.com$/, /.cn$/],
korea: [/naver.com$/, /daum.net$/, /.kr$/],
russia: [/yandex.ru$/, /mail.ru$/, /.ru$/],
germany: [/gmx.de$/, /web.de$/, /.de$/],
france: [/orange.fr$/, /free.fr$/, /.fr$/],
japan: [/yahoo.co.jp$/, /docomo.ne.jp$/, /.jp$/]
}
for (const [region, patterns] of Object.entries(regionalPatterns)) {
if (patterns.some(pattern => pattern.test(domain))) {
return region
}
}
return 'international'
}
private async validateByRegion(email: string, region: string): Promise<ComprehensiveValidationResult> {
// Use region-specific validation strategies
switch (region) {
case 'china':
return this.validateChineseEmail(email)
case 'korea':
return this.validateKoreanEmail(email)
case 'russia':
return this.validateRussianEmail(email)
default:
return this.validateInternationalEmail(email)
}
}
private async validateChineseEmail(email: string): Promise<ComprehensiveValidationResult> {
// Chinese-specific validation with QQ/163 patterns
const baseResult = await this.multiLayerValidator.validateEmail(email)
// Additional Chinese-specific checks
const [localPart, domain] = email.split('@')
if (domain === 'qq.com' && !/^[0-9]+$/.test(localPart)) {
baseResult.overallScore -= 20
baseResult.topIssues.push('QQ Mail typically uses numeric usernames')
}
return baseResult
}
private async validateKoreanEmail(email: string): Promise<ComprehensiveValidationResult> {
// Korean-specific validation with Naver patterns
const baseResult = await this.multiLayerValidator.validateEmail(email)
// Check for Hangul characters
if (/[ᄀ-ᇿ-가-]/.test(email)) {
// Additional Unicode validation for Korean
baseResult.recommendations.push('Korean characters detected - ensure proper encoding')
}
return baseResult
}
private async validateRussianEmail(email: string): Promise<ComprehensiveValidationResult> {
// Russian-specific validation with Yandex/Mail.ru patterns
const baseResult = await this.multiLayerValidator.validateEmail(email)
// Check for Cyrillic characters
if (/[Ѐ-ӿ]/.test(email)) {
baseResult.recommendations.push('Cyrillic characters detected - ensure proper encoding')
}
return baseResult
}
private async validateInternationalEmail(email: string): Promise<ComprehensiveValidationResult> {
// Standard international validation
return this.multiLayerValidator.validateEmail(email)
}
private updateCacheHitRate(hit: boolean): number {
// Exponential moving average for cache hit rate
const alpha = 0.1
const currentRate = this.metrics.cacheHitRate
if (hit) {
return currentRate * (1 - alpha) + alpha * 100
} else {
return currentRate * (1 - alpha)
}
}
private updateErrorRate(isError: boolean): number {
const alpha = 0.1
const currentRate = this.metrics.errorRate
if (isError) {
return currentRate * (1 - alpha) + alpha * 100
} else {
return currentRate * (1 - alpha)
}
}
private recordResponseTime(responseTime: number): void {
const alpha = 0.1
this.metrics.averageResponseTime =
this.metrics.averageResponseTime * (1 - alpha) + responseTime * alpha
}
private startMetricsCollection(): void {
// Collect metrics every 5 minutes
setInterval(() => {
this.logPerformanceMetrics()
}, 5 * 60 * 1000)
// Clean up old cache entries every hour
setInterval(() => {
this.cleanupCache()
}, 60 * 60 * 1000)
}
private logPerformanceMetrics(): void {
console.log('Validation Performance Metrics:', {
totalValidations: this.metrics.totalValidations,
avgResponseTime: `${this.metrics.averageResponseTime.toFixed(2)}ms`,
cacheHitRate: `${this.metrics.cacheHitRate.toFixed(2)}%`,
errorRate: `${this.metrics.errorRate.toFixed(2)}%`,
cacheSize: this.cache.size,
regionalDistribution: this.metrics.regionalDistribution
})
}
private cleanupCache(): void {
const oneHourAgo = Date.now() - 60 * 60 * 1000
const keysToDelete: string[] = []
for (const [key, entry] of this.cache.entries()) {
if (entry.timestamp < oneHourAgo) {
keysToDelete.push(key)
}
}
keysToDelete.forEach(key => this.cache.delete(key))
console.log(`Cleaned up ${keysToDelete.length} expired cache entries`)
}
// Batch validation for high-throughput scenarios
async validateEmailsBatch(emails: string[]): Promise<Map<string, ComprehensiveValidationResult>> {
const results = new Map<string, ComprehensiveValidationResult>()
// Process in parallel batches
const batchSize = 50
for (let i = 0; i < emails.length; i += batchSize) {
const batch = emails.slice(i, i + batchSize)
const batchPromises = batch.map(async (email) => {
try {
const result = await this.validateEmailOptimized(email)
return { email, result }
} catch (error) {
console.error(`Batch validation failed for ${email}:`, error)
return { email, result: null }
}
})
const batchResults = await Promise.all(batchPromises)
batchResults.forEach(({ email, result }) => {
if (result) {
results.set(email, result)
}
})
// Small delay between batches
if (i + batchSize < emails.length) {
await new Promise(resolve => setTimeout(resolve, 100))
}
}
return results
}
getPerformanceMetrics(): PerformanceMetrics {
return { ...this.metrics }
}
clearCache(): void {
this.cache.clear()
console.log('Validation cache cleared')
}
}
// Initialize optimized validator
const optimizedValidator = new OptimizedInternationalValidator()
// Optimized validation endpoint
app.post('/api/validate-email-optimized', async (req, res) => {
try {
const { email } = req.body
if (!email) {
return res.status(400).json({ error: 'Email required' })
}
const result = await optimizedValidator.validateEmailOptimized(email)
res.json({
validation: result,
timestamp: new Date().toISOString()
})
} catch (error) {
console.error('Optimized validation error:', error)
res.status(500).json({ error: 'Validation failed' })
}
})
// Batch validation endpoint
app.post('/api/validate-emails-batch', async (req, res) => {
try {
const { emails } = req.body
if (!Array.isArray(emails)) {
return res.status(400).json({ error: 'Emails array required' })
}
const results = await optimizedValidator.validateEmailsBatch(emails)
res.json({
results: Object.fromEntries(results.entries()),
count: results.size,
timestamp: new Date().toISOString()
})
} catch (error) {
console.error('Batch validation error:', error)
res.status(500).json({ error: 'Batch validation failed' })
}
})
// Performance metrics endpoint
app.get('/api/validation-metrics', (req, res) => {
const metrics = optimizedValidator.getPerformanceMetrics()
res.json({
metrics,
timestamp: new Date().toISOString()
})
})
// Cache management endpoint
app.post('/api/validation-cache/clear', (req, res) => {
optimizedValidator.clearCache()
res.json({
message: 'Cache cleared successfully',
timestamp: new Date().toISOString()
})
})
console.log('Optimized international email validation system initialized')Implementation Strategies
Building robust international email validation requires comprehensive strategies.
Multi-Provider Validation
- Regional provider specialization
- Fallback validation chains
- Cultural sensitivity in error messages
- Localized user interfaces
Performance Optimization
- Geographic distribution of validation services
- Regional caching strategies
- Latency optimization techniques
- Scalable architecture design
Validate emails globally with our International Email Validation API, featuring comprehensive IDN support and regional provider optimization.