Skip to content

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