Skip to main content

Jonas Neubert

… works with bits and atoms

So you think this library can validate email addresses

Published - Feb 02 2026

(Updated: )

Let’s add some validation for this email address form field!

I usually write software for automation, but like every software developer I’m not immune to email address validation.

So you think you can validate email addresses

The FOSDEM 2018 talk “So you think you can validate email addresses” by Stavros has become a canonical reference for expressing the futility of all attempts to validate email addresses. You can watch it on Youtube. In the talk, Stavros shows a series of email addresses and asks the audience to shout “valid” or “invalid” for each one. After 26 examples (that include 32 @ symbols) Stavros recommends a two step process for email address validation:

Step 1: Check if the string contains at least one “@”.

Step 2: Send an email to it. If that works, it was an email address.

Email validation libraries

Not everyone agrees with this recommendation. Email validation libraries exist in great numbers and most languages.

How do email validation libraries compare when faced with the examples from Stavros’ talk?

That’s the question I set out to answer in this blog post.

Validation versus verification

Before we dive in: Terminology.

Validation is the process of deciding for a given string whether it follows the format rules for email addresses.

Verification is the process of deciding for a given valid email address whether it can receive email.

An email address can be valid but unverified, for example if its domain part is an unregistered domain, or if the domain points to a server that isn’t running a server that accepts email.

Valid email addresses can be compared to various other patterns and lists. A Disposable Check compares against a list of domain names and string patterns associated with masked email services. A Personal Email Check compares against a list of domains commonly used by end users for personal use. (Websites use this to coerce people into providing their work or business email address, for example in “sign up to download this whitepaper” flows.)

Many libraries offer some combination of validation, verification, and checks. In this blog post I focus exclusively on validation. For libraries that validate and verify by default, I tried to disable the verification part to the extend possible.

Request For Comment

The closest we have to a standard for the email address string format is a set of RFCs:

  • RFC 5322 defines the email address format. Wouldn’t it be nice if this list ended here?
  • RFC 5321 defines the entire SMTP protocol (for email sending). The protocol defines a slightly stricter grammar for email addresses to which emails can be sent with SMTP.
  • RFCs 6530, 6531, 6532 all have something to say about international email addresses. Where “international” refers to alphabets that go beyond ASCII. And also emojis.
  • The above RFCs generally say that the part after the “@” must be a valid domain name. RFC 10356530 defines those.

One difference between RFC 5322 and RFC 5321 features in two of Stavros’ examples: The former has the concept of parentheses-enclosed comments like (comment)name@domain.com or na(comment)me@domain.com.

Separate from RFCs, the HTML5 standard specifies a regular expression with which browsers should validate inputs for <input type="email" /> form fields.

Some libraries declare that they implement RFC 5321 or RFC 5322 or the HTML5 standard, but most intentionally or unintentionally don’t strictly follow either.

Comparison Table

Summary

LibraryCorrectFalse NegativesFalse Positives
aftership-email-verifier2260
apache-commons-validator2242
checkmail2521
deep-email-validator15310
django-emailvalidator2080
egulias-email-validator2323
email-addresses2512
email-valid-perl2422
email-validator18100
emailvalidation-dotnet2170
hibernate-validator2152
joi1990
libvldmail2323
mailaddress-dotnet2233
mailkit-dotnet2512
net-mail2341
php-filter-var1891
pyisemail2161
python-email-validator2260
rust-email-address2431
symfony-validator16102
truemail16102
valid-email21972
validator-js2350
validator-rust1783
wordpress-is-email16102
zod16120

