In this guide, I’ll walk you through the most common (and efficient) Go email validation approaches and provide you with code snippets for each.
Namely, I’ll cover Go email validation using:
Note: I’ll also include a section on email verification, to which you can jump to by clicking here.
Validate emails in Go using regex
To perform basic regex, or regular expression, checks against the email IDs, I recommend using Go’s native regexp package.
The package is a part of Go’s standard library and is quite straightforward to use. It’s ideal for quick validation checks of the basic email address structure, but that’s that.
To get started, simply copy the following code into your main.go file:
package main
import (
"fmt"
"regexp"
)
func main() {
// Define the stricter email regex pattern (based on RFC 5322)
emailRegex := `^[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])?)*$`
// Compile the regex
re := regexp.MustCompile(emailRegex)
// Test email addresses
emails := []string{
"test@example.com", // Valid
"invalid-email@", // Invalid (missing domain)
"user@domain.co", // Valid
"user@.com", // Invalid
"foo@bar.com", // Valid
"special_chars+123@sub.com", // Valid
"missing@tld", // Valid
"email@domain-with-hyphen.com", // Valid
"user@localhost", // Invalid (no TLD)
}
// Validate each email and print the result
for _, email := range emails {
if re.MatchString(email) {
fmt.Printf("%s is valid\n", email)
} else {
fmt.Printf("%s is invalid\n", email)
}
}
}
This regex is based on RFC 5322 and will make sure that the email:
- Starts with a valid local part (username) that can consist of alphanumeric (a-z, A-Z, 0-9) and special (., !., #, $) characters.
- Contains an @ symbol that separates the username and the domain.
- Can have hyphens (-) but cannot start or end with one.
- Has a valid domain part that starts with an alphanumeric character and is between 1 and 63 characters long.
- Ends with a top-level domain (TLD) that contains at least two alphabetic characters (e.g., com, .org, etc.).
For testing purposes, I’ve added the // Test email addresses
part of the code snippet. So, if you run the app with go run main.go
, you should get the following results:
test@example.com is valid
invalid-email@ is invalid
user@domain.co is valid
user@.com is invalid
foo@bar.com is valid
special_chars+123@sub.com is valid
missing@tld is invalid
email@domain-with-hyphen.com is valid
user@localhost is invalid
Of course, feel free to change the emails in // Test email addresses
if you want to test them out further. As for testing out different regex patterns, I suggest using regex101 or Go Playground. ⚒️
Important:
- Although simple to use, regex can’t cover all cases, such as super long or internationalized domains. You can see the possible trade-offs in this article.
- To extend the validation functionality of regex, you can combine it with other packages I’ll talk about in this article.⚒️
Validate emails in Go using the net/mail package
Another native Go package you can use is net/mail. Similarly to the regex check from the previous chapter, it follows the syntax specified in RFC 5322, and it’s extended by RFC 6532.
Net/mail focuses solely on email parsing and syntax, making it a tad bit more advanced, but still a basic validation solution.
To use net/mail, copy the following code snippet into your main.go:
package main
import (
"fmt"
"net/mail"
)
func main() {
// List of email addresses to validate
emails := []string{
"test@example.com",
"invalid-email@", // Missing domain
"user@domain.co", // Valid
"user@.com", // Invalid domain
"foo@bar.com", // Valid
"missing@tld", // Invalid
"email@domain-with-hyphen.com", // Valid
}
for _, email := range emails {
// Use mail.ParseAddress for validation
_, err := mail.ParseAddress(email)
if err != nil {
fmt.Printf("%s is invalid: %v\n", email, err)
} else {
fmt.Printf("%s is valid\n", email)
}
}
}
By adding this regex validation logic to your Go app/project, you ensure that:
- The username (local part) cannot start or end with a dot (.).
- The domain must have at least one period.
- Domains cannot have consecutive periods or start/end with a hyphen (-).
- The entire email doesn’t exceed 254 characters.
- The email doesn’t contain any invalid characters (e.g., spaces and commas).
- Both the local and the domain parts meet length constraints of the standard (e.g., 64-character limit for the local part)
And when you run the code, you should expect to see these results:
test@example.com is valid
invalid-email@ is invalid: mail: missing '@' or angle-addr
user@domain.co is valid
user@.com is invalid: mail: invalid domain
foo@bar.com is valid
missing@tld is invalid: mail: invalid domain
email@domain-with-hyphen.com is valid
Tip: To validate multiple email addresses in a single string (e.g., a comma-separated list), use mail.ParseAddressList
. And here’s a code example you can copy/paste:
package main
import (
"fmt"
"net/mail"
)
func main() {
// Comma-separated list of email addresses
emailList := "test@example.com, invalid-email@, user@domain.co, foo@bar.com"
// Parse and validate the list
addresses, err := mail.ParseAddressList(emailList)
if err != nil {
fmt.Printf("Invalid list: %v\n", err)
return
}
// Print valid addresses
fmt.Println("Valid emails:")
for _, address := range addresses {
fmt.Println(address.Address)
}
}
And here’s the output you can expect in case any email in the list is invalid:
Invalid list: mail: missing '@' or angle-addr
Validate emails in Go using external packages
If you want to spice it up a bit, you can also use external packages for Go. I’ve scoured the web for the best ones, tried them all, and ditched the packages that aren’t being regularly maintained and the ones that are not efficient. Here’s what I came up with:
go-playground/validator
validator by go-playground isn’t your typical validation package as it has more features than just validation. Also, it doesn’t include DNS/MX records or SMTP checks, which makes it great for users who don’t require any external dependencies but still want to implement straightforward validation logic.
With go-playground/validator, you can:
- Validate the email format.
- Define custom validation functions for more specific use cases.
- For example:
- Validate struct fields based on tags, which can come in handy for form or input validation. E.g.
type User struct {
Email string `validate:"required,email"`
}
- Validate strings, numbers, URLs, and more.
And, similarly to checkmail, this package also offers detailed error messages for better debugging. It even supports field-error mapping.
To set up and use go-playground’s validator, you first need to install it with the following command:
go get github.com/go-playground/validator/v10
Then, import the package in your main.go file:
import (
"fmt"
"github.com/go-playground/validator/v10"
)
Create and initialize a new validator instance:
validate := validator.New()
Lastly, define a struct with validation tags and validate it:
type User struct {
Email string `validate:"required,email"`
}
func main() {
user := User{Email: "example@example.com"}
err := validate.Struct(user)
if err != nil {
// Print detailed validation errors
for _, err := range err.(validator.ValidationErrors) {
fmt.Printf("Field '%s' failed validation for tag '%s'\n", err.Field(), err.Tag())
}
} else {
fmt.Println("Validation passed!")
}
}
To validate individual fields instead of a struct, use this code:
err := validate.Var("example@example.com", "required,email")
if err != nil {
fmt.Println("Invalid email!")
} else {
fmt.Println("Valid email!")
}
On the other hand, if you want to define and register custom validation functions, here’s a code snippet you can play around with and use:
// Custom function to validate email domain
func validDomain(fl validator.FieldLevel) bool {
domain := "example.com"
return strings.HasSuffix(fl.Field().String(), domain)
}
func main() {
validate := validator.New()
// Register custom validation tag
validate.RegisterValidation("domain", validDomain)
type User struct {
Email string `validate:"required,email,domain"`
}
user := User{Email: "test@example.com"}
err := validate.Struct(user)
if err != nil {
fmt.Println("Validation failed:", err)
} else {
fmt.Println("Validation passed!")
}
}
AfterShip/email-verifier
Don’t let the name of this package confuse you, because email-verifier by AfterShip is actually an email validator (more on differences between verification and validation in the next chapter). The package offers more advanced features than the previous two and allows you to;
- Validate the email address format.
- Perform DNS/MX record checks
- Verify the SMTP connection.
- Identify disposable email addresses, such as Mailinator.
- Detect email addresses that belong to free providers (e.g., Gmail, Yahoo, etc.)
- Identify role-based email addresses like support@domain.com.
- Check for catch-all domains that accept emails from any address.
Additionally, email-verifier provides you with precise feedback when an email fails validation, instead of detailed error messages. Here’s how it works:
Start by installing the package with the following command:
go get github.com/AfterShip/email-verifier
Import the package and any other necessary libraries in your main.go file:
package main
import (
"fmt"
emailverifier "github.com/AfterShip/email-verifier"
)
var (
verifier = emailverifier.NewVerifier()
)
func main() {
email := "test@example.com"
ret, err := verifier.Verify(email)
if err != nil {
fmt.Println("verify email address failed, error is: ", err)
return
}
if !ret.Syntax.Valid {
fmt.Println("email address syntax is invalid")
return
}
// Print the result fields based on the updated API
fmt.Println("Email Validation Result:")
fmt.Println("Email:", ret.Email)
fmt.Println("Syntax Valid:", ret.Syntax.Valid)
fmt.Println("Disposable:", ret.Disposable)
fmt.Println("Reachable:", ret.Reachable)
fmt.Println("Role Account:", ret.RoleAccount)
fmt.Println("Free Provider:", ret.Free)
fmt.Println("Has MX Records:", ret.HasMxRecords)
}
Note: The code above imports and creates a new instance of the package, checks the email’s format, DNS records, whether it’s disposable, catch-all, etc. However, you can add/remove features as you see fit.
How to choose the right external package for Go email validation
If you’re having a hard time choosing an external package, think about what your app needs, how easy to use the package is, and whether it’s regularly updated.
For your convenience, here’s a table summarizing the most important aspects:
Feature | go-playground/validator | AfterShip/email-verifier |
Format validation | ✅ | ✅ |
DNS/MX record check | ❌ | ✅ |
SMTP verification | ❌ | ✅ |
Disposable email detection | ❌ | ✅ |
Ease of use | ✅ Very easy to use | ❌ A bit more complex |
Requires external dependencies | ❌ | ✅ |
Best for | Validation with extra features | More complex validation |
MIT license | ✅ | ❌ |
Disclaimer: As of writing this article, all packages I mention are being actively maintained. However, I’d still recommend you to double check the release history or use Snyk to see the package health score.
Bonus: email verification in Go
If we assume that email validation ensures email addresses are entered correctly without typos, email verification is a process that takes this a step further.
Email verification not only detects typos but also sends an activation link or code to the user’s email address, which they then need to activate to verify their address. This way, you can ensure that the email address belongs to an actual user.
For email verification in Go, I’ve found that JWT tokens work best for me. Moreover, the logic is quite simple. And here’s the code snippet you can feel free to copy/paste into your main.go file:
package main
import (
"fmt"
"log"
"net/http"
"time"
"github.com/golang-jwt/jwt/v4"
"gopkg.in/gomail.v2"
)
var jwtSecret = []byte("your_secret_key")
// Generate a JWT token for email verification
func generateToken(email string) (string, error) {
claims := jwt.MapClaims{
"email": email,
"exp": time.Now().Add(30 * time.Minute).Unix(),
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(jwtSecret)
}
// Send verification email
func sendVerificationEmail(email, token string) error {
m := gomail.NewMessage()
m.SetHeader("From", "sender@example.com")
m.SetHeader("To", email)
m.SetHeader("Subject", "Email Verification")
verificationLink := fmt.Sprintf("http://localhost:8080/verify?token=%s", token)
m.SetBody("text/plain", fmt.Sprintf("Hi,\n\nPlease verify your email by clicking the link below:\n%s\n\nThis link will expire in 30 minutes.", verificationLink))
d := gomail.NewDialer("smtp.mailtrap.io", 587, "your_mailtrap_username", "your_mailtrap_password")
return d.DialAndSend(m)
}
// Email verification handler
func verifyHandler(w http.ResponseWriter, r *http.Request) {
tokenString := r.URL.Query().Get("token")
if tokenString == "" {
http.Error(w, "Missing token", http.StatusBadRequest)
return
}
// Parse and validate the token
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return jwtSecret, nil
})
if err != nil || !token.Valid {
http.Error(w, "Invalid or expired token", http.StatusBadRequest)
return
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok || !token.Valid {
http.Error(w, "Invalid token", http.StatusBadRequest)
return
}
email := claims["email"].(string)
w.Write([]byte(fmt.Sprintf("Email %s has been successfully verified!", email)))
}
func main() {
// Simulate sending verification email
http.HandleFunc("/send", func(w http.ResponseWriter, r *http.Request) {
email := r.URL.Query().Get("email")
if email == "" {
http.Error(w, "Missing email", http.StatusBadRequest)
return
}
token, err := generateToken(email)
if err != nil {
http.Error(w, "Failed to generate token", http.StatusInternalServerError)
return
}
err = sendVerificationEmail(email, token)
if err != nil {
http.Error(w, "Failed to send email", http.StatusInternalServerError)
return
}
w.Write([]byte("Verification email sent"))
})
// Verify endpoint
http.HandleFunc("/verify", verifyHandler)
fmt.Println("Server running on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Code breakdown:
generateToken
creates a JWT token with the user’s email in the payload./send?email=<email>
triggers the email verification flow andsendVerificationEmail
.sendVerificationEmail
sends the email with the verification link and token.- Keep in mind that you need an email service for this Golang email-sending logic to work. As you can notice from the comments in the code, I personally use Mailtrap SMTP, which provides me with a reliable email infrastructure and ready-to-use code snippets.
verifyHandler
extracts the token from the URL, verifies it, and extracts the user’s email from the token claims./verify?token=<token>
verifies the token and email.
Next, start the server with go run main.go
and then run the following command to trigger an email:
http://localhost:8080/send?email=test@example.com
Check your inbox for the verification link, click it, or simply manually call:
http://localhost:8080/verify?token=<your-token>
Lastly, keep in mind that I’ve added expiration logic, so the token will be valid for 30 minutes.
Email validation as part of email testing
By inspecting the format of email addresses (validation) and the existence of a user behind them (verification), you keep your email lists clean and improve your email deliverability, but only to a certain extent.
Namely, both validation and verification are only aspects of email testing, the missing link you’re looking for. This is an industry-standard practice that ensures your emails are pitch perfect before you send them out, that they pass spam checks, and that your domain isn’t on a blacklist.
That’s why I always recommend Mailtrap Email Testing, which can help me with all of the above and more.
With Email Testing, you can catch traffic from staging and dev environments and analyze the HTML/CSS of your emails. This allows you to easily spot and, most importantly, remove or fix any faulty lines of code you notice, making your email impeccable.
There is also the Spam Analysis feature, which lets you see how likely your emails are to pass spam filters. It’s super simple: you get a spam score, use the detailed table to make adjustments (if any are needed), and keep the score under 5 to proactively boost your email deliverability.
Finally, I must mention that besides being packed with advanced features, Mailtrap Email Testing is also super easy to configure. You can learn how to test emails in Golang by reading our in-depth article or follow along with the video our YouTube team has prepared for you!
Further reading on email validation: