Email Address Validation

Email addresses have a local-part and a domain separated by an @ symbol. The local-part must be either a dot-atom or a quoted string, and the domain must be either a domain name or a domain literal.

A dot-atom may only contain letters, numbers, dots, and the following characters: ! # $ % & ‘ * + – / = ? ^ _ ` { | } ~. However, neither the first nor the last character can be a dot, and two or more consecutive dots are not allowed. The maximum length of a dot-atom is 64 characters. A regular expression to match for a dot-atom local-part is as follows:

// Dot-atom

"/^(?!.{65,})([!#-'*+\/-9=?^-~-]+)(?>\.(?1))*$/iD"

A quoted string may only contain printable US-ASCII characters or the space character, all contained within double quotes. Double quotes and backslashes are allowed only if part of a quoted-pair (escaped with a backslash). A quoted string may be empty. The maximum length of a quoted string is 64 characters, not including the enclosing double-quotes or the escaping backslash of a quoted-pair. A regular expression to match for a quoted string local-part is as follows:

// Quoted string

'/^"(?>[ !#-\[\]-~]|\\\[ -~]){0,64}"$/iD'

A domain name consists of 1 to 127 labels (not including the (empty) root domain), separated by dots, each containing any combination of letters, numbers, or hyphens. However, neither the first nor the last character can be a hyphen. The maximum length of a domain name and label is 253 and 63 characters respectively. A regular expression to match for a domain name is as follows:

// Domain name

'/^(?!.{254,})(?!.*[^.]{64,})([a-z\d](?>[a-z\d-]*[a-z\d])?)(?>\.(?1)){0,126}$/iD'

A domain literal is one of an IPv4 address, an IPv6 address, or an IPv4-mapped IPv6 address.

An IPv4 address consists of four groups, separated by dots, each containing a decimal value between 0 and 255. A regular expression to match for an IPv4 address is as follows:

// IPv4 Address

'/^(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(?>\.(?1)){3}$/D'

An IPv6 address consists of eight groups, separated by colons, each containing a hexadecimal value between 0 and FFFF. One or more consecutive groups of 0 value can be represented as a double colon; however, this may only occur once. A regular expression to match for an IPv6 address is as follows:

// IPv6 Address

'/^(?>([a-f\d]{1,4})(?>:(?1)){7}|(?!(?:.*[a-f\d](?>:|$)){8,})((?1)(?>:(?1)){0,6})?::(?2)?)$/iD'

An IPv4-mapped IPv6 address is an IPv6 address with the final two groups represented as an IPv4 address. A regular expression to match for an IPv4-mapped IPv6 address is as follows:

// IPv4-mapped IPv6 Address

'/^(?>([a-f\d]{1,4})(?>:(?1)){5}:|(?!(?:.*[a-f\d]:){6,})(?2)?::(?>((?1)(?>:(?1)){0,4}):)?(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(?>\.(?3)){3})$/iD'

When used as a domain literal in an email address, the IP address must be contained within square brackets, and IPv6 or IPv4-mapped IPv6 addresses must be preceded by (unquoted) “IPv6:”. A regular expression to match for a domain literal is as follows:

// Domain literal

'/^\[(?:(?>IPv6:(?>([a-f\d]{1,4})(?>:(?1)){7}|(?!(?:.*[a-f\d][:\]]){8,})((?1)(?>:(?1)){0,6})?::(?2)?))|(?>(?>IPv6:(?>(?1)(?>:(?1)){5}:|(?!(?:.*[a-f\d]:){6,})(?3)?::(?>((?1)(?>:(?1)){0,4}):)?))?(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(?>\.(?4)){3}))\]$/iD'

By bringing these regular expressions together, separating the local-part from the domain with an @ symbol, and limiting the entire length to 254 characters, we are left with the following which matches for every valid RFC 5321 email address:

// Email address

'/^(?!(?>"?(?>\\\[ -~]|[^"])"?){255,})(?!"?(?>\\\[ -~]|[^"]){65,}"?@)(?>([!#-\'*+\/-9=?^-~-]+)(?>\.(?1))*|"(?>[ !#-\[\]-~]|\\\[ -~])*")@(?!.*[^.]{64,})(?>([a-z\d](?>[a-z\d-]*[a-z\d])?)(?>\.(?2)){0,126}|\[(?:(?>IPv6:(?>([a-f\d]{1,4})(?>:(?3)){7}|(?!(?:.*[a-f\d][:\]]){8,})((?3)(?>:(?3)){0,6})?::(?4)?))|(?>(?>IPv6:(?>(?3)(?>:(?3)){5}:|(?!(?:.*[a-f\d]:){6,})(?5)?::(?>((?3)(?>:(?3)){0,4}):)?))?(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(?>\.(?6)){3}))\])$/iD'

From this we can create a function to validate an email address using RFC 5321:

  /**
   * Validate an email address using RFC 5321
   *
   * @param string $email_address
   * @return integer
   */
  function is_valid_email_address_5321($email_address)
  {
    return preg_match('/^(?!(?>"?(?>\\\[ -~]|[^"])"?){255,})(?!"?(?>\\\[ -~]|[^"]){65,}"?@)(?>([!#-\'*+\/-9=?^-~-]+)(?>\.(?1))*|"(?>[ !#-\[\]-~]|\\\[ -~])*")@(?!.*[^.]{64,})(?>([a-z\d](?>[a-z\d-]*[a-z\d])?)(?>\.(?2)){0,126}|\[(?:(?>IPv6:(?>([a-f\d]{1,4})(?>:(?3)){7}|(?!(?:.*[a-f\d][:\]]){8,})((?3)(?>:(?3)){0,6})?::(?4)?))|(?>(?>IPv6:(?>(?3)(?>:(?3)){5}:|(?!(?:.*[a-f\d]:){6,})(?5)?::(?>((?3)(?>:(?3)){0,4}):)?))?(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(?>\.(?6)){3}))\])$/iD', $email_address);
  }

An obsolete version of the local-part — a mixture of atoms and quoted strings, separated by dots — is also allowed. An obsolete quoted string allows any US-ASCII character when part of a quoted-pair, and any US-ASCII character except the null, horizontal tab, new line, carriage return, backslash, and double quote characters when not. An obsolete local-part may only be empty if it is a single quoted string. The maximum length of an obsolete local-part, not including the double quotes enclosing a quoted string or the escaping backslash of a quoted-pair, is 64 characters. A regular expression to match for an obsolete local-part is as follows:

// Obsolete local-part

'/^(?!"?(?>\\\[ -~]|[^"]){65,}"?@)([!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*")(?>\.(?1))*$/iD'

Comments and folding white spaces are also allowed in an email address; before and/or after the local-part, before and/or after the domain, and before and/or after any dot in a local-part and/or domain. Folding white space may also appear in a quoted string and/or in comments, and comments may nest. A comment is almost identical to a quoted string except that it is opened and closed with a left and right parentheses respectively and that parentheses are only allowed as part of a quoted-pair (or as further comments), whereas double quotes may appear freely. Folding white spaces are occurrences of the space and/or horizontal tab character preceded by, optionally, zero or more spaces and/or horizontal tabs followed by a carriage return and line feed pair. An obsolete form of folding white space is also allowed which is a carriage return and line feed pair followed by a space or horizontal tab character. Folding white spaces, where allowed, are optional and may occur repeatedly. A regular expression to match for comments and folding white spaces is as follows:

// Comments and folding white spaces

'/^((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)$/iD'

We can now include these where appropriate in the earlier function to give us the following which matches for RFC 5322 email addresses:

  /**
   * Validate an email address using RFC 5322
   *
   * @param string $email_address
   * @return integer
   */
  function is_valid_email_address_5322($email_address)
  {
    return preg_match('/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z\d-]{64,})(?1)(?>([a-z\d](?>[a-z\d-]*[a-z\d])?)(?>(?1)\.(?!(?1)[a-z\d-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f\d]{1,4})(?>:(?6)){7}|(?!(?:.*[a-f\d][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:|(?!(?:.*[a-f\d]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(?>\.(?9)){3}))\])(?1)$/isD', $email_address);
  }

For a class which allows greater control over which type(s) of email address to validate, see below:

