{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE ScopedTypeVariables #-} {- | Module : Mail.Hailgun.Simple Copyright : Dennis Gosnell 2016 License : BSD3 Maintainer : Dennis Gosnell (cdep.illabout@gmail.com) Stability : experimental Portability : unknown This module provides a simple, easy-to-use wrapper around the package. hailgun is a package providing a way to send email using . Here is a short example of how to use this package: @ \{\-\# LANGUAGE OverloadedStrings \#\-\} \{\-\# LANGUAGE QuasiQuotes \#\-\} module FooBar where import "Control.Monad.Reader" ('Control.Monad.Reader.ReaderT') import "Data.Text" ('Data.Text.Text') import "Data.Text.Encoding" ('Data.Text.Encoding.encodeUtf8') import "Text.Email.Validate" ('EmailAddress') import Text.Shakespeare.Text (sbt) import "Mail.Hailgun.Simple" ('MessageContent'('TextOnly'), 'Email'(..), 'EmailError', 'HailgunContext', 'ResponseFromMailgun', 'sendEmail') -- This function will send a new user an email. sendEmailToNewUser :: 'Text' -- ^ user's name -> 'EmailAddress' -- ^ user's email address -> 'ReaderT' 'HailgunContext' 'IO' ('Either' 'EmailError' 'ResponseFromMailgun') sendEmailToNewUser name emailaddress = do let email = 'Email' { 'emailSubject' = "Thanks for signing up!" , 'emailBody' = 'TextOnly' $ 'encodeUtf8' body , 'emailReplyTo' = myEmailAddress , 'emailRecipientsTo' = [emailaddress] , 'emailRecipientsCC' = [] , 'emailRecipientsBCC' = [] , 'emailAttachments' = [] } 'sendEmail' email where body :: 'Text' body = [sbt|Hi #{name}! | |Thanks for signing up to our service! | |From your friends at foobar.com :-)|] myEmailAddress :: 'EmailAddress' myEmailAddress = undefined @ -} module Mail.Hailgun.Simple ( -- * Sending an 'Email' sendEmail -- * 'Email' , Email(..) , MessageContent(..) -- * Response , ResponseFromMailgun(..) -- * 'HailgunContext' , HailgunContext(..) , HasHailgunContext(getHailgunContext) -- * Errors , EmailError(..) -- * Lower-level calls , emailToHailgunMessage , sendHailgunMessage ) where import Control.Monad.IO.Class (MonadIO, liftIO) import Control.Monad.Reader (MonadReader, reader) import Data.Bifunctor (bimap, first) import Data.Data (Data) import Data.Text (Text, pack) import Data.Typeable (Typeable) import GHC.Generics (Generic) import Mail.Hailgun (Attachment, HailgunContext, HailgunErrorResponse(..), HailgunMessage, HailgunSendResponse(..), MessageContent(..), MessageRecipients(..), hailgunMessage) import qualified Mail.Hailgun as Hailgun import Text.Email.Validate (EmailAddress, toByteString) -- | This class provides one layer (or multiple layers) of indirection. It -- makes it possible to pass 'sendEmail' a generic configuration datatype -- that contains a 'HailgunContext' instead of a 'HailgunContext' directly. -- -- For instance, imagine you had a configuration datatype like this: -- -- @ -- data Config = Config -- { configDatabasePool :: Pool -- , configHailgunContext :: 'HailgunContext' -- } -- @ -- -- You could create an instance of 'HasHailgunContext' for @Config@ like this: -- -- @ -- instance 'HasHailgunContext' Config where -- getHailgunContext :: Config -> 'HailgunContext' -- getHailgunContext = configHailgunContext -- @ -- -- Now, you can pass @Config@ to 'sendEmail' instead of a 'HailgunContext' -- directly. class HasHailgunContext r where getHailgunContext :: r -> HailgunContext instance HasHailgunContext HailgunContext where getHailgunContext = id -- | Datatype to represent possible errors with sending an email. data EmailError = EmailErrorIncorrectEmailFormat Text -- ^ Email was in incorrect format. Since we are creating emails by hand, -- this error should never occur. | EmailErrorSendError Text -- ^ Error from Mailgun when trying to send an email. deriving (Generic, Show, Typeable) -- | Datatype representing an email to send. data Email = Email { emailSubject :: Text , emailBody :: MessageContent , emailReplyTo :: EmailAddress , emailRecipientsTo :: [EmailAddress] , emailRecipientsCC :: [EmailAddress] , emailRecipientsBCC :: [EmailAddress] , emailAttachments :: [Attachment] } -- | Response returned from Mailgun's servers. data ResponseFromMailgun = ResponseFromMailgun { mailgunMessage :: Text -- ^ Freeform message from Mailgun , mailgunId :: Text -- ^ ID of the message accepted by Mailgun } deriving (Data, Generic, Show, Typeable) -- | Send an 'Email'. -- -- Returns an 'EmailErrorIncorrectEmailFormat' error if the format of the email -- was not correct (for instance, if the email senders or receivers were -- incorrect, or the attachments are specified incorrectly). If you are -- constructing an 'Email' by hand (and not programatically), this error will -- indicate a programmer error. -- -- Returns an 'EmailErrorSendError' if there was a problem with actually -- sending the 'Email'. This will usually be an error from the Mailgun -- servers. sendEmail :: forall r m . ( HasHailgunContext r , MonadIO m , MonadReader r m ) => Email -> m (Either EmailError ResponseFromMailgun) sendEmail = either (pure . Left) sendHailgunMessage . emailToHailgunMessage -- | Wrapper around "Mail.Hailgun"'s 'hailgunMessage'. emailToHailgunMessage :: Email -> Either EmailError HailgunMessage emailToHailgunMessage Email { emailSubject , emailBody , emailReplyTo , emailRecipientsTo , emailRecipientsCC , emailRecipientsBCC , emailAttachments } = let recipients = MessageRecipients (fmap toByteString emailRecipientsTo) (fmap toByteString emailRecipientsCC) (fmap toByteString emailRecipientsBCC) eitherHailgunMessage = hailgunMessage emailSubject emailBody (toByteString emailReplyTo) recipients emailAttachments in first (EmailErrorIncorrectEmailFormat . pack) eitherHailgunMessage -- | Wrapper around "Mail.Hailgun"'s 'Hailgun.sendEmail'. Used by 'sendEmail'. sendHailgunMessage :: forall r m . ( HasHailgunContext r , MonadIO m , MonadReader r m ) => HailgunMessage -> m (Either EmailError ResponseFromMailgun) sendHailgunMessage hailgunMsg = do hailgunContext <- reader getHailgunContext eitherSendResponse <- liftIO $ Hailgun.sendEmail hailgunContext hailgunMsg pure $ bimap hailgunErrorResponseToEmailError hailgunSendResponseToResponseFromMailgun eitherSendResponse hailgunErrorResponseToEmailError :: HailgunErrorResponse -> EmailError hailgunErrorResponseToEmailError = EmailErrorSendError . pack . herMessage hailgunSendResponseToResponseFromMailgun :: HailgunSendResponse -> ResponseFromMailgun hailgunSendResponseToResponseFromMailgun HailgunSendResponse {hsrMessage, hsrId} = ResponseFromMailgun {mailgunMessage = pack hsrMessage, mailgunId = pack hsrId}