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 // false
Validates if the string is a properly formatted email address.
"test@example.com".isEmail // true
"invalid".isEmail // false
Returns a normalized version with lowercased domain, or nil if invalid.
"User@EXAMPLE.COM".normalizedEmail // "User@example.com"
"invalid".normalizedEmail // nil
Extracts 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 // nil
Checks if the email is from a recognized personal email provider.
"user@gmail.com".isPersonalEmailProvider // true
"user@company.com".isPersonalEmailProvider // false
Filters 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 // 2
EmailValidator 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.