DEV Community

Cover image for Email Verifier using Go
Mohammad Quanit
Mohammad Quanit

Posted on

Email Verifier using Go

Hello everyone, it's been quite some time since I wrote a tech blog, so I thought I should share something that I've done in my company. There was a requirement where I had to do some verification checks on email and I was using Go on that project, so in this blog, I'll be sharing how I did that and also for you guys to know how email verification and its internals work.
I am sharing here it as a mini project so you can follow what I am doing and share feedback if you like.

Building an email verifier tool in Go involves several components and considerations.

I am not going to deep dive into all of that but covering some of them to get you an understanding of the email verification process.

The first thing that we need to verify is a domain that we were getting from some input e.g. google.com.

Steps to start your project in Go in the terminal/cmd:

1. go mod init github.com/username/email-verifier

2. touch main.go
Enter fullscreen mode Exit fullscreen mode

Starting the implementation in our main.go file.

package main

import (
    "bufio"
    "log"
    "net"
    "os"
    "strings"
)

func main() {
    scanner := bufio.NewScanner(os.Stdin)
    for scanner.Scan() {
        verifyDomain(scanner.Text())
    }

    if err := scanner.Err(); err != nil {
        log.Fatal("Error: could not read from input %v\n", err)
    }
}
Enter fullscreen mode Exit fullscreen mode

In the above code snippet, the NewScanner function will take the domain as input from the terminal or cmd from the user. Then the for loop will continuously scan the very next input after every time the verifyDomain function is invoked. And if there is some error it will be printed on the console as simple as that.

Now, the fun part will be covered in the verifyDomain function, where every call happens to the specific domain through HTTP.

func verifyDomain(domain string) {
   var hasMX, hasSPF, hasDMARC bool
   var spfRecord, dmarcRecord string
}
Enter fullscreen mode Exit fullscreen mode

When we verify email through its internals, we need some components to check whether it is valid. Those components are records and protocols used to manage and secure email delivery. Here's a breakdown:

MX Record

An MX (mail exchange) record specifies mail servers responsible for receiving emails on behalf of a domain. When someone sends an email to user@example.com, the sender's mail server queries the DNS to find the MX records for example.com.

Here's how to look at MX records using Go,

      // MX record
    mxRecords, err := net.LookupMX(domain)
    if err != nil {
        log.Printf("Error: could not find MX record for %s due to %v\n", domain, err)
    }
    if len(mxRecords) > 0 {
        hasMX = true
    }
Enter fullscreen mode Exit fullscreen mode

SPF

SPF (Sender Policy Framework) is an email authentication method that specifies which mail servers are authorized to send emails on behalf of a domain. When an email is received, the recipient's server checks the sender's IP against the domain's SPF record to verify the email is legitimate. Checking SPF can help identify spoofed emails.

SPF example:

v=spf1 ip4:192.0.2.0/24 include:_spf.google.com -all  
Enter fullscreen mode Exit fullscreen mode

v=spf1: Indicates the SPF version.
ip4:192.0.2.0/24: Specifies allowed IP ranges.
include:_spf.google.com: Includes Google's SPF records.
-all: Reject emails from unauthorized sources.

Here's how to look at SPF using Go,

 // SPF record
    txtRecords, err := net.LookupTXT("spf." + domain)
    if err != nil {
        log.Printf("Error: could not find SPF record for %s due to %v\n", domain, err)
    }

    for _, record := range txtRecords {
        if strings.HasPrefix(record, "v=spf1") {
            hasSPF = true
            spfRecord = record
            break
        }
    }
Enter fullscreen mode Exit fullscreen mode

DMARC

DMARC (Domain-based Message Authentication, Reporting, and Conformance) builds on SPF and DKIM (DomainKeys Identified Mail) to provide additional email authentication and reporting capabilities. It specifies how to handle emails that fail SPF or DKIM checks (e.g., reject, quarantine, or do nothing). The domain owner publishes a DMARC policy as a DNS TXT record. DMARC helps assess the security posture of a domain.

DMARC. example:

v=DMARC1; p=reject; rua=mailto:dmarc-reports@example.com;  
Enter fullscreen mode Exit fullscreen mode

v=DMARC1: Indicates the DMARC version.
p=reject: Reject emails that fail authentication.
rua: Email address for aggregate reports.

Here's how to look at DMARC using Go,

// DMARC record
    dmarcRecords, err := net.LookupTXT("_dmarc." + domain)
    if err != nil {
        log.Printf("Error: could not find DMARC record for %s due to %v\n", domain, err)
    }

    for _, record := range dmarcRecords {
        if strings.HasPrefix(record, "v=DMARC1") {
            hasDMARC = true
            dmarcRecord = record
            break
        }
    }

Enter fullscreen mode Exit fullscreen mode

Below is the complete code for verifying email in Go.

package main

import (
    "bufio"
    "log"
    "net"
    "os"
    "strings"
)

func verifyDomain(domain string) {
    var hasMX, hasSPF, hasDMARC bool
    var spfRecord, dmarcRecord string

    // MX record
    mxRecords, err := net.LookupMX(domain)
    if err != nil {
        log.Printf("Error: could not find MX record for %s due to %v\n", domain, err)
    }
    if len(mxRecords) > 0 {
        hasMX = true
    }

    // SPF record
    txtRecords, err := net.LookupTXT("spf." + domain)
    if err != nil {
        log.Printf("Error: could not find SPF record for %s due to %v\n", domain, err)
    }

    for _, record := range txtRecords {
        if strings.HasPrefix(record, "v=spf1") {
            hasSPF = true
            spfRecord = record
            break
        }
    }

    // DMARC record
    dmarcRecords, err := net.LookupTXT("_dmarc." + domain)
    if err != nil {
        log.Printf("Error: could not find DMARC record for %s due to %v\n", domain, err)
    }

    for _, record := range dmarcRecords {
        if strings.HasPrefix(record, "v=DMARC1") {
            hasDMARC = true
            dmarcRecord = record
            break
        }
    }

    log.Printf("Domain: %v,\n MX: %v,\n SPF:  %v,\n DMARC:  %v,\n SPF Rec: %v,\n DMARC Rec %v,\n\n", domain, hasMX, hasSPF, hasDMARC, spfRecord, dmarcRecord)
}

func main() {
    scanner := bufio.NewScanner(os.Stdin)
    for scanner.Scan() {
        verifyDomain(scanner.Text())
    }

    if err := scanner.Err(); err != nil {
        log.Fatalf("Error: could not read from input %v\n", err)
    }
}

Enter fullscreen mode Exit fullscreen mode

When you run go run main.go in the terminal, you will provide a domain name e.g. google.com or resend.com, it will find the records using the HTTP library that we used and return the response.

Example Response:

go run main.go
google.com
2025/01/12 13:15:26 Error: could not find SPF record for google.com due to lookup spf.google.com on 192.168.1.1:53: no such host
2025/01/12 13:15:26 Domain: google.com,
 MX: true,
 SPF:  false,
 DMARC:  true,
 SPF Rec: ,
 DMARC Rec v=DMARC1; p=reject; rua=mailto:mailauth-reports@google.com,
Enter fullscreen mode Exit fullscreen mode

if you like this article, please like and share it with your Gopher friends and follow me on Linkedin, Github, Twitter/X.

Peace ✌🏻

Top comments (0)