Expected Valid Emails

  • ✓ = correctly accepted
  • ✗ = false negative (incorrectly rejected)
  1. foo@bar.com - simple test case
  2. hi@stavros.io
  3. hi+there@stavros.io - plus is allowed
  4. stavros.k@stavros.io - dot in local part is valid
  5. f*uck@stavros.io - asterisks are allowed in local part
  6. #$%!^/&@stavros.io
  7. (sta)vros@stavros.io - (sta) is a comment
  8. stavros@stavros.io(io) - (io) is a comment
  9. "hi@you"@stavros.io - quoted at symbol in local part is allowed
  10. "hi you"@stavros.io - quoted space in local part is allowed
  11. " "@stavros.io - quoted space in local part is allowed
  12. "<\"@\\"".!#%$@stavros.io - quotes are separated by dots
  13. cow@[dead::beef] - IPv6 domain part
  14. stavros@io - domain part is TLD
  15. 我買@屋企.香港
  16. 1@[23456789] - [23456789] is a decimal IPv4 address
Library12345678910111213141516
aftership-email-verifier
apache-commons-validator
checkmail
deep-email-validator
django-emailvalidator
egulias-email-validator
email-addresses
email-valid-perl
email-validator
emailvalidation-dotnet
hibernate-validator
joi
libvldmail
mailaddress-dotnet
mailkit-dotnet
net-mail
php-filter-var
pyisemail
python-email-validator
rust-email-address
symfony-validator
truemail
valid-email2
validator-js
validator-rust
wordpress-is-email
zod

Expected Invalid Emails

  • ✓ = correctly rejected
  • ✗ = false positive (incorrectly accepted)
  1. f o o@bar.com - contains spaces
  2. hi@ - missing domain part
  3. stavros.@stavros.io - dot at end of local part is invalid
  4. stavros..k@stavros.io - two dots in sequence in local part
  5. !#$%&'*(-/=?@stavros.io
  6. h(a)i@stavros.io - parentheses in name part
  7. em@il@stavros.io - two at symbols
  8. "<\"@\"".!#%$@stavros.io - illegal characters
  9. <\"@\\".!#%$@stavros.io - illegal characters
  10. hi"@"you@stavros.io - the quotes must be dot-separated
  11. hi\ there@stavros.io - spaces must be quoted even if escaped
  12. 1@23456789 - 23456789 is not a valid TLD
Library123456789101112
aftership-email-verifier
apache-commons-validator
checkmail
deep-email-validator
django-emailvalidator
egulias-email-validator
email-addresses
email-valid-perl
email-validator
emailvalidation-dotnet
hibernate-validator
joi
libvldmail
mailaddress-dotnet
mailkit-dotnet
net-mail
php-filter-var
pyisemail
python-email-validator
rust-email-address
symfony-validator
truemail
valid-email2
validator-js
validator-rust
wordpress-is-email
zod

Methodology

You can see the scripts I used for validating Stavros’ collection of strings against each library below in the Appendix. The test harness and complete scripts are in this Github repo. Please open a PR there if you see issues with any of the test scripts or to add additional libraries.

Full disclosure: I used Claude Code to write most of the test scripts. I lack time and skill to write all these scripts myself.

For the test harness, the input and output file formats of the test scripts are standardized and intentionally simple: The input is simply one string to validate per line, the output prefixes each of those input lines with eight characters, either "valid " or "invalid ". This way I avoid unintentional transformations of the string by the test harness. My first version used comma separated values (CSV) files but quickly realized that CSV parsers and strings with quotes and backslash characters don’t mix well.

The Test Cases

I transcribed the email addresses shown in Stavros’ talk, hopefully without typos:

hi@stavros.io
hi@
hi+there@stavros.io
stavros.k@stavros.io
stavros.@stavros.io
stavros..k@stavros.io
!#$%&'*(-/=?@stavros.io
f*uck@stavros.io
#$%!^/&@stavros.io
h(a)i@stavros.io
(sta)vros@stavros.io
stavros@stavros.io(io)
em@il@stavros.io
"<\"@\"".!#%$@stavros.io
<\"@\\".!#%$@stavros.io
"hi@you"@stavros.io
"hi you"@stavros.io
" "@stavros.io
hi"@"you@stavros.io
"<\"@\\"".!#%$@stavros.io
hi\ there@stavros.io
cow@[dead::beef]
stavros@io
我買@屋企.香港
1@23456789
1@[23456789]

