Validate email address in Go


When you have a website with login or subscribe option, it is always nice to validate if the provided email address is valid. I want to briefly provide a way to do this in Go here. There are multiple ways to do the validation, but here are the three steps we will use to validate an email address.

Firstly, based on the RFC 3696 Errata 1690, a valid email address has a maximum of 254 characters. Here is the relevant snippet from the RFC:

A valid email address has a maximum of 64 characters in the “local part” (before the “@”) and a maximum of 255 characters in the domain part (after the “@”) for a total length of 320 characters. However, there is a restriction in RFC 2821 on the length of an address in MAIL and RCPT commands of 254 characters.

Secondly, W3C has provided recommendation on the valid email address syntax. It also provides a regular expression that can be used to validate the syntax of the email address. Here is the relevant regex snippet from the document linked earlier:

/^[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])?)*$/

Finally, check for the existance of the MX record for the domain part of the email address. We can lookup MX record in Go using the net package.

So let’s combine these into a utility function in Go.

// filename: validate.go
package main

import (
	"net"
	"regexp"
	"strings"
)

func validEmail(id string) bool {
	if len(id) > maxEmailLength {
		return false
	}

	if !emailRegex.MatchString(id) {
		return false
	}

	domain := strings.Split(id, "@")[1]
	if mx, errLookup := net.LookupMX(domain); errLookup != nil || len(mx) == 0 {
		return false
	}

	return true
}

const maxEmailLength = 254

var 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])?)*$")
// filename: validate_test.go
package main

import (
	"fmt"
	"testing"
)

// TestEmailValidity is the unit test to test validEmail function.
func TestEmailValidity(t *testing.T) {
	var tests = []struct {
		email string
		want  bool
	}{
		{email: "test@gmail.com", want: true},
		{email: "test@notgmail.com", want: false},
		{email: "test@", want: false},
		{email: "@gmail.com", want: false},
	}

	for _, td := range tests {
		testname := fmt.Sprintf("when testing email id `%s`", td.email)
		t.Run(testname, func(t *testing.T) {
			ok := validEmail(td.email)
			if ok != td.want {
				t.Errorf("got %t, want %t", ok, td.want)
			}
		})
	}
}
# result of the unit test

➜  go test -v 
=== RUN   TestEmailValidity
=== RUN   TestEmailValidity/when_testing_email_id_`test@gmail.com`
=== RUN   TestEmailValidity/when_testing_email_id_`test@notgmail.com`
=== RUN   TestEmailValidity/when_testing_email_id_`test@`
=== RUN   TestEmailValidity/when_testing_email_id_`@gmail.com`
--- PASS: TestEmailValidity (3.82s)
    --- PASS: TestEmailValidity/when_testing_email_id_`test@gmail.com` (0.26s)
    --- PASS: TestEmailValidity/when_testing_email_id_`test@notgmail.com` (3.56s)
    --- PASS: TestEmailValidity/when_testing_email_id_`test@` (0.00s)
    --- PASS: TestEmailValidity/when_testing_email_id_`@gmail.com` (0.00s)
PASS
ok      go.imgur.com/validate-email     5.107s

References

  1. https://www.rfc-editor.org/errata_search.php?rfc=3696&eid=1690
  2. https://www.w3.org/TR/2016/REC-html51-20161101/sec-forms.html#valid-e-mail-address
  3. https://pkg.go.dev/net#LookupMX

In case you would like to get notified about more articles like this, please subscribe to my substack.



Comments

comments powered by Disqus