{-# LANGUAGE OverloadedStrings #-} {- | 'Text' versions of Smtp operations. This module does not validate that your text satisfies the requirements of the relevant RFCs -- see the note in "Network.Mail.Assumpta.MonadSmtp#permissiblecharacters" about "permissible characters". In general, unless you are using an SMTP extension, your text must consist of 7-bit clean ASCII. == Example code Short example (which will work if you have an SMTP server running on local port 2025): (see the directory for a copy of the code): > {-# LANGUAGE OverloadedStrings #-} > > -- Program that runs a short SMTP session > -- with a server running on local port 2025. > > module Main where > > import Data.Monoid > import qualified Data.Text as T > import Network.BSD (getHostName) -- requires 'network' package > import Network.Mail.Assumpta.Text > > sampleMesg :: T.Text > sampleMesg = T.intercalate crlf [ > "Date: Tue, 21 Jan 2020 02:28:37 +0800" > , "To: neddie.seagoon@gmail.com" > , "From: jim.moriarty@gmail.com" > , "Subject: test Tue, 21 Jan 2020 02:28:37 +0800" > , "" > , "Sapristi nyuckoes!" > ] > > -- The sender and recipient supplied to the > -- SMTP server are what is used to route the > -- message; any 'To:' and 'From:' fields > -- in the message are completely ignored for this > -- purpose. > sender, recipient :: T.Text > sender = "somesender@mycorp.com" > recipient = "somerecipient@mozilla.org" > > main :: IO () > main = do > let > port = 2025 > hostname <- getHostName > print =<< runSmtp "localhost" port (do > expectGreeting > ehlo $ T.pack hostname > -- Properly, we should escape periods at the start of a > -- line. But we know there aren't any > sendRawMail sender [recipient] sampleMesg > quit) == Alternatives to "connection" If you want to use other network libraries besides , it should be pretty straightforward to adapt the code here. If you want to use stream-like IO, one possibility is the package, which provides Conduit-based sources and sinks on top of "Network.Connection". -} module Network.Mail.Assumpta.Text ( -- * Run Smtp actions -- | Run actions in the 'MonadSmtp' monad. ABS.runSmtp , ABS.runSecureSmtp , ABS.runSmtpWithParams , ABS.runSmtpHandle -- * MonadError variants -- | Instead of returning an Either, these run in MonadError -- -- thus the caller can specialize them to a 'Maybe', 'Either', -- or some other 'MonadError' instance as desired. , ABS.runSmtp' , ABS.runSecureSmtp' , ABS.runSmtpWithParams' , ABS.runSmtpHandle' -- * SMTP commands -- | Functions for sending commands -- to an SMTP server. In general, these are wrappers around -- 'command' and 'expect' -- they send some command, -- and try to parse a response (throwing an 'SmtpError' -- on failure). -- See the "Network.Mail.Assumpta.ByteString" module for -- documentation of each function. , helo , ehlo , mailFrom , rcptTo , data_ , ABS.noop , ABS.quit , ABS.rset , ABS.startTLS , expn , vrfy , help , sendRawMail , simpleMail -- * Server responses , ABS.expect , ABS.expectGreeting -- * Low-level operations and types , ABS.command , ABS.Reply , ABS.ReplyLine(..) , ABS.ReplyCode , ABS.SmtpError(..) -- * Monad transformer -- | A monad transformer, 'SmtpT', which provides the ability to -- send SMTP commands and parse replies, together with operations -- on the transformer. , ABS.SmtpT , ABS.Smtp , ABS.liftSmtpT , ABS.mapSmtpT -- * Network operations , ABS.open , ABS.openTls , ABS.openParams , ABS.close , ABS.withHandle , ABS.withSecureHandle , ABS.withHandleParams , ABS.HostName , ABS.Port , ABS.Handle(..) , ABS.defaultTLSSettings , send , sendLine -- * Utility functions , toCrLf , ABS.rethrow , ABS.mkParams , ABS.toIOError , ABS.crlf , escapePeriods ) where import Data.ByteString.Lazy (toStrict) import Data.Foldable (toList) import Data.Monoid -- needed for early versions of Base import qualified Data.Text as T import Data.Text ( Text ) import Data.Text.Encoding ( encodeUtf8, decodeUtf8 ) import Data.Text.Lazy (fromStrict) import qualified Network.Mail.Mime as MM ( Address(..), simpleMail , renderMail' ) import qualified Network.Mail.Assumpta.ByteString as ABS import Network.Mail.Assumpta.MonadSmtp ( MonadSmtp ) import qualified Network.Mail.Assumpta.MonadSmtp as M -- | See 'M.helo' helo :: MonadSmtp m => Text -> m () helo = M.helo . encodeUtf8 -- | See 'M.ehlo' ehlo :: MonadSmtp m => Text -> m () ehlo = M.ehlo . encodeUtf8 -- | See 'M.mailFrom' mailFrom :: MonadSmtp m => Text -> m () mailFrom = M.mailFrom . encodeUtf8 -- | See 'M.rcptTo' rcptTo :: MonadSmtp m => Text -> m () rcptTo = M.rcptTo . encodeUtf8 -- | See 'M.data_' data_ :: MonadSmtp m => Text -> m () data_ = M.data_ . encodeUtf8 -- | See 'M.expn' expn :: MonadSmtp m => Text -> m M.Reply expn = M.expn . encodeUtf8 -- | See 'M.vrfy' vrfy :: MonadSmtp m => Text -> m M.Reply vrfy = M.vrfy . encodeUtf8 -- | See 'M.help' help :: MonadSmtp m => Maybe Text -> m M.Reply help = M.help . fmap encodeUtf8 -- | See 'M.send' send :: MonadSmtp m => Text -> m () send = M.send . encodeUtf8 -- | See 'M.sendLine' sendLine :: MonadSmtp m => Text -> m () sendLine = M.sendLine . encodeUtf8 -- | replace newlines (@\'\\n'@) with crlf sequence (@\'\\r\\n'@). toCrLf :: Text -> Text toCrLf txt = T.intercalate M.crlf $ T.lines txt -- | Where a period ('.') character starts a (crlf-delimited) -- line, replace it with two periods. escapePeriods :: Text -> Text escapePeriods txt = T.concat $ map escapeLine $ T.splitOn M.crlf txt where escapeLine t = if "." `T.isPrefixOf` t then "." <> t else t -- | see 'ABS.sendRawMail'. sendRawMail :: (MonadSmtp m, Foldable t) => Text -> t Text -> Text -> m () sendRawMail sender recipients message = let sender' = encodeUtf8 sender recipients' = map encodeUtf8 (toList recipients) message' = encodeUtf8 message in ABS.sendRawMail sender' recipients' message' -- | A simple interface for generating an email with HTML and plain-text -- alternatives and some file attachments and sending it via an -- SMTP server. -- -- Uses lazy IO for reading in the attachment contents. Simple wrapper -- around 'MM.renderMail''. /Caution/: Not tested, use with care. -- Likely to change. -- -- sample use: -- -- @ -- > import qualified Network.Mail.Mime as MM -- > :set -XOverloadedStrings -- > :{ -- let addr = MM.Address (Just "joe") "joe@nowhere" -- subj = "a test subject" -- body = "a test body" -- > :} -- > simpleMail addr addr subj body "" [] "myserver.mydomain.com" "mail.yourserver.org" 2025 -- @ {-# WARNING simpleMail "experimental, likely to change" #-} simpleMail :: MM.Address -- ^ to -> MM.Address -- ^ from -> T.Text -- ^ subject -> T.Text -- ^ plain body -> T.Text -- ^ HTML body -> [(T.Text, FilePath)] -- ^ content type and path of attachments -> String -- ^ qualified name of local host -> String -- ^ SMTP server to connect to -> Int -- ^ Port of SMTP server -> IO (T.Text, Either M.SmtpError ()) simpleMail to from subject plainBody htmlBody attachments localHost server port = do let plainBody' = fromStrict plainBody htmlBody' = fromStrict htmlBody mail <- escape . toStrict <$> (MM.renderMail' =<< MM.simpleMail to from subject plainBody' htmlBody' attachments) let mail' = if crlf `T.isSuffixOf` mail then mail <> "." <> crlf else mail <> crlf <> "." <> crlf to' = MM.addressEmail to from' = MM.addressEmail from res <- ABS.runSmtp server port $ do ABS.expectGreeting ehlo $ T.pack localHost sendRawMail from' [to'] mail' ABS.quit return (mail, res) where escape = escapePeriods . toCrLf . decodeUtf8 crlf = ABS.crlf ----testX :: IO (Either SmtpError ()) --testX = -- let addr = MM.Address (Just "joe") "joe@place" -- subj = "a test subj" -- body = "a test body" -- in simpleMail addr addr subj body "" [] "myserver.lan" "localhost" 2025