<?php

  /**
   * Squiloople Framework
   *
   * LICENSE: Feel free to use and redistribute this code.
   *
   * @author Michael Rushton <michael@squiloople.com>
   * @link http://squiloople.com/
   * @package Squiloople
   * @version 1.0
   * @copyright © 2012 Michael Rushton
   */

  /**
   * Email Address Validator
   *
   * Validate email addresses according to the relevant standards
   */
  final class EmailAddressValidator
  {

    // The RFC 5321 constant
    const RFC_5321 = 5321;

    // The RFC 5322 constant
    const RFC_5322 = 5322;

    /**
     * The email address
     *
     * @access private
     * @var string $_email_address
     */
    private $_email_address;

    /**
     * A quoted string local part is either allowed (true) or not (false)
     *
     * @access private
     * @var boolean $_quoted_string
     */
    private $_quoted_string = FALSE;

    /**
     * An obsolete local part is either allowed (true) or not (false)
     *
     * @access private
     * @var boolean $_obsolete
     */
    private $_obsolete = FALSE;

    /**
     * A basic domain name is either required (true) or not (false)
     *
     * @access private
     * @var boolean $_basic_domain_name
     */
    private $_basic_domain_name = TRUE;

    /**
     * A domain literal domain is either allowed (true) or not (false)
     *
     * @access private
     * @var boolean $_domain_literal
     */
    private $_domain_literal = FALSE;

   /**
     * Comments and folding white spaces are either allowed (true) or not (false)
     *
     * @access private
     * @var boolean $_cfws
     */
    private $_cfws = FALSE;

    /**
     * Set the email address and turn on the relevant standard if required
     *
     * @access public
     * @param string $email_address
     * @param null|integer $standard
     */
    public function __construct($email_address, $standard = NULL)
    {

      // Set the email address
      $this->_email_address = $email_address;

      // Set the relevant standard or throw an exception if an unknown is requested
      switch ($standard)
      {

        // Do nothing if no standard requested
        case NULL:
          break;

        // Otherwise if RFC 5321 requested
        case self::RFC_5321:
          $this->setStandard5321();
          break;

        // Otherwise if RFC 5322 requested
        case self::RFC_5322:
          $this->setStandard5322();
          break;

        // Otherwise throw an exception
        default:
          throw new Exception('Unknown RFC standard for email address validation.');

      }

    }

    /**
     * Call the constructor fluently
     *
     * @access public
     * @static
     * @param string $email_address
     * @param null|integer $standard
     * @return EmailAddressValidator
     */
    public static function setEmailAddress($email_address, $standard = NULL)
    {
      return new self($email_address, $standard);
    }

    /**
     * Validate the email address using a basic standard
     *
     * @access public
     * @return EmailAddressValidator
     */
    public function setStandardBasic()
    {

      // A quoted string local part is not allowed
      $this->_quoted_string = FALSE;

      // An obsolete local part is not allowed
      $this->_obsolete = FALSE;

      // A basic domain name is required
      $this->_basic_domain_name = TRUE;

      // A domain literal domain is not allowed
      $this->_domain_literal = FALSE;

      // Comments and folding white spaces are not allowed
      $this->_cfws = FALSE;

      // Return the EmailAddressValidator object
      return $this;

    }

    /**
     * Validate the email address using RFC 5321
     *
     * @access public
     * @return EmailAddressValidator
     */
    public function setStandard5321()
    {

      // A quoted string local part is allowed
      $this->_quoted_string = TRUE;

      // An obsolete local part is not allowed
      $this->_obsolete = FALSE;

      // Only a basic domain name is not required
      $this->_basic_domain_name = FALSE;

      // A domain literal domain is allowed
      $this->_domain_literal = TRUE;

      // Comments and folding white spaces are not allowed
      $this->_cfws = FALSE;

      // Return the EmailAddressValidator object
      return $this;

    }

    /**
     * Validate the email address using RFC 5322
     *
     * @access public
     * @return EmailAddressValidator
     */
    public function setStandard5322()
    {

      // A quoted string local part is disallowed
      $this->_quoted_string = FALSE;

      // An obsolete local part is allowed
      $this->_obsolete = TRUE;

      // Only a basic domain name is not required
      $this->_basic_domain_name = FALSE;

      // A domain literal domain is allowed
      $this->_domain_literal = TRUE;

      // Comments and folding white spaces are allowed
      $this->_cfws = TRUE;

      // Return the EmailAddressValidator object
      return $this;

    }

    /**
     * Either allow (true) or do not allow (false) a quoted string local part
     *
     * @access public
     * @param boolean $allow
     * @return EmailAddressValidator
     */
    public function setQuotedString($allow = TRUE)
    {

      // Either allow (true) or do not allow (false) a quoted string local part
      $this->_quoted_string = $allow;

      // Return the EmailAddressValidator object
      return $this;

    }

    /**
     * Either allow (true) or do not allow (false) an obsolete local part
     *
     * @access public
     * @param boolean $allow
     * @return EmailAddressValidator
     */
    public function setObsolete($allow = TRUE)
    {

      // Either allow (true) or do not allow (false) an obsolete local part
      $this->_obsolete = $allow;

      // Return the EmailAddressValidator object
      return $this;

    }

    /**
     * Either require (true) or do not require (false) a basic domain name
     *
     * @access public
     * @param boolean $allow
     * @return EmailAddressValidator
     */
    public function setBasicDomainName($allow = TRUE)
    {

      // Either require (true) or do not require (false) a basic domain name
      $this->_basic_domain_name = $allow;

      // Return the EmailAddressValidator object
      return $this;

    }

    /**
     * Either allow (true) or do not allow (false) a domain literal domain
     *
     * @access public
     * @param boolean $allow
     * @return EmailAddressValidator
     */
    public function setDomainLiteral($allow = TRUE)
    {

      // Either allow (true) or do not allow (false) a domain literal domain
      $this->_domain_literal = $allow;

      // Return the EmailAddressValidator object
      return $this;

    }

    /**
     * Either allow (true) or do not allow (false) comments and folding white spaces
     *
     * @access public
     * @param boolean $allow
     * @return EmailAddressValidator
     */
    public function setCFWS($allow = TRUE)
    {

      // Either allow (true) or do not allow (false) comments and folding white spaces
      $this->_cfws = $allow;

      // Return the EmailAddressValidator object
      return $this;

    }

    /**
     * Return the regular expression for a dot atom local part
     *
     * @access private
     * @return string
     */
    private function _getDotAtom()
    {
      return "([!#-'*+\/-9=?^-~-]+)(?>\.(?1))*";
    }

    /**
     * Return the regular expression for a quoted string local part
     *
     * @access private
     * @return string
     */
    private function _getQuotedString()
    {
      return '"(?>[ !#-\[\]-~]|\\\[ -~])*"';
    }

    /**
     * Return the regular expression for an obsolete local part
     *
     * @access private
     * @return string
     */
    private function _getObsolete()
    {

      return '([!#-\'*+\/-9=?^-~-]+|"(?>'
        . $this->_getFWS()
        . '(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*'
        . $this->_getFWS()
        . '")(?>'
        . $this->_getCFWS()
        . '\.'
        . $this->_getCFWS()
        . '(?1))*';

    }

    /**
     * Return the regular expression for a domain name domain
     *
     * @access private
     * @return string
     */
    private function _getDomainName()
    {

      // Return the basic domain name format if required
      if ($this->_basic_domain_name)
      {

        return '(?>' . $this->_getDomainNameLengthLimit()
          . '[a-z\d](?>[a-z\d-]*[a-z\d])?'
          . $this->_getCFWS()
          . '\.'
          . $this->_getCFWS()
          . '){1,126}[a-z]{2,6}';

      }

      // Otherwise return the full domain name format
      return $this->_getDomainNameLengthLimit()
        . '([a-z\d](?>[a-z\d-]*[a-z\d])?)(?>'
        . $this->_getCFWS()
        . '\.'
        . $this->_getDomainNameLengthLimit()
        . $this->_getCFWS()
        . '(?2)){0,126}';

    }

    /**
     * Return the regular expression for an IPv6 address
     *
     * @access private
     * @return string
     */
    private function _getIPv6()
    {
      return '([a-f\d]{1,4})(?>:(?3)){7}|(?!(?:.*[a-f\d][:\]]){8,})((?3)(?>:(?3)){0,6})?::(?4)?';
    }

    /**
     * Return the regular expression for an IPv4-mapped IPv6 address
     *
     * @access private
     * @return string
     */
    private function _getIPv4MappedIPv6()
    {
      return '(?3)(?>:(?3)){5}:|(?!(?:.*[a-f\d]:){6,})(?5)?::(?>((?3)(?>:(?3)){0,4}):)?';
    }

    /**
     * Return the regular expression for an IPv4 address
     *
     * @access private
     * @return string
     */
    private function _getIPv4()
    {
      return '(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(?>\.(?6)){3}';
    }

    /**
     * Return the regular expression for a domain literal domain
     *
     * @access private
     * @return string
     */
    private function _getDomainLiteral()
    {

      return '\[(?:(?>IPv6:(?>'
        . $this->_getIPv6()
        . '))|(?>(?>IPv6:(?>'
        . $this->_getIPv4MappedIPv6()
        . '))?'
        . $this->_getIPv4()
        . '))\]';

    }

    /**
     * Return either the regular expression for folding white spaces or its backreference
     *
     * @access private
     * @param boolean $define
     * @return string
     */
    private function _getFWS($define = FALSE)
    {

      // Return the backreference if $define is set to FALSE otherwise return the regular expression
      if ($this->_cfws)
      {
        return !$define ? '(?P>fws)' : '(?<fws>(?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)';
      }

    }

    /**
     * Return the regular expression for comments
     *
     * @access private
     * @return string
     */
    private function _getComments()
    {

      return '(?<comment>\((?>'
        . $this->_getFWS()
        . '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?P>comment)))*'
        . $this->_getFWS()
        . '\))';

    }

    /**
     * Return either the regular expression for comments and folding white spaces or its backreference
     *
     * @access private
     * @param boolean $define
     * @return string
     */
    private function _getCFWS($define = FALSE)
    {

      // Return the backreference if $define is set to FALSE
      if ($this->_cfws && !$define)
      {
        return '(?P>cfws)';
      }

      // Otherwise return the regular expression
      if ($this->_cfws)
      {

        return '(?<cfws>(?>(?>(?>'
          . $this->_getFWS(TRUE)
          . $this->_getComments()
          . ')+'
          . $this->_getFWS()
          . ')|'
          . $this->_getFWS()
          . ')?)';

      }

    }

    /**
     * Establish and return the valid format for the local part
     *
     * @access private
     * @return string
     */
    private function _getLocalPart()
    {

      // The local part may be obsolete if allowed
      if ($this->_obsolete)
      {
        return $this->_getObsolete();
      }

      // Otherwise the local part must be either a dot atom or a quoted string if the latter is allowed
      if ($this->_quoted_string)
      {
        return '(?>' . $this->_getDotAtom() . '|' . $this->_getQuotedString() . ')';
      }

      // Otherwise the local part must be a dot atom
      return $this->_getDotAtom();

    }

    /**
     * Establish and return the valid format for the domain
     *
     * @access private
     * @return string
     */
    private function _getDomain()
    {

      // The domain must be either a domain name or a domain literal if the latter is allowed
      if ($this->_domain_literal)
      {
        return '(?>' . $this->_getDomainName() . '|' . $this->_getDomainLiteral() . ')';
      }

      // Otherwise the domain must be a domain name
      return $this->_getDomainName();

    }

    /**
     * Return the email address length limit
     *
     * @access private
     * @return string
     */
    private function _getEmailAddressLengthLimit()
    {
      return '(?!(?>' . $this->_getCFWS() . '"?(?>\\\[ -~]|[^"])"?' . $this->_getCFWS() . '){255,})';
    }

    /**
     * Return the local part length limit
     *
     * @access private
     * @return string
     */
    private function _getLocalPartLengthLimit()
    {
      return '(?!(?>' . $this->_getCFWS() . '"?(?>\\\[ -~]|[^"])"?' . $this->_getCFWS() . '){65,}@)';
    }

    /**
     * Establish and return the domain name length limit
     *
     * @access private
     * @return string
     */
    private function _getDomainNameLengthLimit()
    {
      return '(?!' . $this->_getCFWS() . '[a-z\d-]{64,})';
    }

    /**
     * Check to see if the domain can be resolved to MX RRs
     *
     * @access private
     * @param array $domain
     * @return integer|boolean
     */
    private function _verifyDomain($domain)
    {

      // Return 0 if the domain cannot be resolved to MX RRs
      if (!checkdnsrr(end($domain), 'MX'))
      {
        return 0;
      }

      // Otherwise return true
      return TRUE;

    }

    /**
     * Perform the validation check on the email address's syntax and, if required, call _verifyDomain()
     *
     * @access public
     * @param boolean $verify
     * @return boolean|integer
     */
    public function isValid($verify = FALSE)
    {

      // Return false if the email address has an incorrect syntax
      if (!preg_match(

          '/^'
        . $this->_getEmailAddressLengthLimit()
        . $this->_getLocalPartLengthLimit()
        . $this->_getCFWS()
        . $this->_getLocalPart()
        . $this->_getCFWS()
        . '@'
        . $this->_getCFWS()
        . $this->_getDomain()
        . $this->_getCFWS(TRUE)
        . '$/isD'
        , $this->_email_address

      ))
      {
        return FALSE;
      }

      // Otherwise check to see if the domain can be resolved to MX RRs if required
      if ($verify)
      {
        return $this->_verifyDomain(explode('@', $this->_email_address));
      }

      // Otherwise return 1
      return 1;

    }

  }

