{-# LANGUAGE GeneralizedNewtypeDeriving , UndecidableInstances #-} {-# OPTIONS_GHC -fno-warn-redundant-constraints #-} module Mail ( -- * Structured MIME emails module Network.Mail.Mime -- * MailT , MailT, defMailT , MailSmtpData(..), _smtpEnvelopeFrom, _smtpRecipients , _MailSmtpDataSet , MailContext(..) , MonadMail(..) , getMailMessageRender, getMailMsgRenderer -- * YesodMail , YesodMail(..) , MailException(..) -- * Monadically constructing Mail , PrioritisedAlternatives , ToMailPart(..) , addAlternatives, provideAlternative, providePreferredAlternative , addPart, addPart', modifyPart, partIsAttachment , MonadHeader(..) , MailHeader , MailObjectId , replaceMailHeader, addMailHeader, removeMailHeader, getMailHeaders, lookupMailHeader , replaceMailHeaderI, addMailHeaderI , setSubjectI , setMailObjectUUID, setMailObjectIdRandom, setMailObjectIdCrypto, setMailObjectIdPseudorandom , getMailObjectId , setDate, setDateCurrent , getMailSmtpData , _addressName, _addressEmail , _mailFrom, _mailTo, _mailCc, _mailBcc, _mailHeaders, _mailHeader, _mailParts , _partType, _partEncoding, _partDisposition, _partFilename, _partHeaders, _partContent ) where import ClassyPrelude.Yesod hiding (snoc, (.=), getMessageRender, derivePersistFieldJSON) import Data.Kind (Type) import Model.Types.Languages import Model.Types.Csv import Network.Mail.Mime hiding (addPart, addAttachment) import qualified Network.Mail.Mime as Mime (addPart) import Settings.Mime import Data.Monoid (Last(..)) import Control.Monad.Trans.RWS (RWST(..)) import Control.Monad.Trans.State (StateT(..), execStateT, mapStateT) import Control.Monad.Trans.Writer (execWriter, execWriterT, Writer) import Control.Monad.RWS.Class (MonadWriter(..), MonadState(..), modify) import Control.Monad.Fail import Control.Monad.Base import Control.Monad.Catch import Generics.Deriving.Monoid (memptydefault, mappenddefault) import qualified Data.Sequence as Seq import qualified Data.Set as Set import qualified Data.Foldable as Foldable import qualified Data.Text as Text import qualified Data.Text.Lazy as LT import qualified Data.Text.Lazy.Builder as LTB import qualified Data.ByteString.Lazy as LBS import qualified Data.ByteString as BS import Utils (MsgRendererS(..), MonadSecretBox(..), YamlValue) import Utils.Lens.TH import Control.Lens hiding (from) import Control.Lens.Extras (is) import Text.Blaze.Renderer.Utf8 import Data.UUID (UUID) import qualified Data.UUID as UUID import Data.UUID.Cryptographic.ImplicitNamespace import Data.Binary (Binary) import qualified Data.Binary as Binary import "network-bsd" Network.BSD (getHostName) import Data.Time.Zones (utcTZ, utcToLocalTimeTZ, timeZoneForUTCTime) import Data.Time.LocalTime (ZonedTime(..), TimeZone(..)) import Data.Time.Format (rfc822DateFormat) 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 qualified Data.Yaml as Yaml 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 Data.CaseInsensitive (CI) import qualified Data.CaseInsensitive as CI import Control.Monad.Random (MonadRandom(..)) import qualified Crypto.Saltine.Class as Saltine (IsEncoding(..)) import qualified Data.ByteArray as ByteArray (convert) import Crypto.MAC.KMAC (KMAC) import qualified Crypto.MAC.KMAC as KMAC import Crypto.Hash.Algorithms (SHAKE128) import Language.Haskell.TH (nameBase) import Network.Mail.Mime.Instances() makeLenses_ ''Address makeLenses_ ''Mail makeLenses_ ''Part _partFilename :: Traversal' Part Text _partFilename = _partDisposition . dispositionFilename where dispositionFilename :: Traversal' Disposition Text dispositionFilename f (AttachmentDisposition t) = AttachmentDisposition <$> f t dispositionFilename f (InlineDisposition t) = InlineDisposition <$> f t dispositionFilename _ DefaultDisposition = pure DefaultDisposition _mailHeader :: CI ByteString -> Traversal' Mail Text _mailHeader hdrName = _mailHeaders . traverse . filtered (views _1 $ (== hdrName) . CI.mk) . _2 newtype MailT m a = MailT { _unMailT :: RWST MailContext MailSmtpData Mail m a } deriving newtype ( MonadTrans, Monad, Functor, MonadFail, Applicative, Alternative, MonadPlus , MonadIO, MonadLogger, 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 Address } deriving (Eq, Ord, Show, Read, Generic) instance Semigroup MailSmtpData where (<>) = mappenddefault instance Monoid MailSmtpData where mempty = memptydefault mappend = (<>) _MailSmtpDataSet :: Getter MailSmtpData Bool _MailSmtpDataSet = to $ \MailSmtpData{..} -> none id [ is (_Wrapped . _Nothing) smtpEnvelopeFrom , Set.null smtpRecipients ] data MailContext = MailContext { mcLanguages :: Languages , mcDateTimeFormat :: SelDateTimeFormat -> DateTimeFormat , mcCsvOptions :: CsvOptions } deriving (Eq, Ord, Read, Show, Generic, Typeable) deriveJSON defaultOptions { fieldLabelModifier = intercalate "-" . map toLower . drop 1 . splitCamel } ''MailContext instance Hashable MailContext instance NFData MailContext instance Default MailContext where def = MailContext { mcLanguages = def , mcDateTimeFormat = def , mcCsvOptions = def } makeLenses_ ''MailContext makeLenses_ ''MailSmtpData class (MonadHandler m, MonadState Mail m) => MonadMail m where askMailLanguages :: m Languages askMailDateTimeFormat :: SelDateTimeFormat -> m DateTimeFormat askMailCsvOptions :: m CsvOptions tellMailSmtpData :: MailSmtpData -> m () instance MonadHandler m => MonadMail (MailT m) where askMailLanguages = view _mcLanguages askMailDateTimeFormat = (view _mcDateTimeFormat ??) askMailCsvOptions = view _mcCsvOptions tellMailSmtpData = tell getMailMessageRender :: ( MonadMail m , HandlerSite m ~ site , RenderMessage site msg ) => m (msg -> Text) getMailMessageRender = renderMessage <$> getYesod <*> (view _Wrapped <$> 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 envelopeFromAddress :: (MonadHandler m, HandlerSite m ~ site) => m Text envelopeFromAddress = addressEmail <$> defaultFromAddress 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 , MonadUnliftIO m , MonadThrow m ) => (SMTPConnection -> m a) -> m a mailSmtp _ = throwM MailNotAvailable mailT :: ( MonadHandler m , HandlerSite m ~ site , MonadUnliftIO m , MonadLogger m , MonadThrow m ) => MailContext -> MailT m a -> m a default mailT :: ( MonadHandler m , HandlerSite m ~ site , MonadUnliftIO m , MonadThrow m ) => MailContext -> MailT m a -> m a mailT = defMailT defaultMailLayout :: ( MonadHandler m , HandlerSite m ~ site ) => WidgetFor site () -> m Html defaultMailLayout wgt = do PageContent{..} <- liftHandler $ widgetToPageContent wgt msgs <- getMessages withUrlRenderer [hamlet| $newline never $doctype 5