A simple, robust email validation library for Swift, inspired by WordPress's battle-tested validation logic. Designed to be lightweight, fast, and RFC-compliant while providing convenient Swift-native APIs.
- âś… RFC 5321/5322 compliant validation
- âś… WordPress-inspired logic (handles billions of emails)
- âś… Zero dependencies - pure Swift
- âś… Comprehensive provider detection - recognizes major email providers
- âś… Swift-native APIs - feels natural in Swift code
- âś… High performance - optimized for speed
- âś… Extensive test coverage - 20+ test cases covering edge cases
Add EmailValidator to your project using Xcode:
- File → Add Package Dependencies
- Enter: https://github.com/arraypress/swift-email-validator
- Select your desired version
Or add to your Package.swift:
dependencies: [
    .package(url: "https://github.com/arraypress/swift-email-validator", from: "1.0.0")
]import EmailValidator
// Basic validation
"user@example.com".isEmail // true
"invalid-email".isEmail    // false
// Get normalized email (lowercased domain)
"User@EXAMPLE.COM".normalizedEmail // "User@example.com"
// Parse email components
"user@example.com".emailLocalPart // "user"
"user@example.com".emailDomain    // "example.com"
// Provider detection
"user@gmail.com".emailProvider          // "Gmail"
"user@gmail.com".isPersonalEmailProvider // true
"user@company.com".isPersonalEmailProvider // falseValidates if the string is a properly formatted email address.
"test@example.com".isEmail  // true
"invalid".isEmail           // falseReturns a normalized version with lowercased domain, or nil if invalid.
"User@EXAMPLE.COM".normalizedEmail // "User@example.com"
"invalid".normalizedEmail          // nilExtracts the username portion (before @).
"user.name@example.com".emailLocalPart // "user.name"Extracts the domain portion (after @).
"user@sub.example.com".emailDomain // "sub.example.com"Detects known email providers.
"user@gmail.com".emailProvider    // "Gmail"
"user@yahoo.com".emailProvider    // "Yahoo"
"user@company.com".emailProvider  // nilChecks if the email is from a recognized personal email provider.
"user@gmail.com".isPersonalEmailProvider    // true
"user@company.com".isPersonalEmailProvider  // falseFilters array to only valid email addresses.
let emails = ["valid@example.com", "invalid", "another@test.org"]
emails.validEmails // ["valid@example.com", "another@test.org"]Returns normalized versions of all valid emails.
let emails = ["User@EXAMPLE.COM", "invalid", "test@DOMAIN.ORG"]
emails.normalizedEmails // ["User@example.com", "test@domain.org"]Get valid emails if any exist, nil otherwise.
let emails = ["valid@example.com", "invalid", "another@test.org"]
if let validEmails = emails.validEmailsIfAny {
    print("Found \(validEmails.count) valid emails")
} else {
    print("No valid emails found")
}Get normalized emails if any exist, nil otherwise.
let emails = ["User@EXAMPLE.COM", "invalid", "test@DOMAIN.ORG"]
if let normalized = emails.normalizedEmailsIfAny {
    print("Normalized emails: \(normalized)")
    // Result: ["User@example.com", "test@domain.org"]
} else {
    print("No valid emails to normalize")
}Check if the collection contains any valid emails.
let emails = ["valid@example.com", "invalid", "another@test.org"]
if emails.hasValidEmails {
    print("Processing valid emails...")
    processEmails(emails.validEmails)
}Counts valid email addresses in the collection.
["valid@example.com", "invalid", "another@test.org"].validEmailCount // 2EmailValidator recognizes these major providers:
- Gmail (gmail.com, googlemail.com)
- Outlook (outlook.com + regional variants, hotmail.com + regional variants, live.com + regional variants, msn.com)
- Yahoo (yahoo.com, yahoo.co.uk, yahoo.ca, yahoo.de, yahoo.fr, yahoo.com.au, and more)
- iCloud (icloud.com, me.com, mac.com)
- ProtonMail (protonmail.com, proton.me)
- Tutanota (tutanota.com, tutanota.de)
- Hey (hey.com)
- AOL (aol.com + regional variants)
- Yandex (yandex.com, yandex.ru)
- Mail.Ru (mail.ru)
- GMX (gmx.de, gmx.com, gmx.net)
- Web.de (web.de)
- Orange (orange.fr, wanadoo.fr)
- Free (free.fr)
- La Poste (laposte.net)
- NetEase (163.com, 126.com)
- QQ Mail (qq.com)
- Naver (naver.com)
- Daum (daum.net)
- Zoho (zoho.com, zoho.eu)
func validateEmailField(_ email: String) -> String? {
    guard email.isEmail else {
        return "Please enter a valid email address"
    }
    return nil
}func processSignup(email: String) {
    guard let normalizedEmail = email.normalizedEmail else {
        showError("Invalid email address")
        return
    }
    
    if normalizedEmail.isPersonalEmailProvider {
        // Personal email - different onboarding flow
        showPersonalOnboarding()
    } else {
        // Business email - enterprise features
        showBusinessOnboarding()
    }
    
    // Store normalized email
    user.email = normalizedEmail
}func processEmailList(_ emails: [String]) {
    let validEmails = emails.validEmails
    let normalizedEmails = emails.normalizedEmails
    
    print("Found \(emails.validEmailCount) valid emails out of \(emails.count)")
    
    // Process each email
    for email in validEmails {
        if let provider = email.emailProvider {
            print("Email from \(provider): \(email)")
        }
    }
}func analyzeEmailSignups(_ emails: [String]) {
    let validEmails = emails.validEmails
    let personalCount = validEmails.filter(\.isPersonalEmailProvider).count
    let businessCount = validEmails.count - personalCount
    
    print("Personal emails: \(personalCount)")
    print("Business emails: \(businessCount)")
    
    // Group by provider
    let grouped = Dictionary(grouping: validEmails) { $0.emailProvider ?? "Other" }
    for (provider, emails) in grouped {
        print("\(provider): \(emails.count) emails")
    }
}func processEmails(_ emails: [String]) {
    // Use optional variants for cleaner code
    if let validEmails = emails.validEmailsIfAny {
        print("Processing \(validEmails.count) valid emails")
        
        if let normalized = emails.normalizedEmailsIfAny {
            // Work with normalized emails
            sendBulkEmail(to: normalized)
        }
    } else {
        print("No valid emails to process")
    }
    
    // Or use boolean check
    if emails.hasValidEmails {
        print("Found valid emails, proceeding...")
    }
}EmailValidator is optimized for performance:
- Individual validation: ~0.007ms per email
- Bulk operations: ~0.001ms per email in arrays
- Zero allocations for failed validations
- Lazy evaluation in collection operations
EmailValidator follows RFC 5321/5322 standards with these key rules:
- Must contain exactly one @ symbol
- Local part (before @) max 64 characters
- Domain part (after @) max 253 characters
- Total email max 254 characters
- Minimum 6 characters total
- No leading or trailing dots
- No consecutive dots
- ASCII characters only
- Allows: letters, numbers, and !#$%&'*+-/=?^_{|}~.`
- At least two parts separated by dots
- Each part max 63 characters
- No leading/trailing hyphens in domain parts
- Top-level domain must be at least 2 letters
- ASCII letters, numbers, and hyphens only
- iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ / watchOS 6.0+
- Swift 5.5+
- Xcode 13.0+
We welcome contributions! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
EmailValidator is available under the MIT license. See LICENSE for details.
Validation logic inspired by WordPress's is_email() function, adapted for Swift with modern APIs and comprehensive provider detection.