Download ZIP

On creating the object, using either EmailAddressValidator::setEmailAddress($email_address) or new EmailAddressValidator($email_address), the default settings allow dot-atom@domain-name email addresses where the domain name must have at least two labels and the top-level domain must be between two and six alphabetic characters inclusive in length. If EmailAddressValidator::RFC_5321 is passed as the second (optional) parameter then a quoted string local-part and a domain literal domain are allowed, as well as the more liberal domain name format. If EmailAddressValidator::RFC_5322 is passed as the second (optional) parameter then an obsolete local-part, a domain literal domain, and comments and folding white spaces are allowed, as well as the more liberal domain name format. To add a format, call its associated method with either no parameter or a true parameter. To remove a format, call its associated method with a false parameter. The setStandardBasic(), setStandard5321(), and setStandard5322() methods do not accept any parameters. To return the validation check (either 1 for valid or false for invalid), use the isValid() method. The following is a list of available settings:

// A dot-atom local-part and a domain name domain are allowed
setStandardBasic()

// A dot-atom or quoted string local-part and a domain name or domain literal domain are allowed
setStandard5321()

// An obsolete local-part, a domain name or domain literal domain, and comments and folding white spaces are allowed
setStandard5322()

// A quoted string local-part is allowed
setQuotedString()

// An obsolete local-part is allowed
setObsolete()