Notes:

  • (sta)vros@stavros.io - The characters in parentheses are comments according to RFC 5322 (nobody supports them)
  • "hi you"@stavros.io - Spaces are generally not allowed, but quoted strings allow spaces.
  • cow@[dead::beef] and 1@[23456789] - Instead of a domain you are allowed to use an IP addresses (RFC 5321). These two examples happen to be a valid IPv6 address and a valid IPv4 address written in decimal representation, respectively.
  • stavros@io - TLD-only addresses are technically valid per RFC.
  • 我買@屋企.香港 - Internationalized addresses are coverd in RFC 6531. A side effect of this RFC is that emojis and other Unicode characters are allowed in email addresses.

The Libraries

NameRepositoryPackageLanguage
validator.jsvalidatorjs/validator.jsNPMJavaScript
deep-email-validatormfbx9da4/deep-email-validatorNPMJavaScript
email-validatormanishsaraan/email-validatorNPMJavaScript
Zodcolinhacks/zodNPMJavaScript
Joihapijs/joiNPMJavaScript
email-addressesjackbearheart/email-addressesNPMJavaScript
python-email-validatorJoshData/python-email-validatorPyPIPython
pyIsEmailmichaelherold/pyIsEmailPyPIPython
Django EmailValidatordjango/djangoPyPIPython
PHP filter_var()N/AN/APHP
EmailValidatoregulias/EmailValidatorPackagistPHP
WordPressWordPress/WordPressN/APHP
Symfony Validatorsymfony/validatorPackagistPHP
Apache Commons Validatorapache/commons-validatorJava
Hibernate Validatorhibernate/hibernate-validatorMavenJava
AfterShip/email-verifierAfterShip/email-verifierGo
net/mailgolang/gopkg.go.devGo
checkmailbadoux/checkmailpkg.go.devGo
truemailtruemail-rb/truemailRuby
valid_email2micke/valid_email2Ruby
MailAddressdotnet/runtimeN/A.NET
EmailValidationjstedfast/EmailValidationNuGet.NET
MailKitjstedfast/MailKitNuGet.NET
email_addressjohnstonskj/rust-email_addresscrates.ioRust
validatorKeats/validatorcrates.ioRust
Email::ValidPerl-Email-Project/Email-ValidCPANPerl
libvldmaildertuxmalwieder/libvldmailN/AC

validator.js

A library of string validators and sanitizers for JavaScript with over 23,000 Github stars. Provides an isEmail() function with configurable options for allowing display names, UTF-8 local parts, and more.

import isEmail from 'validator/lib/isEmail'

validator.isEmail('foo@bar.com', { allow_ip_domain: true })

By default, IP addresses in the domain part are rejected. For an accurate comparison with the test cases, the allow_ip_domain option is enabled.

Configuration options:

  • allow_display_name - Allow display names like Name <email@example.com>. Default: false.
  • require_display_name - Require a display name. Default: false.
  • allow_utf8_local_part - Allow UTF-8 characters in the local part. Default: true.
  • allow_ip_domain - Allow IP addresses in the domain part. Default: false.
  • domain_specific_validation - Extra validation for Gmail addresses. Default: false.
  • blacklisted_chars - String of characters not allowed in the local part.

email-validator (Javascript)

The second most downloaded email validation library on NPM, by a wide margin, behind validator.js. Advertised as “a simple module”, and simple it is: The entire library is 33 lines of code in a single file, plus 100 lines of tests. Within those 33 lines is no room for parameters or config options. The last update was pushed in 2018, which some may call “abandoned” and others “finished”.

const validator = require('email-validator')

validator.validate('foo@bar.com') // true

The library correctly rejects all of Stavros’ invalid test cases, as well as 10 out of 16 valid test cases.

email-addresses (Javascript)

