'How to validate an email address in Go

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("[email protected]")) // true
    fmt.Println(isEmailValid("[email protected]")) // 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?



Solution 1:[1]

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{
    "[email protected]",
    "bad-example",
} {
    fmt.Printf("%18s valid: %t\n", email, valid(email))
}

Which outputs (try it on the Go Playground):

  [email protected] valid: true
       bad-example valid: false

Solution 2:[2]

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("[email protected]"))         // true 
fmt.Println(isEmailValid("bad-email"))               // false
fmt.Println(isEmailValid("[email protected]"))      // false
fmt.Println(isEmailValid("test-email.com"))        // false
fmt.Println(isEmailValid("[email protected]"))  // true

Solution 3:[3]

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.

Solution 4:[4]

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

// Accepts at least the [email protected] 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
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 Shah
Solution 4 pepe