Validate email address in Go

development golang email

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.

Get new posts by email

Comments

comments powered by Disqus