Neck to neck with email-validator in terms of downloads, but quite different in terms of LOC: This one has 1098 lines of code plus a couple of hundred lines of test coverage.

The library is implements as a recursive decent parser for the grammar specified in RFC 5322. This means that for any valid email address, this library yields a AST of the components that match definitions in the RFC which you can query individually.

For the test script I used parseOneAddress() which returns null for invalid addresses.

An RFC 5322 email address parser that handles full email address formats including display names and comments. For validating just the address portion (RFC 5321), the documentation recommends using node-address-rfc2821 instead.

const addrs = require('email-addresses')

// Returns address object if valid, null if invalid
const parsed = addrs.parseOneAddress('test@example.com')
parsed.local // => test
parsed.domain // => example.com

// Or use addrs() to get access to the AST
const parsed2 = addrs('test@example.com')
parsed.ast.children // => tree object

There are a few configuration options, all of which I kept the default values for:

  • rfc6532 enables Unicode support. Warning: Depending on which function you use, the default value changes.
  • strict turns off features of RFC 5322 marked “Obsolete”.
  • rejectTLD requires at least one dot in the domain part. This one was tricky: Enabling it would have removed one false positive from the results but added a false negative.

The strict adherence to RFC 5322 means that email-addresses supports display names ("Display Name" <display.name@gmail.com>) which Stavros examples don’t cover. The docs recommend node-address-rfc2821 as a similar parser implementation for just the address part. A future version of this post should include that one, I saw the reference too late to include it this time around.

deep-email-validator (Javascript, Node.js only)

The “deep” in the name refers to the list of features beyond validation: Typos, disposable check, DNS lookup, and SMTP.

import validate from 'deep-email-validator'

const main = async () => {
  await validate({
    email: 'name@example.org',
    sender: 'name@example.org',
    validateRegex: true,
    validateMx: true,
    validateTypo: true,
    validateDisposable: true,
    validateSMTP: true,
  })
}

The validation part is rather simple:

Validates email looks like an email i.e. contains an “@” and a “.” to the right of it.

Ironically this is still more strict than Stavro’s suggested method because the requirement for a “.” rules out top level domains, for example “lewis@ferrari”. Using top-level domains like that has been banned by ICANN since 2013, so maybe not a big deal.

deep-email-validator was last updated in 2021. I include it here as an example for the deep bench of email validation packages on NPM that gained modest traction by Github stars and downloads.

Note that deep-email-validator is Node.js only Javascript, so no good for your web form.

Zod (Javascript)

Zod is a Javascript object validation library that has a field type for email addresses:

import { z } from 'zod'

z.string().email()

Zod intentionally does not support many RFC-compliant but unusual email formats. For example: No comments, no quotes, no IP addresses, no emojis. From their docs:

By default, Zod uses a comparatively strict email regex designed to validate normal email addresses containing common characters. It’s roughly equivalent to the rules enforced by Gmail. To learn more about this regex, refer to this post.

