hailgun-simple (empty) → 0.1.0.0
raw patch · 5 files changed
+298/−0 lines, 5 filesdep +basedep +email-validatedep +hailgunsetup-changed
Dependencies added: base, email-validate, hailgun, mtl, text, transformers
Files
- LICENSE +30/−0
- README.md +15/−0
- Setup.hs +2/−0
- hailgun-simple.cabal +30/−0
- src/Mail/Hailgun/Simple.hs +221/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Dennis Gosnell (c) 2016++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Author name here nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,15 @@++Mail.Hailgun.Simple+===================++[](http://travis-ci.org/cdepillabout/hailgun-simple)+[](https://hackage.haskell.org/package/hailgun-simple)+[](http://stackage.org/lts/package/hailgun-simple)+[](http://stackage.org/nightly/package/hailgun-simple)++This module provides an easy-to-use, simple wrapper around the+[hailgun](https://hackage.haskell.org/package/hailgun) package.++## Usage++TODO
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ hailgun-simple.cabal view
@@ -0,0 +1,30 @@+name: hailgun-simple+version: 0.1.0.0+synopsis: Easy-to-use wrapper for the hailgun package+description: Please see README.md+homepage: https://github.com/cdepillabout/hailgun-simple+license: BSD3+license-file: LICENSE+author: Dennis Gosnell+maintainer: cdep.illabout@gmail.com+copyright: 2016 Dennis Gosnell+category: Mail+build-type: Simple+extra-source-files: README.md+cabal-version: >=1.10++library+ hs-source-dirs: src+ exposed-modules: Mail.Hailgun.Simple+ build-depends: base >= 4.6 && < 5+ , email-validate >= 2.2+ , hailgun >= 0.4+ , mtl >= 2.2+ , text >= 1.2+ , transformers >= 0.4+ default-language: Haskell2010+ ghc-options: -Wall++source-repository head+ type: git+ location: git@github.com:cdepillabout/hailgun-simple.git
+ src/Mail/Hailgun/Simple.hs view
@@ -0,0 +1,221 @@+{-# 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+<https://hackage.haskell.org/package/hailgun hailgun> package. hailgun+is a package providing a way to send email using+<https://www.mailgun.com Mailgun>.++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}