// A basic domain name is required
setBasicDomainName()

// A domain literal domain is allowed
setDomainLiteral()

// Comments and folding white spaces are allowed
setCFWS()

If you pass a true parameter to the isValid() method then the _verifyDomain() method will be called to check to see if the domain can be resolved to MX RRs, but only if the email address is syntactically valid. If the verification is successful then the object will return true; if the verification is unsuccessful then the object will return 0.

Alternatively, for those who’d like just a simple regular expression which allows the majority of in-use email addresses, use the following:

  /**
   * Validate an email address using a basic standard
   *
   * @param string $email_address
   * @return integer
   */
  function is_valid_email_address($email_address)
  {
    return preg_match("/^(?!.{255,})(?!.{65,}@)([!#-'*+\/-9=?^-~-]+)(?>\.(?1))*@(?!.*[^.]{64,})(?>[a-z\d](?>[a-z\d-]*[a-z\d])?\.){1,126}[a-z]{2,6}$/iD", $email_address);
  }

For the official documentation on email addresses, please see RFC 5321 and RFC 5322.

43 thoughts on “Email Address Validation”

  1. I found a code snippet you posted on linuxjournal.com for email validation.

    return preg_match(“/^(?=.{5,254})(?:(?:\”[^\”]{1,62}\”)|(?:(?!\.)(?!.*\.[.@])[a-z0-9!#$%&’*+\/=?^_`{|}~^.-]{1,64}))@(?:(?:\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\])|(?:(?!-)(?!.*-\$)(?!.*-\.)(?!.*\.-)(?!.*[^n]–)(?!.*[^x]n–)(?!n–)(?!.*[^.]xn--)(?:[a-z0-9-]{1,63}\.){1,127}(?:[a-z0-9-]{1,63})))$/i”, $email);

    Would this be equivalent to this class you have on this site?

  2. No, the new regular expression is:

    /**
     * Validate an email address using RFC 5322
     *
     * @param string $email_address
     * @return integer
     */
    function is_valid_email_address_5322($email_address)
    {
      return preg_match('/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD', $email_address);
    }
  3. Rasmus says: “This seems to miss the trivial case of a@foo which according to the RFCs and Dominic Sayers test cases should be invalid”.

    This is not the case. RFC 5321 says that “a domain name (or often just a “domain”) consists of one or more components, separated by dots if more than one appears. In the case of a top-level domain used by itself in an email address, a single string is used without any dots.”

    And a real-life example:

    checkdnsrr('ai', 'MX') // Returns true
    getmxrr('ai', $array) // Returns true
  4. Yes, but read further in that RFC you quoted. Section 2.3.5 says:

    Only resolvable, fully-qualified domain names (FQDNs) are permitted
    when domain names are used in SMTP. In other words, names that can
    be resolved to MX RRs or address (i.e., A or AAAA) RRs (as discussed
    in Section 5) are permitted, as are CNAME RRs whose targets can be
    resolved, in turn, to MX or address RRs. Local nicknames or
    unqualified names MUST NOT be used.

    Both Cal Henderson’s and Dominic Sayers’ validators label addresses of this form invalid.

  5. So, here is my slightly modified version that enforces public Internet addresses:

    '/^(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){255,})(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){65,}@)(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\
    x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22))(?:\.(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1
    F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22)))*@(?:(?:(?!.*[^.]{64,})(?:(?:(?:xn--)?[a-z0-9]+(?:-[a-z0-9]+)*\.){1,126}){1,}(?:(?:[a-z][a-z0-9]*)|(?:(?:xn--)[a-z0-9]+))(?:-[a-z0-9]+)*)|(?:\[(?:(?:IPv6:
    (?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9][:\]]){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}
    ){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?)))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:\.(?:(?:25[0-5])|(?:2
    [0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))\]))$/i'
  6. I guess the key here is the SMTP part. Are you checking for valid email addresses routeable on the public Internet or not. If you are, then this test has to fail as you can’t deliver to a TLD and if it isn’t a TLD then it must be a local nickname which is specifically disallowed by RFC 5321.

  7. Some single-label domains can be resolved to MX RRs:

    checkdnsrr('ua', 'MX') // Returns true
    getmxrr('ua', $array) // Returns true

    And some examples of single-label domain email addresses:

    vince@ai
    paul@io
    root@km
    joost@tk
    admin@tt
    hostmaster@ua

    Source: Tony Finch – TLDs with MXs

  8. Right, I should have said cannot reliably be delivered to because of potential local hostname conflicts. There are actually quite a few RFC-defying addresses that work. gmail will happily deliver to foo.@example.com as well, for example, but that is also obviously an invalid address according to the RFCs. Of course, the HTML5 guys are allowing bogus addresses like that now. Check out their ABNF at: http://bit.ly/9eOdUm

  9. That doesn’t change the fact that they are syntactically valid (and that they are also in use). My code intends to allow every RFC 5322/5321 valid email address (excluding semantically invisible (unnecessary) content like folding white space and comments) and deny every invalid email address.

    If I am to also allow invalid email addresses (foo.@example.com, as you offer), then the entire purpose of an email address validator is moot.

    However, I accept that the RFCs allow impractical addresses like “My name”.is.”Michael”@[IPv6:FFFF::255.255.255.255], which is why I have also made a class which allows the developer to allow or disallow different types of local-parts and domains. Taking into account your replies, I have now also included a method to turn on and off single-label domain names (setBasicDomainName()).

  10. This code won’t run – there’s no SetStrictAtom function set. When you call the constructor, php throws an error. This is when you set strict = false.

    $x = EmailAddressValidator::SetEmailAddress($contact_email,false);

  11. Hello,

    i dont understand this part.
    “Additionally, the top level domain must not begin with a number.”

    As far as i know 911.com is starting with a number and is a valid (and working) TLD.

  12. Michael, very interesting but by the end I’m slightly confused. Is there some PHP code that I could use to trap, say 99% of false emails. It doesn’t have to be perfect but just easy to implement. I just want to ‘trim’ an email list before sending. Thanks in advance.

  13. If you have an array ($emails) of email addresses, try the following code:

    foreach ($emails as $key => $email)
    {
    
      if (EmailAddressValidator::setEmailAddress($email)->isValid())
      {
        mail($email, $subject, $message, $headers);
      }
    
      else
      {
        unset($emails[$key]);
      }
    
    }
    

    That will send an email to the valid addresses and remove from the array the invalid addresses.

  14. I just ran into this report from ICANN which closed for comments just this week: http://www.icann.org/en/news/public-comment/sac053-dotless-domains-24aug12-en.htm
    It’s all about dotless domains, which covers those pesky ‘a@b’ addresses that PHP’s FILTER_VALIDATE_EMAIL rejects. Short version: they are proposing to outlaw them, saying they should be “contractually prohibited where appropriate, and strongly discouraged in all cases” by banning A, AAAA and MX records at TLD-level. So, while the RFCs may have some debate over it, it looks like the DNS authorities may render the point moot, and thus we should reject them. What do you make of it?

  15. Hi Marcus,

    Thanks for sharing that. It’s good to see that there might finally be a resolution to the situation that everyone can accept. Whether or not this will affect email address validation will depend on whether or not, if adopted, ICANN will do so retroactively — after all, email addresses with TLD-only domain names currently exist and are in use and it seems possible that they will allow for them. If this is indeed the case then validators should continue to accept TLD-only domain names, and if not then they should be changed to reject them.

    I can only wait for their decision before making one of my own (although in the class I have provided above one can prohibit TLD-only domain names by using the setBasicDomainName() method (although this also requires the TLD to be between 2 and 6 characters inclusive in length — and using only letters)).

  16. Your regex has been incorporated into the PHPMailer project, however I had some problems with regexes containing literal tab characters (in the _getFWS function and the is_valid_email_address function posted above); converting them to \t appeared to fix the problem. It could well have been an IDE issue, but it seems easy enough to avoid by making this small change.

  17. Hi Marcus,

    Thanks for the feedback. It probably is an issue with the IDE; I’m guessing you have a setting that automatically replaces tabs with spaces? As this is quite common behaviour I’ve taken your advice and replaced the literal tab characters with \t (I assume when referring to the function you actually meant is_valid_email_address_5322 as it’s the only one that allows tabs in an email address).

    Also, glad to see it’s being put to good use. Hopefully the guys over at php.net will get around to updating the native FILTER_VALIDATE_EMAIL filter as well. They’re currently using an older (and less accurate) version of my regex.

  18. Hi,

    Since updating to your latest regex, I’m seeing a problem with PHP 5.2. I think the version of PCRE used in it has some limit that your pattern exceeds, like this:

    PHP Warning:
    preg_match() [function.preg-match]:
    Compilation failed: reference to non-existent subpattern at offset 10

    Could you suggest a fix for this?

  19. Hi Marcus,

    I don’t have that version of PHP at hand to test on but it seems likely that, as you suggested, it uses an earlier version of PCRE — likely a version that doesn’t allow for recursive pattern-matching. Unfortunately recursive pattern-matching is a necessity to allow for nested comments, so isn’t something that can be fixed.

    However, if you’re OK with rejecting comments and folding white spaces (which is perfectly acceptable as CFWS is semantically invisible and so not part of the email address anyway) you could try the following. I haven’t tested it on PHP 5.2 but it works fine on 5.4, passing all the PHP Mailer tests (except ones that allow CFWS). I’ve also switched subpatterns with literal patterns (just in case there are other issues with the earlier version of subpatterns in 5.2):

    '/^(?!(?>"?(?>\\\[ -~]|[^"])"?){255,})(?!(?>"?(?>\\\[ -~]|[^"])"?){65,}@)(?>[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*")(?>\.(?>[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*"))*@(?>(?![a-z0-9-]{64,})(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>\.(?![a-z0-9-]{64,})(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)){0,126}|\[(?:(?>IPv6:(?>(?>[a-f0-9]{1,4})(?>:[a-f0-9]{1,4}){7}|(?!(?:.*[a-f0-9][:\]]){8,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?::(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?))|(?>(?>IPv6:(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){5}:|(?!(?:.*[a-f0-9]:){6,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4})?::(?>(?:[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4}):)?))?(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?>\.(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}))\])$/isD'
  20. Thank you! I was going crazy, but now my phpmailer it works! a mile of thanks to you! I’m an italian programmer and here we say “Grazie mille!”

  21. Hi, Xano. This should work:

    /^(?!("?(\\[ -~]|[^"])"?){255,})(?!("?(\\[ -~]|[^"])"?){65,}@)([!#-'*+\/-9=?^-~-]+|"(([\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\[\x00-\xFF]))*")(\.([!#-'*+\/-9=?^-~-]+|"(([\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\[\x00-\xFF]))*"))*@((?![a-z0-9-]{64,})([a-z0-9]([a-z0-9-]*[a-z0-9])?)(\.(?![a-z0-9-]{64,})([a-z0-9]([a-z0-9-]*[a-z0-9])?)){0,126}|\[((IPv6:(([a-f0-9]{1,4})(:[a-f0-9]{1,4}){7}|(?!(.*[a-f0-9][:\]]){8,})([a-f0-9]{1,4}(:[a-f0-9]{1,4}){0,6})?::([a-f0-9]{1,4}(:[a-f0-9]{1,4}){0,6})?))|((IPv6:([a-f0-9]{1,4}(:[a-f0-9]{1,4}){5}:|(?!(.*[a-f0-9]:){6,})([a-f0-9]{1,4}(:[a-f0-9]{1,4}){0,4})?::(([a-f0-9]{1,4}(:[a-f0-9]{1,4}){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}))\])$/i

    Note that it doesn’t allow for comments or folding white spaces as JavaScript’s regular expressions don’t include the feature which allows for recursive pattern matching.

  22. Hi again,

    I’ve run into a problem with this regex on some addresses that are valid in RFC822, but not in others that are applicable. Specifically addresses like these:
    ‘joe_user @example.com’ and ‘joe_user@example .com’ (note the spaces). Both of these addresses pass your regex, but will not work with most MTAs. RFC5322 says that local parts can only contain dot-atom and quoted-string, neither of which allow bare spaces, so I’m not quite sure how these are being allowed. Both RFCs 1034 and 1123 say that domains can’t contain spaces. While not normative, RFC3696 reiterates that spaces can occur in local parts only if quoted or escaped.
    What do you think?

    Marcus

  23. I got to the bottom of this, just some version confusion….
    The PHP 5.2 version you posted on Jan 24th 2013 doesn’t allow spaces; The ‘revised’ one from your March 2010 comment does; The new shorter one (that needs a later PCRE) in your actual article doesn’t. All clear now!

  24. Hi Marcus,

    Yes, the Jan 24th 2013 variation doesn’t allow comments and folding white space as recursive pattern matching is not available in older versions of PCRE.

    As for it being valid if RFC 5322, these are the relevant sections:

    dot-atom        =   [CFWS] dot-atom-text [CFWS]
    
    quoted-string   =   [CFWS]
                           DQUOTE *([FWS] qcontent) [FWS] DQUOTE
                           [CFWS]
    
    local-part      =   dot-atom / quoted-string / obs-local-part
    
    domain-literal  =   [CFWS] "[" *([FWS] dtext) [FWS] "]" [CFWS]
    
    domain          =   dot-atom / domain-literal / obs-domain
    

    The [CFWS] being “comments and folding white space”. So spaces are perfectly acceptable before or after local-parts or domains (and before or after any dots in the local-part or domain).

    However, it’s worth noting that joe_user @example.com and joe_user@example .com are the same email address — CFWS is semantically invisible — so in my view email addresses should be validated according to RFC 5321 (the is_valid_email_address_5321 function) rather than RFC 5322.

    The way I look at it is that RFC 5321 defines the actual email address (the location) while RFC 5322 defines how an email address is displayed.

    It’s like if your actual address is 1 Awesometown but your friend can write a letter to 1 Awesometown (it’s really not that awesome). If you want to check the validity of someone’s address, you wouldn’t bother with the latter; it’s the former that matters.

    So, if that’s something you want to take into account, this is the RFC 5321 validation function modified to work in older versions of PHP:

    /**
     * Validate an email address using RFC 5321
     *
     * @param string $email_address
     * @return integer
     */
    function is_valid_email_address_5321($email_address)
    {
      return preg_match('/^(?!(?>"?(?>\\\[ -~]|[^"])"?){255,})(?!"?(?>\\\[ -~]|[^"]){65,}"?@)(?>[!#-\'*+\/-9=?^-~-]+(?>\.[!#-\'*+\/-9=?^-~-]+)*|"(?>[ !#-\[\]-~]|\\\[ -~])*")@(?!.*[^.]{64,})(?>[a-z\d](?>[a-z\d-]*[a-z\d])?(?>\.[a-z\d](?>[a-z\d-]*[a-z\d])?){0,126}|\[(?:(?>IPv6:(?>[a-f\d]{1,4}(?>:[a-f\d]{1,4}){7}|(?!(?:.*[a-f\d][:\]]){8,})(?>[a-f\d]{1,4}(?>:[a-f\d]{1,4}){0,6})?::(?>[a-f\d]{1,4}(?>:[a-f\d]{1,4}){0,6})?))|(?>(?>IPv6:(?>[a-f\d]{1,4}(?>:[a-f\d]{1,4}){5}:|(?!(?:.*[a-f\d]:){6,})(?>[a-f\d]{1,4}(?>:[a-f\d]{1,4}){0,4})?::(?>[a-f\d]{1,4}(?>:[a-f\d]{1,4}){0,4}:)?))?(?>25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(?>\.(?>25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)){3}))\])$/iD', $email_address);
    }

    It passes all your texts except those that allow CFWS and obsolete local-parts (but the latter doesn’t matter as an obsolete local-part can just be written as a quoted-string anyway, e.g. michael.”rushton”@squiloople.com is the same as “michael.rushton”@squiloople.com).

  25. Hi Michael
    I would like to know how can I modify the regular expression in this function:
    function is_valid_email_address_5322($email_address){…}
    in such a way that validates only linked emails(emails that start with the mailto: word). I want to extract and evaluate only linked emails from an html page. I am new to regular expressions and your function is amazing as well as complicated for me.
    Thank you in advance

  26. Hi E01T, that should just be a case of adding mailto: right at the start after '/^':

    /**
     * Validate an email address using RFC 5322
     *
     * @param string $email_address
     * @return integer
     */
    function is_valid_email_address_5322($email_address)
    {
      return preg_match('/^mailto:(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z\d-]{64,})(?1)(?>([a-z\d](?>[a-z\d-]*[a-z\d])?)(?>(?1)\.(?!(?1)[a-z\d-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f\d]{1,4})(?>:(?6)){7}|(?!(?:.*[a-f\d][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:|(?!(?:.*[a-f\d]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(?>\.(?9)){3}))\])(?1)$/isD', $email_address);
    }
    
  27. Thanks for you quick response.
    Actually it doesn’t. First of all remember that I am scanning through html, the start^ and end$ string symbols does not seem to work. When I removed them it was able to grab and validate textual email addresses(no links). The linked email addresses were able to get validated and modified partially e.g. mailto:etsouderos@something.com ,
    part of it will stay intact like “mailto:etsouderor” and the rest of it will get validated and modified(in my case obfuscated).
    So what I did was I removed the ^ and $ symbols and I added the (?:mailto:| after the start ‘/ , closed the parenthesis after the first @symbol which now becomes @)) and now it is able to grab and validate both the textual and the linked addresses. But what I want to do is modify only the linked emails.
    Any suggestions?

  28. Punycode addresses are valid if using the is_valid_email_address_5321() or is_valid_email_address_5322() functions, or if using the EmailAddressValidator class and calling setStandard5321(), setStandard5322(), or setBasicDomainName(FALSE).

    Cyrillic email addresses are not valid as only ASCII characters are acceptable in an email address (hence the adoption of Punycode).

  29. What about local domain parts? `user@localhost` is a legitimate email address according to RFC 2822. Yes, *SMTP* imposes its own limitations on *deliverable* addresses by requiring internet-resolvable _domain_ names instead of the _hostnames_allowed by the Internet Message Format, but this doesn’t change the fact that non-dotted domain parts are valid in addresses even if they are not valid when provided to the SMTP protocol (which might not even be involved in the delivery of a message.)

  30. is_valid_email_address_5321 and is_valid_email_address_5322 both accept non-dotted (single label) domain names, as does EmailAddressValidator with setBasicDomain set to FALSE (or when using RFC 5321 or RFC 5322 as the standard).

    // Returns 1
    EmailAddressValidator::setEmailAddress(
      'user@localhost',
      EmailAddressValidator::RFC_5321
    )->isValid();
  31. Hi Zvonko Ilitsh,

    Thanks for the information there. Before internationalized domain names can be used they need to be converted into ASCII characters using Punycode as the DNS only allows ASCII characters. Internationalized addresses, like the one you’ve provided above, pass the validation in my code (assuming setBasicDomainName is set to false). There are some PHP functions to convert IDNs here and should be used before passing the result to the validation.

Leave a Reply

Your email address will not be published. Required fields are marked *