Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

daison12006013
Copy link

@daison12006013 daison12006013 commented May 22, 2024

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
}

@git-hulk
Copy link
Member

@daison12006013 Generally looks good, thanks for your contribution. It would be great if we could add dedicated methods for those exported fields instead of turning them to a public member directly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants