49

I have checked the StackOverflow and couldn't find any question that answers how to validate email in Go Language.

After some research, I figured out and solved it as per my need.

I have this regex and Go function, which work fine:

import (
    "fmt"
    "regexp"
)

func main() {
    fmt.Println(isEmailValid("test44@gmail.com")) // true
    fmt.Println(isEmailValid("test$@gmail.com")) // true -- expected "false" 
}


// isEmailValid checks if the email provided is valid by regex.
func isEmailValid(e string) bool {
    emailRegex := regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
    return emailRegex.MatchString(e)
}

The problem is that it accepts the special characters that I don't want. I tried to use some from other languages' "regex" expression, but it throws the error "unknown escape" in debug.

Could anyone give me a good regex or any fast solution (pkg) that works with GoLang?

Riyaz Khan
  • 2,765
  • 2
  • 15
  • 29

6 Answers6

107

The standard lib has email parsing and validation built in, simply use: mail.ParseAddress().

A simple "is-valid" test:

func valid(email string) bool {
    _, err := mail.ParseAddress(email)
    return err == nil
}

Testing it:

for _, email := range []string{
    "good@exmaple.com",
    "bad-example",
} {
    fmt.Printf("%18s valid: %t\n", email, valid(email))
}

Which outputs (try it on the Go Playground):

  good@exmaple.com valid: true
       bad-example valid: false

NOTE:

The net/mail package implements and follows the RFC 5322 specification (and extension by RFC 6532). This means a seemingly bad email address like bad-example@t is accepted and parsed by the package because it's valid according to the spec. t may be a valid local domain name, it does not necessarily have to be a public domain. net/mail does not check if the domain part of the address is a public domain, nor that it is an existing, reachable public domain.

