{-# LANGUAGE GeneralizedNewtypeDeriving , UndecidableInstances #-} {-# OPTIONS_GHC -fno-warn-redundant-constraints #-} module Mail ( -- * Structured MIME emails module Network.Mail.Mime -- * MailT , MailT, defMailT , MailSmtpData(..) , _MailSmtpDataSet , MailContext(..) , MonadMail(..) , getMailMessageRender, getMailMsgRenderer -- * YesodMail , VerpMode(..) , YesodMail(..) , MailException(..) -- * Monadically constructing Mail , PrioritisedAlternatives , ToMailPart(..) , addAlternatives, provideAlternative, providePreferredAlternative , addPart, addPart', modifyPart, partIsAttachment , MonadHeader(..) , MailHeader , MailObjectId , replaceMailHeader, addMailHeader, removeMailHeader , replaceMailHeaderI, addMailHeaderI , setSubjectI , setMailObjectUUID, setMailObjectIdRandom, setMailObjectIdCrypto, setMailObjectIdPseudorandom , setDate, setDateCurrent , setMailSmtpData , _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 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, 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.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 qualified Data.ByteString as BS import Utils (MsgRendererS(..), MonadSecretBox(..), maybeT, 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 Control.Monad.Trans.Maybe (MaybeT(..)) 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) 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 Text } 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 } 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 } makeLenses_ ''MailContext class (MonadHandler m, MonadState Mail m) => MonadMail m where askMailLanguages :: m Languages 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 <*> (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 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 mailVerp :: ( MonadHandler m , HandlerSite m ~ site ) => m VerpMode mailVerp = return VerpNone 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