/^(?!\.)(?!.*\.\.)([a-z0-9_'+\-\.]*)[a-z0-9_+-]@([a-z0-9][a-z0-9\-]*\.)+[a-z]{2,}$/i

The library also bundles a handful of alternative email regexes and allows you to bring your own.

Joi (Javascript)

Joi is another Javascript validation library that includes a field type for email addresses:

const Joi = require('joi')

const schema = Joi.string().email()

The documentation mentions one edge case:

Note that quoted email addresses (e.g. “test”@example.com) are not supported and will fail validation.

These config options exist:

  • allowFullyQualified - if true, domains ending with a . character are permitted. Defaults to false.
  • allowUnicode - if true, Unicode characters are permitted. Defaults to true.
  • ignoreLength - if true, ignore invalid email length errors. Defaults to false.
  • minDomainSegments - number of segments required for the domain. The default setting excludes single segment domains such as example@io which is a valid email but very uncommon. Defaults to 2.
  • maxDomainSegments - maximum number of allowed domain segments. Default to no limit.
  • tlds - options for TLD (top level domain) validation. By default, the TLD must be a valid name listed on the IANA registry. To disable validation, set tlds to false.

python-email-validator

This is currently the most popular option for Python with 20 million weekly PyPI downloads. Pydantic uses it for its EmailStr field type.

from email_validator import validate_email, EmailNotValidError

try:
    emailinfo = validate_email(
        "my+address@example.org",
        check_deliverability=False,
        allow_quoted_local=True,
        allow_domain_literal=True,
    )
except EmailNotValidError as e:
    print(str(e))  # Prints a human-readable explanation.

The default settings allow UTF-8 and verifies with a DNS check, but disallow quotes and IP addresses. I enable the default-off flags for a fair comparison against Stavros’ examples.

The Readme declares:

This is an opinionated library. You should definitely also consider using the less-opinionated pyIsEmail if it works better for you.

In the test, python-email-validator rejects all invalid test cases and 6 valid ones, validating the “opinionated” statement.

By the way, while the test script ignores the validation failure messages, you should not. They are quite specific:

  • f o o@bar.com → “The email address contains invalid characters before the @-sign: SPACE”
  • stavros.@stavros.io → “An email address cannot have a period immediately before the @-sign”
  • stavros..k@stavros.io → “An email address cannot have two periods in a row”
  • em@il@stavros.io → “The part after the @-sign contains invalid characters: ‘@’”
  • hi\ there@stavros.io → “The email address contains invalid characters before the @-sign: “", SPACE”

pyIsEmail (Python)

A Python port of the is_email PHP library (which is not covered again in this blog post). Provides detailed diagnostic information when validation fails.

from pyisemail import is_email

address = "test@example.com"
bool_result = is_email(address)
detailed_result = is_email(address, diagnose=True)

Configuration options:

  • check_dns - Validate that the domain has MX records. Default: False.
  • diagnose - Return detailed diagnostic information instead of a boolean. Default: False.

Django EmailValidator (Python)

Django’s built-in email validator is used throughout the framework for model fields and form validation. It’s one of the most widely deployed email validators given Django’s popularity in Python web development.

from django.core.validators import EmailValidator
from django.core.exceptions import ValidationError

validator = EmailValidator()
try:
    validator("test@example.com")
    # Valid
except ValidationError:
    # Invalid

The validator uses a regex-based approach that aims to match the HTML5 email input specification while allowing some RFC 5322 edge cases. It’s designed to be practical for web forms rather than strictly RFC-compliant.

Configuration options:

  • allowlist - Allowlist of domains (e.g., ['example.com']). Only these domains will be accepted.
  • message - Custom error message for validation failures.
  • code - Custom error code for validation failures.

Django requires minimal settings configuration to use the validator standalone (outside of a Django project).

PHP Standard Library

  • Version tested: 8.3.30 (published January 2026)
  • Test code

PHP has an email validator built into the standard library:

filter_var($email, FILTER_VALIDATE_EMAIL)

It’s not easily discovered in the documentation because it is a flag to the more general filter_var function. There are no configuration options available.

The PHP docs advertise RFC 5321 compliance. But the PHP docs also allow for comments to be posted under each page. Naturally there are comments pointing out deviations from the standard, including this highly upvoted one from 2013.

The test results suggest that PHP is overly strict about the local part of the email and quite flexible about the domain part: It accepts the IPv6 address and the naked TLD (both correct per RFC) but also the numbers-only domain (incorrect).

WordPress (PHP)

The is_email() function is WordPress’s built-in email validator. WordPress powers over 40% of websites, this is likely one of the most widely executed email validation functions in existence.

Because Wordpress’ internal functions aren’t normally used as a library, for my test I copied the is_email source code from wp-includes/formatting.php. Then the usage becomes trivial:

$result = is_email($email);  // returns email if valid, false otherwise

The function performs these checks:

  • Minimum length of 6 characters
  • Must contain @ after the first position
  • Local part: only allows a-z0-9!#$%&'*+/=?^_{|}~.-`
  • Domain: must have at least two parts, each containing only a-z0-9-
  • No leading/trailing hyphens or whitespace in domain parts

The docs are refreshingly honest about its limitations:

“Does not grok i18n domains.” […] “Not RFC compliant.”

The test results confirm this: The function rejects all the theoreticaly valid but unusual strings and slips up only on the two invalid cases that contain dots in the wrong places.

No configuration options available.

Symfony Validator (PHP)

Symfony’s Validator component provides an Email constraint with multiple validation modes. In strict mode, it uses egulias/EmailValidator. The default mode is VALIDATION_MODE_HTML5 and uses a regex that you can see in this file.

use Symfony\Component\Validator\Constraints\Email;
use Symfony\Component\Validator\Validation;

$validator = Validation::createValidator();
$constraint = new Email(['mode' => Email::VALIDATION_MODE_HTML5]);
$violations = $validator->validate($email, $constraint);

Configuration options:

  • VALIDATION_MODE_HTML5 - Uses the HTML5 regex pattern (default). Good for web forms.
  • VALIDATION_MODE_STRICT - Uses egulias/EmailValidator for strict RFC compliance. Requires egulias/email-validator to be installed.

EmailValidator (PHP)

The most comprehensive PHP validator with multiple validation strategies. Supports strict RFC 5321/5322 compliance and can warn about technically-valid-but-unusual addresses that you may want to reject in practice.

use Egulias\EmailValidator\EmailValidator;
use Egulias\EmailValidator\Validation\RFCValidation;

$validator = new EmailValidator();
$validator->isValid("example@example.com", new RFCValidation());

Validation strategies:

  • RFCValidation - Validates according to RFC 5321/5322 (used for this comparison).
  • NoRFCWarningsValidation - Fails on RFC warnings (stricter than RFCValidation).
  • DNSCheckValidation - Checks if the domain has valid DNS records.
  • SpoofCheckValidation - Detects spoofing attempts using similar-looking characters.

Apache Commons Validator (Java)

Part of the Apache Commons project. Provides an EmailValidator class that can be configured to allow local addresses and TLDs.

import org.apache.commons.validator.routines.EmailValidator;

EmailValidator validator = EmailValidator.getInstance();
boolean isValid = validator.isValid("test@example.com");

Configuration options:

  • getInstance() - Default instance, no local addresses or TLD-only domains.
  • getInstance(allowLocal) - Allow local addresses (e.g., user@localhost).
  • getInstance(allowLocal, allowTld) - Also allow TLD-only domains (e.g., user@io).

The docs are here.

This code is nearly 20 years old and references a long-gone website https://javascript.internet.com as inspiration, and it’s still getting polished today.

Hibernate Validator (Java)

I am way out of my depth on this, but Claude asked me to tell you:

This is the reference implementation of Jakarta Bean Validation (formerly JSR 380). Provides the @Email annotation for declarative validation. Widely used in Spring Boot and Jakarta EE applications.

To keep the test script minimal, I use Claude uses validateValue() to validate the strings directly without creating full bean instances:

import jakarta.validation.constraints.Email;

public class EmailWrapper {
    @Email
    private String email;
}

// Validate using:
validator.validateValue(EmailWrapper.class, "email", emailString);

No configuration options available for the basic @Email annotation.

Go net/mail (Standard Library)

Go’s standard library net/mail package provides RFC 5322 email address parsing via mail.ParseAddress().

This is a parser rather than a dedicated validator, so it may accept some unusual but technically valid RFC 5322 addresses.

import "net/mail"

_, err := mail.ParseAddress("test@example.com")
if err == nil {
    // Valid
}

No configuration options available.

checkmail (Go)

127 lines of Go that cover format validation, DNS lookup, and verification with SMTP.

For the test I call ValidateFormat() which performs syntax validation using a simple regexp based on the W3C HTML5 email specification:

import "github.com/badoux/checkmail"

err := checkmail.ValidateFormat("test@example.com")
if err == nil {
    // Valid format
}

Available functions:

  • ValidateFormat() - Syntax validation only (used for this comparison).
  • ValidateHost() - Validates format + checks DNS MX records.
  • ValidateHostAndUser() - Validates format + DNS + verifies mailbox via SMTP.

The library intentionally uses a simple validation approach. From its documentation:

Format (simple regexp, see: https://www.w3.org/TR/html5/forms.html#valid-e-mail-address and https://davidcel.is/posts/stop-validating-email-addresses-with-regex/)

email-verifier (Go)

A full-featured Go library that supports syntax validation, MX lookup, SMTP verification, disposable email detection, and typo suggestions. For this comparison, I disabled all verification features and only tested syntax validation via ret.Syntax.Valid.

import emailverifier "github.com/AfterShip/email-verifier"

verifier := emailverifier.NewVerifier().
    DisableSMTPCheck().
    DisableDomainSuggest().
    DisableAutoUpdateDisposable()

ret, _ := verifier.Verify("test@example.com")
isValid := ret != nil && ret.Syntax.Valid

Configuration options:

  • .DisableSMTPCheck() - Disable SMTP verification.
  • .DisableDomainSuggest() - Disable typo suggestions.
  • .DisableAutoUpdateDisposable() - Disable auto-updating disposable email list.

truemail (Ruby)

truemail offers the full menu of checks: Domain allow/deny lists, regex (the part we care about here), DNS check, SMTP check.

By default this validation not performs strictly following RFC 5322 standard, so you can override Truemail default regex pattern if you want.

To run syntax validation only:

require 'truemail'

Truemail.configure do |config|
  config.verifier_email = 'verifier@example.com'
  config.default_validation_type = :regex  # syntax-only, no DNS/SMTP
end

def valid?(email)
  Truemail.valid?(email)
end

To use a custom regex:

Truemail.configure do |config|
  config.verifier_email = 'verifier@example.com'
  config.default_validation_type = :regex  # syntax-only, no DNS/SMTP
  config.email_pattern = /regex_pattern/  # <-- your custom regex here
end

To add allow list or deny list checking:

Truemail.configure do |config|
  config.verifier_email = 'verifier@example.com'
  config.default_validation_type = :regex  # syntax-only, no DNS/SMTP
  config.whitelist_validation=true
  config.whitelisted_domains=['stavros.io']
  config.blacklist_validation=true
  config.blacklisted_domains=['jonasneubert.com']

You can also provide a list of allowed or denied emails in whitelisted_emails and blacklisted_emails. But if you do that, then why use a validation library at all?

truemail-go is a Go port of the truemail’s syntax validation features only. Both Ruby original and Go port received their most recent commit in 2024.

valid_email2 (Ruby)

This is widely used in Rails applications because it integrates with ActiveModel, Rails’ system for object validation. It’s probably the most common choice in the Ruby ecosystem.

require 'valid_email2'

email = ValidEmail2::Address.new("test@example.com")
email.valid?  # true

The validation process is not configurable. Use .valid_mx?, .disposable?, .blacklisted? for the other checks offered by the library.

System.Net.Mail.MailAddress (.NET Standard Library)

.NET’s built-in email address parser. As is typical for .NET, the documentation goes into detail about what is and what isn’t supported. One interesting detail:

.NET 9 and earlier ONLY: Consecutive and trailing dots in user names. For example, user...name..@host. (Starting in .NET 10, consecutive dots aren’t allowed.)

using System.Net.Mail;

try {
    var addr = new MailAddress(email);
    bool isValid = addr.Address == email;
} catch {
    // Invalid
}

Note that MailAddress also accepts “Display Name ” format, so validation should verify that Address matches the original input to ensure that part of your input string wasn’t interpreted as display name.

No configuration options available.

EmailValidation (.NET)

Simple, correct .NET validator with RFC 6531 (internationalized) support. Written by Jeffrey Stedfast, who also authored MailKit.

using EmailValidation;

bool isValid = EmailValidator.Validate("test@example.com");

No configuration options available.

MailKit (.NET)

MailKit is a cross-platform mail client library for .NET, written by Jeffrey Stedfast (who also wrote EmailValidation). While primarily an IMAP/SMTP client, it includes email address parsing via MailboxAddress.TryParse() from the MimeKit dependency.

using MimeKit;

bool isValid = MailboxAddress.TryParse("test@example.com", out var _);

The validation behavior differs slightly from EmailValidation because MailKit’s parser is designed for real-world email handling rather than strict validation.

No configuration options available.

email_address (Rust)

A Rust newtype for email address strings. The docs claim RFC 5322 compliance and support for UTF-8 addresses.

use email_address::EmailAddress;

let is_valid = EmailAddress::is_valid("test@example.com");

No configuration options available.

Among our contenders its performance against the Stavros test suite is among the top 4, in part because it does not allow for comments in the address.

validator (Rust)

Comprehensive validation library for Rust, akin to Zod in Node and Pydantic in Python. Validates email addresses based on the HTML5 spec rather than RFC 5322.

use validator::ValidateEmail;

fn validate(email: &str) -> bool {
    email.validate_email()
}

No configuration options available for email validation.

Email::Valid (Perl)

The closest I’ve ever come to Perl is as a marketing (sic) intern at Nestoria where I was sitting next to some Perl wizards. Lacking first hand knowledge I have no choice but to believe the Google wisdom that this library with 18 Github stars is indeed the premier Perl solution for email validation.

The syntax of the basic usage example looks Perlish indeed:

use Email::Valid;

my $email = 'example@domain.com';
my $valid = Email::Valid->address(-address => $email, -fqdn => 0);
print $valid ? "Valid email" : "Invalid email";

Of the config parameters mentioned in the docs, two are relevant to our test cases:

  • allow_ip defaults to true and allows for IP addresses for the domain part. Sadly, the library still fails the two IP address examples which are an IPv6 and a decimal format IPv4.
  • fqdn defaults to true and requires the domain part to be fully qualified. For a fair comparison with other libraries, I set -fqdn => 0 to allow single-segment domains like stavros@io, which are technically valid per RFC standards.

libvldmail (C)

A C library for email syntax validation only. Follows RFC 6531 by default with fallback to RFC 5321.

#include <vldmail.h>

valid_mail_t validator = validate_email(L"foo@bar.com");
if (validator.success != 0) {
    // Valid
}

Two configuration options are available:

  • NO_UNICODE_MAIL_PLEASE - A very polity flag for restricting validation to ASCII characters only.
  • STRICT_VALIDATION - Apply stricter RFC standards, marking deprecated formats as invalid.

Overall, libvldmail does quite well on Stavros’ test cases. It’s the only library that lets the hi@ example slip through as valid, something I’d consider a bug.

Other Options not Reviewed

  • pydantic because it delegates email validation to python-email-validator.
  • skeggse/isemail because it is no longer maintained. Just like pyIsEmail, it is a port of the is_email PHP library.
  • go-mail because it delegates email validation to Go’s standard library net/mail. Note: The naming history is confusing - go-gomail/gomail was the original (~2016), go-mail/mail was a fork (~2019), and wneessen/go-mail is the currently maintained version (2022+).
  • Mailchecker is a cross-language library that primarily compares an email address against a list of disposable email address providers. It also offers validation and defers this to PHP’s filter_var and to the regex from validator.js for all other languages.
  • mailcheck.js) because it is not a full validator, it finds domain misspellings for a fixed list of domains. And because it’s been unmaintained for 10 years, after a successful run as jQuery plugin.
  • Libraries that send each email address to an API, for example email-verifier.

Further Reading