For the last couple of years we’ve mostly used Devise’s extremely simple email address regex to validate email addresses. After all, the real proof of whether an address is correct is if the confirmation email gets through to the end user…
# Email regex used to validate email formats. It simply asserts that
# one (and only one) @ exists in the given string. This is mainly
# to give user feedback and not to assert the e-mail validity.
config.email_regexp = /\A[^@]+@[^@]+\z/
If you haven’t read it already, you should read David Celis’s Stop Validating Email Addresses With Regex blog post for a more detailed explanation of why you shouldn’t validate email addresses with regexes.
The downside of this regex is that it can and will let through email addresses that raise Mail::Field::ParseError
exceptions once Mail actually tries to parse them.
For example, some non-latin characters (such as Ä, Ö, or Å) cause explosions:
re = /\A[^@]+@[^@]+\z/
email_address = "matti.meikälä[email protected]"
puts "Valid" if email_address =~ re
#=> Valid
Mail::Address.new email_address
# Mail::Field::ParseError: Mail::AddressList can not parse |matti.meikälä[email protected]|
# Reason was: Only able to parse up to matti.meikä
Obviously if Mail can’t parse the email address the email won’t be sent to the user…
My latest approach is to combine the Devise regular expression validation with an additional check that the Mail library can actually parse the user supplied address:
# app/validators/email_validator.rb
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
email = Mail::Address.new(value)
unless email.address =~ Devise.email_regexp
record.errors.add(attribute, :invalid_email)
end
rescue Mail::Field::ParseError => e
record.errors.add(attribute, :invalid_email)
end
end
If you’re wondering why we need to check that the email address both passes the regex and Mail::Address.new(…)
, it’s because Mail will happily accept a string such as foo
as a valid address:
email = Mail::Address.new "foo"
# => #<Mail::Address:2309836100 Address: |foo| >
email.address
# => "foo"
It would also be possible to pass Mail’s error message back to the user to give her a more detailed explanation of why the email address looks invalid, though this may be more difficult if your app uses internationalisation (as is the case with many of our apps).
If you enjoyed this, you might enjoy learning more about why emails are generally pretty terrible.