{-# LANGUAGE GeneralizedNewtypeDeriving , UndecidableInstances #-} module Mail ( -- * Structured MIME emails module Network.Mail.Mime -- * MailT , MailT, defMailT , MailSmtpData(..), MailContext(..), MailLanguages(..) , MonadMail(..) , getMailMessageRender, getMailMsgRenderer -- * YesodMail , VerpMode(..) , YesodMail(..) , MailException(..) -- * Monadically constructing Mail , PrioritisedAlternatives , ToMailPart(..) , addAlternatives, provideAlternative, providePreferredAlternative , addPart , MonadHeader(..) , MailHeader , MailObjectId , replaceMailHeader, addMailHeader, removeMailHeader , replaceMailHeaderI, addMailHeaderI , setSubjectI, setMailObjectId, setMailObjectId' , setDate, setDateCurrent , setMailSmtpData , _mailFrom, _mailTo, _mailCc, _mailBcc, _mailHeaders, _mailParts , _partType, _partEncoding, _partFilename, _partHeaders, _partContent ) where import ClassyPrelude.Yesod hiding (snoc, (.=), getMessageRender) import Network.Mail.Mime hiding (addPart, addAttachment) import qualified Network.Mail.Mime as Mime (addPart) import Data.Monoid (Last(..)) import Control.Monad.Trans.RWS (RWST(..)) import Control.Monad.Trans.State (StateT(..), execStateT, mapStateT) import Control.Monad.Trans.Writer (execWriter, Writer) import Control.Monad.RWS.Class (MonadWriter(..), MonadReader(..), MonadState(..), modify) import Control.Monad.Fail import GHC.Generics (Generic) import Generics.Deriving.Monoid (memptydefault, mappenddefault) import Data.Sequence (Seq) import qualified Data.Sequence as Seq import Data.Set (Set) import qualified Data.Set as Set import qualified Data.Text as Text import qualified Data.Foldable as Foldable import qualified Data.Text.Lazy as LT import qualified Data.Text.Lazy.Builder as LTB import qualified Data.ByteString.Lazy as LBS import Utils (MsgRendererS(..)) import Utils.Lens.TH import Control.Lens hiding (from) import Text.Blaze.Renderer.Utf8 import Data.UUID (UUID) import qualified Data.UUID as UUID import qualified Data.UUID.V4 as UUID import Data.UUID.Cryptographic.ImplicitNamespace import Data.Binary (Binary) import GHC.TypeLits (KnownSymbol) import Network.BSD (getHostName) import Data.Time.Zones (TZ, utcTZ, utcToLocalTimeTZ, timeZoneForUTCTime) import Data.Time.LocalTime (ZonedTime(..)) import Network.HaskellNet.SMTP (SMTPConnection) import qualified Network.HaskellNet.SMTP as SMTP import qualified Text.Hamlet as Hamlet (Translate) import qualified Text.Shakespeare as Shakespeare (RenderUrl) import qualified Data.Aeson as Aeson import qualified Data.Aeson.Encode.Pretty as Aeson import Data.Aeson (Options(..)) import Data.Aeson.TH import Utils.PathPiece (splitCamel) import Utils.DateTime import Data.Universe.Instances.Reverse () import Data.Universe.Instances.Reverse.JSON () import Data.Universe.Instances.Reverse.Hashable () import GHC.Exts (IsList) makeLenses_ ''Mail makeLenses_ ''Part newtype MailT m a = MailT { _unMailT :: RWST MailContext MailSmtpData Mail m a } deriving newtype ( MonadTrans, Monad, Functor, MonadFail, Applicative, Alternative, MonadPlus , MonadIO, MonadHandler, MonadCatch, MonadThrow, MonadMask, MonadResource, MonadBase b , MonadState Mail, MonadWriter MailSmtpData, MonadReader MailContext ) instance {-# OVERLAPPING #-} (MonadCrypto m, MonadCryptoKey m ~ CryptoIDKey) => MonadCrypto (MailT m) where type MonadCryptoKey (MailT m) = CryptoIDKey cryptoIDKey f = lift (cryptoIDKey return) >>= f data MailSmtpData = MailSmtpData { smtpEnvelopeFrom :: Last Text , smtpRecipients :: Set Text } deriving (Eq, Ord, Show, Read, Generic) instance Monoid (MailSmtpData) where mempty = memptydefault mappend = mappenddefault newtype MailLanguages = MailLanguages { mailLanguages :: [Lang] } deriving (Eq, Ord, Show, Read, Generic, Typeable) deriving newtype (FromJSON, ToJSON, IsList) instance Default MailLanguages where def = MailLanguages [] instance Hashable MailLanguages data MailContext = MailContext { mcLanguages :: MailLanguages , mcDateTimeFormat :: SelDateTimeFormat -> DateTimeFormat } deriving (Eq, Ord, Read, Show, Generic, Typeable) deriveJSON defaultOptions { fieldLabelModifier = intercalate "-" . map toLower . drop 1 . splitCamel } ''MailContext instance Hashable MailContext instance Default MailContext where def = MailContext { mcLanguages = def , mcDateTimeFormat = def } makeLenses_ ''MailContext class (MonadHandler m, MonadState Mail m) => MonadMail m where askMailLanguages :: m MailLanguages askMailDateTimeFormat :: SelDateTimeFormat -> m DateTimeFormat tellMailSmtpData :: MailSmtpData -> m () instance MonadHandler m => MonadMail (MailT m) where askMailLanguages = view _mcLanguages askMailDateTimeFormat = (view _mcDateTimeFormat ??) tellMailSmtpData = tell data VerpMode = VerpNone | Verp { verpSeparator, verpAtReplacement :: Char } deriving (Eq, Show, Read, Generic) deriveJSON defaultOptions { constructorTagModifier = intercalate "-" . map toLower . drop 1 . splitCamel , fieldLabelModifier = intercalate "-" . map toLower . drop 1 . splitCamel , sumEncoding = UntaggedValue } ''VerpMode getMailMessageRender :: ( MonadMail m , HandlerSite m ~ site , RenderMessage site msg ) => m (msg -> Text) getMailMessageRender = renderMessage <$> getYesod <*> (mailLanguages <$> askMailLanguages) getMailMsgRenderer :: forall site m. ( MonadMail m , HandlerSite m ~ site ) => m (MsgRendererS site) getMailMsgRenderer = do mr <- getMailMessageRender return $ MsgRenderer (mr . SomeMessage :: RenderMessage site msg => msg -> Text) data MailException = MailNotAvailable | MailNoSenderSpecified | MailNoRecipientsSpecified deriving (Eq, Ord, Read, Show, Generic, Typeable) instance Exception MailException class Yesod site => YesodMail site where defaultFromAddress :: (MonadHandler m, HandlerSite m ~ site) => m Address defaultFromAddress = (Address Nothing . ("yesod@" <>) . pack) <$> liftIO getHostName mailObjectIdDomain :: (MonadHandler m, HandlerSite m ~ site) => m Text mailObjectIdDomain = pack <$> liftIO getHostName mailDateTZ :: (MonadHandler m, HandlerSite m ~ site) => m TZ mailDateTZ = return utcTZ mailSmtp :: ( MonadHandler m , HandlerSite m ~ site , MonadBaseControl IO m ) => (SMTPConnection -> m a) -> m a mailSmtp _ = throwM MailNotAvailable mailVerp :: ( MonadHandler m , HandlerSite m ~ site ) => m VerpMode mailVerp = return VerpNone mailT :: ( MonadHandler m , HandlerSite m ~ site , MonadBaseControl IO m , MonadLogger m ) => MailContext -> MailT m a -> m a mailT = defMailT defaultMailLayout :: ( MonadHandler m , HandlerSite m ~ site ) => WidgetT site IO () -> m Html defaultMailLayout wgt = do PageContent{..} <- liftHandlerT $ widgetToPageContent wgt msgs <- getMessages withUrlRenderer [hamlet| $newline never $doctype 5