feat: check wildcard addresses, retry several times to make sure that it really doesn't exist

This commit is contained in:
orion 2023-12-06 20:17:26 -06:00
parent 825c35969c
commit 687a9c76b8
Signed by: orion
GPG Key ID: 6D4165AE4C928719
2 changed files with 41 additions and 7 deletions

View File

@ -3,6 +3,7 @@ package:
- aff
- aff-promise
- arrays
- bifunctors
- effect
- either
- foldable-traversable
@ -11,6 +12,7 @@ package:
- nullable
- prelude
- strings
- tailrec
- transformers
- typelevel-prelude
name: email-address

View File

@ -1,13 +1,16 @@
module Effect.Email (EmailError(..), emailErrorToString, deliverable) where
module Effect.Email (EmailError(..), emailErrorCode, emailErrorToString, deliverable) where
import Prelude
import Control.Monad.Cont (lift)
import Control.Monad.Error.Class (throwError)
import Control.Monad.Except (ExceptT)
import Control.Monad.Error.Class (liftEither, throwError)
import Control.Monad.Except (ExceptT(..), runExceptT)
import Control.Monad.Rec.Class (Step(..), tailRecM)
import Control.Promise (Promise)
import Control.Promise as Promise
import Data.Array as Array
import Data.Bifunctor (lmap)
import Data.Either (Either(..))
import Data.Email (Email)
import Data.Email as Email
import Data.Eq.Generic (genericEq)
@ -25,14 +28,25 @@ foreign import checkEmailImpl :: String -> Effect $ Promise $ QEDResult
data EmailError
= EmailUnreachable Email
| EmailWildcardExists Email
| EmailSyntaxInvalid Email
| EmailMXInvalid Email
| EmailMXNoRecords Email
| EmailSMTPInvalid Email
| EmailSMTPError Email String
emailErrorCode :: EmailError -> String
emailErrorCode (EmailUnreachable _) = "email_unreachable"
emailErrorCode (EmailWildcardExists _) = "email_wildcard"
emailErrorCode (EmailSyntaxInvalid _) = "email_invalid"
emailErrorCode (EmailMXInvalid _) = "email_mx_bad"
emailErrorCode (EmailMXNoRecords _) = "email_mx_missing"
emailErrorCode (EmailSMTPInvalid _) = "email_smtp_bad"
emailErrorCode (EmailSMTPError _ _) = "email_smtp_error"
emailErrorToString :: EmailError -> String
emailErrorToString (EmailUnreachable e) = "Email <" <> Email.toString e <> "> is unreachable"
emailErrorToString (EmailWildcardExists e) = "Wildcard address <*." <> Email.domain e <> "> is registered; cannot definitively say if <" <> Email.toString e <> "> exists."
emailErrorToString (EmailSyntaxInvalid e) = "Email <" <> Email.toString e <> "> is invalid"
emailErrorToString (EmailMXInvalid e) = "Email <" <> Email.toString e <> "> domain has invalid MX record"
emailErrorToString (EmailMXNoRecords e) = "Email <" <> Email.toString e <> "> domain has no MX records"
@ -64,10 +78,9 @@ type QEDResult =
}
}
deliverable :: Email -> ExceptT EmailError Aff Email
deliverable email' = do
deliverableOnce :: Email -> ExceptT EmailError Aff Email
deliverableOnce email' = do
let email = Email.toString email'
{ reachable, syntax, mx, smtp } <- lift $ Promise.toAffE $ checkEmailImpl email
when (not reachable) $ throwError $ EmailUnreachable email'
when (not syntax.valid) $ throwError $ EmailSyntaxInvalid email'
@ -75,5 +88,24 @@ deliverable email' = do
when (not smtp.valid) $ throwError $ EmailSMTPInvalid email'
when (maybe true Array.null $ Nullable.toMaybe $ mx.mxRecords) $ throwError $ EmailMXNoRecords email'
for_ (Nullable.toMaybe $ smtp.error) $ throwError <<< EmailSMTPError email'
pure email'
deliverable :: Email -> ExceptT EmailError Aff Email
deliverable email' =
let
attempt n
| n < 10 =
do
res <- runExceptT $ deliverableOnce email'
case res of
Left (EmailUnreachable _) -> pure $ Loop $ n + 1
Left e -> pure $ Done $ Left e
Right ok -> pure $ Done $ Right ok
| otherwise = pure $ Done $ Left $ EmailUnreachable email'
in
do
wildcard <- liftEither $ lmap (const $ EmailSyntaxInvalid email') $ Email.parse $ "*@" <> Email.domain email'
res <- lift $ runExceptT $ deliverableOnce wildcard
case res of
Right _ -> throwError $ EmailWildcardExists email'
_ -> ExceptT $ tailRecM attempt 0