icza
  • 389,944
  • 63
  • 907
  • 827
  • 4
    Thanks for that, it's working on most of the cases. Still, I have some issues with the following invalid emails: "email@example.com (Joe Smith)", "email@example" – Riyaz Khan Mar 14 '21 at 12:00
  • @RiyazKhan Those email addresses are valid according to [RFC 5322](https://tools.ietf.org/html/rfc5322). The `net/mail` package implements RFC 5322 compliant email address parsing. – icza Mar 14 '21 at 12:10
  • 2
    got it, as in the doc for RFC 5322, it's written that it's checking domain. Here is [screenshot](https://paste.pics/e1dbbbeeee708fdd68fab6583eae4f7d), then why it's failing for the "email@example" address. – Riyaz Khan Mar 14 '21 at 12:16
  • 4
    @RiyazKhan `example` is a valid domain, just like `example.com`. It does not necessarily have to designate a public domain, it may be a local domain of a local network. As to the `(Joe Smith)` part: it's a comment and it may be anywhere in the email, see [Wikipedia: Email address](https://en.wikipedia.org/wiki/Email_address). Trust me, the `net/mail` package can parse email addresses better, faster and more reliably than your custom solution can. – icza Mar 14 '21 at 12:33
  • 2
    I don't think this is a valid email "bad-example@t" – tile Jul 17 '21 at 09:55
  • 2
    @tile According to [RFC 5322](https://www.rfc-editor.org/rfc/rfc5322.html) it is valid. `t` may be a valid local domain name. As mentioned in the above comments, the domain may be a local domain, it does not necessarily have to be a public domain. – icza Jul 18 '21 at 14:36
  • this method ParseAddress also accepts "good " as a valid string. Might not be what you want. – Duck1337 Oct 29 '21 at 16:56
  • @Duck1337 Yes, because that format is used to specify the person name and email address. `good` is the person name, `good@email.com` is the email address in your example. It's perfectly valid by RFC 5322. – icza Nov 02 '21 at 15:45
  • can `my@you` be a valid email account? `mail.ParseAddress()` is returning `true` – Mudasir Younas Dec 31 '21 at 10:11
  • 1
    @MudasirYounas Yes, it can be. All email addresses are valid where `mail.ParseAddress()` returns `nil` error. No need to ask for every one of them. – icza Dec 31 '21 at 10:21
  • even `a@b` get passwd, this is not very good for real email. – Eric Aug 17 '22 at 05:03
  • @Eric Please read comments above. It's valid, `b` may be a _local_ domain, so in a local network `a@b` can be used as a valid email address. – icza Aug 17 '22 at 06:19
14

The above approach from @icza is nice, however, if we use it in login/signup form validation, many people will enter a partial or incorrect email address, which will create a bunch of invalid records in production. Furthermore, who knows we might get killed because of that.

Therefore, I went to the regex solution to validate standard emails:

Here is the code:

func isEmailValid(e string) bool {
    emailRegex := regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`)
    return emailRegex.MatchString(e)
}

Test Cases:

fmt.Println(isEmailValid("test44@gmail.com"))         // true 
fmt.Println(isEmailValid("bad-email"))               // false
fmt.Println(isEmailValid("test44$@gmail.com"))      // false
fmt.Println(isEmailValid("test-email.com"))        // false
fmt.Println(isEmailValid("test+email@test.com"))  // true
Riyaz Khan
  • 2,765
  • 2
  • 15
  • 29
  • 3
    even the most rotten answer of SO acknowledge that $ is accepted. https://stackoverflow.com/a/2049510/4466350 https://play.golang.org/p/iI2GeEmHRzE –  Aug 14 '21 at 10:15
  • 2
    Believe me, `mail.ParseAddress()` does a better job than what you can. It accepts local domain, but tell me, are you sure `this-surely-doesnt-exist-just-showing-this-will-pass@nowhereland.cum` is a better "valid" email than `good@mydomain`? You should implement and require email verification anyway. Else simply validating if the email address is syntactically correct is worth close to nothing. – icza Aug 19 '21 at 11:17
  • @icza why `mail.ParseAddress()` is passing your bad example email `this-surely-doesnt-exist-just-showing-this-will-pass@nowhereland.cum` ?. Who cares about the local domain in production, can you register an email address like a local domain in any email provider service? – Riyaz Khan Aug 23 '21 at 04:24
  • @RiyazKhan Yes, you can't use a local domain at `gmail.com`, because `gmail.com` is a public domain. But if a company operates an email service for its workers, yes, you can use (or you may _only_ use) a local domain there. – icza Aug 23 '21 at 06:49
  • This misses non-ASCII characters, which are valid in the domain name part and the TLD. Also, there are ASCII TLDs with length greater than 4 (https://en.wikipedia.org/wiki/List_of_Internet_top-level_domains). And, even if you have a valid email, you still need to check it's actually owned by your user. So you HAVE to require email verification. – ineiti Nov 09 '21 at 05:06
2

Since I find regexp hard to read I prefer readable boring code. For example:

// Accepts at least the x@y.zz pattern.
func isEmailAddress(v string) bool {
    if v == "" {
        return false
    }
    if containsWhitespace(v) {
        return false
    }

    iAt := strings.IndexByte(v, '@')
    if iAt == -1 {
        return false
    }

    localPart := v[:iAt]
    if localPart == "" {
        return false
    }

    domain := v[iAt+1:]
    if domain == "" {
        return false
    }

    iDot := strings.IndexByte(domain, '.')
    if iDot == -1 || iDot == 0 || iDot == len(domain)-1 {
        return false
    }

    if strings.Index(domain, "..") != -1 {
        return false
    }

    iTLD := strings.LastIndexByte(domain, '.')
    return 2 <= len([]rune(domain[iTLD+1:]))
}

func containsWhitespace(v string) bool {
    for _, r := range v {
        if unicode.IsSpace(r) {
            return true
        }
    }
    return false
}
pepe
  • 814
  • 8
  • 12
1

This external package govalidator is doing the job perfectly for me.

import (
  "fmt"
  "github.com/asaskevich/govalidator"
)
func main() {
    d := govalidator.IsEmail("helloworld@") 
    fmt.Println(d) // false
}
Mrkouhadi
  • 446
  • 5
  • 14
0

Method implementation example:

var emailRegexp = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")

This was in the same package as where I created my struct

type EmailInputRegistration struct {
    Email           string
}

And then for handling errors:

func (in EmailInputRegistration) Validate() error {
    if !emailRegexp.MatchString(in.Email) {
        return fmt.Errorf("%w: email invalid", ErrValidation)
    }
    //any other exception handling...

    return nil
}

Ideally, this EmailInputRegistration should be refactored to include all the data needed for Registering such as email, user, password, etc.

Shah
  • 2,126
  • 1
  • 16
  • 21
0

You can create a function IsEmail which is based on the standard library mail

func IsEmail(email string) bool {
  emailAddress, err := mail.ParseAddress(email)
  return err == nil && emailAddress.Address == email
}

Test cases:

func main() {
  fmt.Println("'hello@world.com'", IsEmail("hello@world.com")) // true
  fmt.Println("'asd <hello@world.com>'", IsEmail("asd <hello@world.com>")) // false
  fmt.Println("'a@b'", IsEmail("a@b")) // true
}
Slavik Meltser
  • 9,712
  • 3
  • 47
  • 48