email-verifier icon indicating copy to clipboard operation
email-verifier copied to clipboard

Update Verify() with goroutine to speed up the request and some minor changes

Open daison12006013 opened this issue 1 month ago • 0 comments

Updates

  • Changed the getMD5Hash() return order
  • Changed the struct's Verifier to make the enablers accessible outside the package
  • Added goroutine channels inside Verify() to speed up the request.
    • in-addition I've added Verifier.VerifyTimeout to control the timeout (2seconds is already battle tested in fly.io using shared1x256)

In the meantime, if someone would like to incorporate this on their code, you may copy below

Source Code
package email_checker

import (
	"context"
	"time"

	emailVerifier "github.com/AfterShip/email-verifier"
)

func GetEmailInformation(email string) (EmailInformation, error) {
	verification, err := NewVerifier().Verify(email)
	if err != nil {
		return EmailInformation{}, err
	}

	return EmailInformation{
		IsValidFormat: verification.Syntax.Valid,
		Result:        *verification,
	}, nil
}

var (
	reachableYes     = "yes"
	reachableNo      = "no"
	reachableUnknown = "unknown"

	smtpCheckEnabled     bool = true
	catchAllCheckEnabled bool = true
	gravatarCheckEnabled bool = true
	domainSuggestEnabled bool = true
)

type Verifier struct {
	*emailVerifier.Verifier
}

type EmailInformation struct {
	IsValidFormat bool `json:"is_valid_format"`
	emailVerifier.Result
}

func NewVerifier() *Verifier {
	verifier := &Verifier{emailVerifier.NewVerifier()}
	verifier.EnableAutoUpdateDisposable()

	if smtpCheckEnabled {
		verifier.EnableSMTPCheck()
	}

	if catchAllCheckEnabled {
		verifier.EnableCatchAllCheck()
	}

	if gravatarCheckEnabled {
		verifier.EnableGravatarCheck()
	}

	if domainSuggestEnabled {
		verifier.EnableDomainSuggest()
	}

	return verifier
}

func (v *Verifier) Verify(email string) (*emailVerifier.Result, error) {
	ret := emailVerifier.Result{
		Email:     email,
		Reachable: reachableUnknown,
	}

	syntax := v.ParseAddress(email)
	ret.Syntax = syntax
	if !syntax.Valid {
		return &ret, nil
	}

	ret.Free = v.IsFreeDomain(syntax.Domain)
	ret.RoleAccount = v.IsRoleAccount(syntax.Username)
	ret.Disposable = v.IsDisposable(syntax.Domain)

	// If the domain name is disposable, mx and smtp are not checked.
	if ret.Disposable {
		return &ret, nil
	}

	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()

	mxCh := make(chan *emailVerifier.Mx)
	smtpCh := make(chan *emailVerifier.SMTP)
	errCh := make(chan error)

	go func() {
		mx, err := v.CheckMX(syntax.Domain)
		if err != nil {
			errCh <- err
			return
		}
		mxCh <- mx
	}()

	go func() {
		smtp, err := v.CheckSMTP(syntax.Domain, syntax.Username)
		if err != nil {
			errCh <- err
			return
		}
		smtpCh <- smtp
	}()

	select {
	case mx := <-mxCh:
		ret.HasMxRecords = mx.HasMXRecord
	case smtp := <-smtpCh:
		ret.SMTP = smtp
		ret.Reachable = v.calculateReachable(smtp)
	case err := <-errCh:
		return &ret, err
	case <-ctx.Done():
		return &ret, ctx.Err()
	}

	if gravatarCheckEnabled {
		ctx, cancel = context.WithTimeout(context.Background(), 2*time.Second)
		defer cancel()

		gravatarCh := make(chan *emailVerifier.Gravatar)
		errCh = make(chan error)

		go func() {
			gravatar, err := v.CheckGravatar(email)
			if err != nil {
				errCh <- err
				return
			}
			gravatarCh <- gravatar
		}()

		select {
		case gravatar := <-gravatarCh:
			ret.Gravatar = gravatar
		case err := <-errCh:
			return &ret, err
		case <-ctx.Done():
			return &ret, ctx.Err()
		}
	}

	if domainSuggestEnabled {
		ret.Suggestion = v.SuggestDomain(syntax.Domain)
	}

	return &ret, nil
}

func (v *Verifier) calculateReachable(s *emailVerifier.SMTP) string {
	if !smtpCheckEnabled {
		return reachableUnknown
	}
	if s.Deliverable {
		return reachableYes
	}
	if s.CatchAll {
		return reachableUnknown
	}
	return reachableNo
}

daison12006013 avatar May 22 '24 02:05 daison12006013