packages feed

SMTPClient 0.2 → 0.3

raw patch · 4 files changed

+180/−113 lines, 4 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Network.SMTP.Client: sendSMTP_ :: (String -> IO ()) -> Maybe (IORef Int) -> String -> SockAddr -> [Message] -> IO ()
- Network.SMTP.ClientSession: smtpSent :: SMTPState -> Int
+ Network.SMTP.Client: Bcc :: [NameAddr] -> Field
+ Network.SMTP.Client: Cc :: [NameAddr] -> Field
+ Network.SMTP.Client: Code :: SuccessCode -> Category -> Int -> SmtpCode
+ Network.SMTP.Client: Comments :: String -> Field
+ Network.SMTP.Client: Connection :: Category
+ Network.SMTP.Client: Date :: CalendarTime -> Field
+ Network.SMTP.Client: From :: [NameAddr] -> Field
+ Network.SMTP.Client: InReplyTo :: [String] -> Field
+ Network.SMTP.Client: Information :: Category
+ Network.SMTP.Client: IntermediateSuccess :: SuccessCode
+ Network.SMTP.Client: Keywords :: [[String]] -> Field
+ Network.SMTP.Client: MailSystem :: Category
+ Network.SMTP.Client: Message :: [Field] -> String -> Message
+ Network.SMTP.Client: MessageID :: String -> Field
+ Network.SMTP.Client: NameAddr :: Maybe String -> String -> NameAddr
+ Network.SMTP.Client: ObsReceived :: [(String, String)] -> Field
+ Network.SMTP.Client: OptionalField :: String -> String -> Field
+ Network.SMTP.Client: PermanentFailure :: SuccessCode
+ Network.SMTP.Client: PreliminarySuccess :: SuccessCode
+ Network.SMTP.Client: Received :: ([(String, String)], CalendarTime) -> Field
+ Network.SMTP.Client: References :: [String] -> Field
+ Network.SMTP.Client: Reply :: SmtpCode -> [String] -> SmtpReply
+ Network.SMTP.Client: ReplyTo :: [NameAddr] -> Field
+ Network.SMTP.Client: ResentBcc :: [NameAddr] -> Field
+ Network.SMTP.Client: ResentCc :: [NameAddr] -> Field
+ Network.SMTP.Client: ResentDate :: CalendarTime -> Field
+ Network.SMTP.Client: ResentFrom :: [NameAddr] -> Field
+ Network.SMTP.Client: ResentMessageID :: String -> Field
+ Network.SMTP.Client: ResentReplyTo :: [NameAddr] -> Field
+ Network.SMTP.Client: ResentSender :: NameAddr -> Field
+ Network.SMTP.Client: ResentTo :: [NameAddr] -> Field
+ Network.SMTP.Client: ReturnPath :: String -> Field
+ Network.SMTP.Client: Sender :: NameAddr -> Field
+ Network.SMTP.Client: Subject :: String -> Field
+ Network.SMTP.Client: Success :: SuccessCode
+ Network.SMTP.Client: Syntax :: Category
+ Network.SMTP.Client: To :: [NameAddr] -> Field
+ Network.SMTP.Client: TransientFailure :: SuccessCode
+ Network.SMTP.Client: Unspecified3 :: Category
+ Network.SMTP.Client: Unspecified4 :: Category
+ Network.SMTP.Client: Unused0 :: SuccessCode
+ Network.SMTP.Client: data Category :: *
+ Network.SMTP.Client: data Field :: *
+ Network.SMTP.Client: data Message :: *
+ Network.SMTP.Client: data NameAddr :: *
+ Network.SMTP.Client: data SmtpCode :: *
+ Network.SMTP.Client: data SmtpReply :: *
+ Network.SMTP.Client: data SuccessCode :: *
+ Network.SMTP.Client: nameAddr_addr :: NameAddr -> String
+ Network.SMTP.Client: nameAddr_name :: NameAddr -> Maybe String
+ Network.SMTP.Client: sendSMTP' :: (String -> IO ()) -> Maybe (IORef [Maybe SmtpReply]) -> String -> SockAddr -> [Message] -> IO ()
+ Network.SMTP.ClientSession: Bcc :: [NameAddr] -> Field
+ Network.SMTP.ClientSession: Cc :: [NameAddr] -> Field
+ Network.SMTP.ClientSession: Code :: SuccessCode -> Category -> Int -> SmtpCode
+ Network.SMTP.ClientSession: Comments :: String -> Field
+ Network.SMTP.ClientSession: Connection :: Category
+ Network.SMTP.ClientSession: Date :: CalendarTime -> Field
+ Network.SMTP.ClientSession: From :: [NameAddr] -> Field
+ Network.SMTP.ClientSession: InReplyTo :: [String] -> Field
+ Network.SMTP.ClientSession: Information :: Category
+ Network.SMTP.ClientSession: IntermediateSuccess :: SuccessCode
+ Network.SMTP.ClientSession: Keywords :: [[String]] -> Field
+ Network.SMTP.ClientSession: MailSystem :: Category
+ Network.SMTP.ClientSession: Message :: [Field] -> String -> Message
+ Network.SMTP.ClientSession: MessageID :: String -> Field
+ Network.SMTP.ClientSession: NameAddr :: Maybe String -> String -> NameAddr
+ Network.SMTP.ClientSession: ObsReceived :: [(String, String)] -> Field
+ Network.SMTP.ClientSession: OptionalField :: String -> String -> Field
+ Network.SMTP.ClientSession: PermanentFailure :: SuccessCode
+ Network.SMTP.ClientSession: PreliminarySuccess :: SuccessCode
+ Network.SMTP.ClientSession: Received :: ([(String, String)], CalendarTime) -> Field
+ Network.SMTP.ClientSession: References :: [String] -> Field
+ Network.SMTP.ClientSession: Reply :: SmtpCode -> [String] -> SmtpReply
+ Network.SMTP.ClientSession: ReplyTo :: [NameAddr] -> Field
+ Network.SMTP.ClientSession: ResentBcc :: [NameAddr] -> Field
+ Network.SMTP.ClientSession: ResentCc :: [NameAddr] -> Field
+ Network.SMTP.ClientSession: ResentDate :: CalendarTime -> Field
+ Network.SMTP.ClientSession: ResentFrom :: [NameAddr] -> Field
+ Network.SMTP.ClientSession: ResentMessageID :: String -> Field
+ Network.SMTP.ClientSession: ResentReplyTo :: [NameAddr] -> Field
+ Network.SMTP.ClientSession: ResentSender :: NameAddr -> Field
+ Network.SMTP.ClientSession: ResentTo :: [NameAddr] -> Field
+ Network.SMTP.ClientSession: ReturnPath :: String -> Field
+ Network.SMTP.ClientSession: Sender :: NameAddr -> Field
+ Network.SMTP.ClientSession: Subject :: String -> Field
+ Network.SMTP.ClientSession: Success :: SuccessCode
+ Network.SMTP.ClientSession: Syntax :: Category
+ Network.SMTP.ClientSession: To :: [NameAddr] -> Field
+ Network.SMTP.ClientSession: TransientFailure :: SuccessCode
+ Network.SMTP.ClientSession: Unspecified3 :: Category
+ Network.SMTP.ClientSession: Unspecified4 :: Category
+ Network.SMTP.ClientSession: Unused0 :: SuccessCode
+ Network.SMTP.ClientSession: data Category :: *
+ Network.SMTP.ClientSession: data Field :: *
+ Network.SMTP.ClientSession: data Message :: *
+ Network.SMTP.ClientSession: data NameAddr :: *
+ Network.SMTP.ClientSession: data SmtpCode :: *
+ Network.SMTP.ClientSession: data SmtpReply :: *
+ Network.SMTP.ClientSession: data SuccessCode :: *
+ Network.SMTP.ClientSession: nameAddr_addr :: NameAddr -> String
+ Network.SMTP.ClientSession: nameAddr_name :: NameAddr -> Maybe String
+ Network.SMTP.ClientSession: smtpStatuses :: SMTPState -> [Maybe SmtpReply]
- Network.SMTP.Client: sendSMTP :: String -> SockAddr -> [Message] -> IO ()
+ Network.SMTP.Client: sendSMTP :: Maybe (IORef [Maybe SmtpReply]) -> String -> SockAddr -> [Message] -> IO ()
- Network.SMTP.ClientSession: SMTPState :: [String] -> (String -> SMTPState -> SMTPState) -> Bool -> Maybe String -> Int -> SMTPState
+ Network.SMTP.ClientSession: SMTPState :: [String] -> (String -> SMTPState -> SMTPState) -> Bool -> Maybe String -> [Maybe SmtpReply] -> SMTPState

Files

Network/SMTP/Client.hs view
@@ -1,13 +1,28 @@ {-# LANGUAGE DeriveDataTypeable #-} +-- | An SMTP client in the IO Monad.+ module Network.SMTP.Client (         sendSMTP,-        sendSMTP_,-        SMTPException(..)+        sendSMTP',+        SMTPException(..),+        SmtpReply(..),+        SmtpCode(..),+        SuccessCode(..),+        Category(..),+        Message(..),+        Field(..),+        NameAddr(..)     ) where  import Network.SMTP.ClientSession import Control.Exception+import Text.ParserCombinators.Parsec.Rfc2821 (+        SmtpReply(..),+        SmtpCode(..),+        SuccessCode(..),+        Category(..)+    ) import Text.ParserCombinators.Parsec.Rfc2822 (         Message(..),         Field(..),@@ -23,26 +38,32 @@  -- | Send a list of email messages to an SMTP server. Throws SMTPException on -- failure at the communication protocol level.--- The message body may contain either \"\n\" or \"\\r\\n\" for an end-of-line--- marker and in all cases it will be sent correctly to the server.-sendSMTP :: String      -- ^ Domain name for EHLO command+--+-- The optional IORef is used to store a list of statuses for messages sent so+-- far, where Nothing means success.  The list elements correspond to the elements+-- of the input message list.  If the caller catches an exception, this list is+-- likely to be shorter than the input message list, and so it gives an indication+-- of how many messages were dispatched.+--+-- The message body may use either \"\\n\" or \"\\r\\n\" for an end-of-line+-- marker and in either case it will be sent correctly to the server.+sendSMTP ::+            Maybe (IORef [Maybe SmtpReply]) -- ^ For storing failure statuses of messages sent so far+         -> String      -- ^ Domain name for EHLO command          -> SockAddr    -- ^ Network address of SMTP server          -> [Message]   -- ^ List of messages to send          -> IO ()-sendSMTP = sendSMTP_ (\_ -> return ()) Nothing+sendSMTP = sendSMTP' (\_ -> return ()) --- | Like sendSMTP_ but takes an additional function for logging all input and--- output for diagnostic purposes.  Also an optional IORef for storing the--- number of emails sent so far.  The emails are sent strictly in order, so this--- count can be used when an exception is caught to mark sent emails.-sendSMTP_ :: (String -> IO ())  -- ^ Diagnostic log function-          -> Maybe (IORef Int)  -- ^ Optional IORef for storing the number of-                                -- emails successfully sent so far.+-- | Like sendSMTP but takes an additional function for logging all input and+-- output for diagnostic purposes.+sendSMTP' :: (String -> IO ())  -- ^ Diagnostic log function+          -> Maybe (IORef [Maybe SmtpReply]) -- ^ For storing failure statuses of messages sent so far           -> String             -- ^ Domain name for EHLO command           -> SockAddr           -- ^ Network address of SMTP server           -> [Message]          -- ^ List of messages to send           -> IO ()-sendSMTP_ log mSentSoFar domain sockAddr messages = do+sendSMTP' log mStatuses domain sockAddr messages = do         sock <- socket AF_INET Stream defaultProtocol         connect sock sockAddr         handle <- socketToHandle sock ReadWriteMode@@ -54,17 +75,17 @@                  process :: Handle -> SMTPState -> IO ()         process h state = do-        -            case mSentSoFar of-                Just sentSoFar -> writeIORef sentSoFar (smtpSent state)++            case mStatuses of+                Just statuses -> writeIORef statuses (smtpStatuses state)                 Nothing -> return ()-        -            forM_ (reverse $ smtpOutQueue state) $ \ line -> do++            forM_ (smtpOutQueue state) $ \line -> do                 log $ "-> "++line                 hPutStr h line                 hPutStr h "\r\n"                 hFlush h-        +             case (smtpSuccess state, smtpFailure state) of                 (True, _) -> do                     log "SUCCEEDED"@@ -73,11 +94,11 @@                 otherwise -> do                     -- Strip trailing \r. hGetLine has already stripped \n for us.                     reply <- reverse . dropWhile (=='\r') . reverse <$> hGetLine h-                    putStrLn $ "<- "++reply+                    log $ "<- "++reply                     let state' = smtpReceive state reply $ state {smtpOutQueue = []}                     process h state' -+-- | An exception indicating a communications failure at the level of the SMTP protocol. data SMTPException = SMTPException String     deriving (Eq, Show, Typeable) 
Network/SMTP/ClientSession.hs view
@@ -1,6 +1,15 @@+-- | A pure SMTP client state machine.+ module Network.SMTP.ClientSession (         smtpClientSession,-        SMTPState(..)+        SMTPState(..),+        SmtpReply(..),+        SmtpCode(..),+        SuccessCode(..),+        Category(..),+        Message(..),+        Field(..),+        NameAddr(..)         {-, suite-}     ) where @@ -18,7 +27,7 @@     ) import Data.Maybe import qualified Data.Set as Set-import Prelude hiding (fail, return)+import Prelude hiding (fail) import Data.List import System.Time import System.Locale@@ -35,33 +44,36 @@  data SMTPState = SMTPState { -        -- | Caller must output any lines queued up in this list, making sure to-        -- clear them in the state passed back to smtpReceive.  These lines are in-        -- reverse order, so the caller must reverse them before processing.-        -- They do not have end-of-line characters, so the caller must add \"\\r\\n\"-        -- on the end (as required by RFC2821 - not just \"\\n\").+        -- | Step 1. Caller must output any lines queued up in this list.  They do not+        -- have end-of-line characters, so the caller must add \"\\r\\n\" on the+        -- end (as required by RFC2821 - not just \"\\n\").         smtpOutQueue :: [String], -        -- | When there is nothing to send, the caller should wait for a line from-        -- the SMTP server, strip any end-of-line characters, and pass it to this-        -- function for processing.+        -- | Step 2. When sends are completed, the caller should wait for a line from+        -- the SMTP server, strip the \"\\n\" end-of-line characters, and pass the+        -- line to this function for processing.         smtpReceive  :: String -> SMTPState -> SMTPState, -        -- | When True, this flag indicates that the SMTP session has completed-        -- successfully and there is no more work to do.+        -- | Step 3. Check if this is True, which indicates that the SMTP session+        -- has completed successfully and there is no more work to do.         smtpSuccess  :: Bool, -        -- | When Just err, this indicates that a protocol error has occurred, and-        -- that the caller must terminate the session.+        -- | Step 4. Check if this is Just err, which indicates that a protocol error+        -- has occurred, and that the caller must terminate the session.         smtpFailure  :: Maybe String, -        -- | The number of emails successfully sent so far.-        smtpSent     :: Int+        -- | A list containing a failure status for each message that has been sent so far,+        -- where each element corresponds to one in the list of messages.+        -- If the SMTP session fails part-way through, this list is likely to be+        -- shorter than the input messages list.+        -- "Nothing" means success, and "Just x" is a failure status returned by+        -- the SMTP server.+        smtpStatuses :: [Maybe SmtpReply]     }  send :: String -> (SMTPState -> SMTPState) -> SMTPState -> SMTPState-send txt return state =-    return $+send txt cont state =+    cont $         state {                 smtpOutQueue = txt:smtpOutQueue state             }@@ -72,23 +84,41 @@ receive :: (String -> SMTPState -> SMTPState) -> SMTPState -> SMTPState receive cont state =     state {-            smtpReceive = \line state -> cont line $ state {smtpReceive = nullReceive}+            -- Reverse the out queue before passing it to the caller because we+            -- have been assembling it backwards.+            smtpOutQueue = reverse $ smtpOutQueue state,+            smtpReceive = \line state -> cont line $ state {+                    smtpOutQueue = [],+                    smtpReceive = nullReceive+                }         }  fail :: String -> SMTPState -> SMTPState fail errorText state =     state {+            smtpOutQueue = [],             smtpFailure = Just errorText         }  success :: SMTPState -> SMTPState-success state = state {smtpSuccess = True}+success state =+    state {+            smtpOutQueue = [],+            smtpSuccess = True+        } -check :: SuccessCode -> Category -> String -> SmtpReply -> (SMTPState -> SMTPState) -> SMTPState -> SMTPState-check code cat descr reply return =+equals :: SuccessCode -> Category -> SmtpReply -> Bool+equals code cat reply =     case reply of-        Reply (Code gotCode gotCat _) _ | gotCode == code && gotCat == cat -> return-        _ -> fail $ "SMTP error: got "+++        Reply (Code gotCode gotCat _) _ | gotCode == code && gotCat == cat -> True+        otherwise -> False++check :: (SmtpReply -> Bool) -> String -> SmtpReply -> (SMTPState -> SMTPState) -> SMTPState -> SMTPState+check cond descr reply cont =+    if cond reply+        then cont+        else+            fail $ "SMTP error: got "++                     (cleanUp $ show reply)++                     " when I expected "++descr @@ -99,71 +129,75 @@     dropWhile (\x -> x == '\n') .     reverse .     filter (/= '\r')- + maybeRead :: Read a => String -> Maybe a maybeRead s = case reads s of     [(x, "")] -> Just x     _         -> Nothing -for :: [a] -> (a -> (SMTPState -> SMTPState) -> SMTPState -> SMTPState) -> (SMTPState -> SMTPState) -> SMTPState -> SMTPState-for [] _ return = return-for (x:xs) code return =-    code x $-    for xs code return+for :: [a]+       -- loop body, passed: value next+    -> (a -> (SMTPState -> SMTPState) -> SMTPState -> SMTPState)+    -> (SMTPState -> SMTPState)+    -> SMTPState -> SMTPState+for [] _ cont = cont+for (x:xs) body cont =+    body x $+    for xs body cont -incSent :: (SMTPState -> SMTPState) -> SMTPState -> SMTPState-incSent return state = return $ state {smtpSent = smtpSent state + 1}+putStatuses :: [Maybe SmtpReply] -> (SMTPState -> SMTPState) -> SMTPState -> SMTPState+putStatuses statuses cont state = cont $ state {smtpStatuses = statuses}  -- | Receive an SMTP reply, e.g. -- 250-worked -- 250-like -- 250 a charm receiveReply :: (SmtpReply -> SMTPState -> SMTPState) -> SMTPState -> SMTPState-receiveReply return =+receiveReply cont =         rec [] $ \revMsgs@(final:_) ->         if length(final) < 4 || final !! 3 /= ' ' then             fail ("malformed SMTP reply: "++final)         else             case maybeRead (take 3 final) of                 Just code ->-                    return $ mkReply code (reverse $ map (drop 4) revMsgs)+                    cont $ mkReply code (reverse $ map (drop 4) revMsgs)                 Nothing ->                     fail ("Malformed SMTP reply: "++final)     where         rec :: [String] -> ([String] -> SMTPState -> SMTPState) -> SMTPState -> SMTPState-        rec msgs return =+        rec msgs cont =             receive $ \line ->             if length(line) < 4 then                 fail ("malformed SMTP reply: "++line)             else if line !! 3 == '-' then-                rec (line:msgs) return+                rec (line:msgs) cont             else-                return (line:msgs)+                cont (line:msgs)          mkReply :: Int -> [String] -> SmtpReply         mkReply code msgs = reply (code `div` 100) ((code `div` 10) `mod` 10) (code `mod` 10) msgs +equalsMailOK :: SmtpReply -> Bool+equalsMailOK = equals Success MailSystem+ checkMailOK :: (SMTPState -> SMTPState) -> SMTPState -> SMTPState-checkMailOK return =+checkMailOK cont =     receiveReply $ \reply ->-    check Success MailSystem "\"mail system OK\" (code 25x)" reply $-    return+    check equalsMailOK "\"mail system OK\" (code 25x)" reply $+    cont  checkConnectionOK :: (SMTPState -> SMTPState) -> SMTPState -> SMTPState-checkConnectionOK return =+checkConnectionOK cont =     receiveReply $ \reply ->-    check Success Connection "\"connection OK\" (code 22x)" reply $-    return+    check (equals Success Connection) "\"connection OK\" (code 22x)" reply $+    cont -checkDataOK :: (SMTPState -> SMTPState) -> SMTPState -> SMTPState-checkDataOK return =-    receiveReply $ \reply ->-    check IntermediateSuccess MailSystem "\"connection OK\" (code 35x)" reply $-    return+equalsDataOK :: SmtpReply -> Bool+equalsDataOK = equals IntermediateSuccess MailSystem --- | Pure state machine for an SMTP client session.  Caller must handle I/O.--- The message body may contain either \"\n\" or \"\\r\\n\" for an end-of-line--- marker. All are stripped before passing to caller for dispatch.+-- | Construct a pure state machine for an SMTP client session.  Caller must+-- handle I/O.  The message body may use either \"\\n\" or \"\\r\\n\" for an+-- end-of-line marker. smtpClientSession :: String     -- ^ Domain name used in EHLO command                   -> [Message]  -- ^ List of messges to send                   -> SMTPState@@ -172,7 +206,7 @@             smtpReceive  = nullReceive,             smtpSuccess  = False,             smtpFailure  = Nothing,-            smtpSent     = 0+            smtpStatuses = []         }  -- Continuation passing style.@@ -181,20 +215,21 @@         checkConnectionOK $         send ("EHLO "++domain) $         checkMailOK $-        sendMessages messages $+        sendMessages messages [] $         send "QUIT" $         checkConnectionOK $         success     where-        sendMessages :: [Message] -> (SMTPState -> SMTPState) -> SMTPState -> SMTPState-        sendMessages messages return =-            for messages (\message cont ->-                    sendMessage message cont-                ) $-            return+        sendMessages :: [Message] -> [Maybe SmtpReply] -> (SMTPState -> SMTPState) -> SMTPState -> SMTPState+        sendMessages [] _ cont = cont+        sendMessages (message:rest) statuses cont =+            sendMessage message $ \status ->+            let statuses' = status:statuses in   -- collate statuses backwards+            putStatuses (reverse statuses') $    -- reverse to write smtpStatuses in correct order+            sendMessages rest statuses' cont -        sendMessage :: Message -> (SMTPState -> SMTPState) -> SMTPState -> SMTPState-        sendMessage message return =+        sendMessage :: Message -> (Maybe SmtpReply -> SMTPState -> SMTPState) -> SMTPState -> SMTPState+        sendMessage message cont =             let Message fields _ = message                  froms = map (\(NameAddr _ addr) -> addr) $@@ -217,23 +252,35 @@                 fail "email contains to To: field"             else (                     send ((("MAIL FROM:"++) . angle_addr (head froms)) "") $-                    checkMailOK $-                    for tos (\to return ->-                            send ((("RCPT TO:"++) . angle_addr to) "") $-                            checkMailOK $-                            return-                        ) $-                    send "DATA" $-                    checkDataOK $-                    let msgLines = formatMessage message in-                    for msgLines (\line return ->-                            send (if line == "." then ". " else line) $-                            return-                        ) $-                    send "." $-                    checkMailOK $-                    incSent-                    return+                    receiveReply $ \reply ->+                    if not $ equalsMailOK reply then+                        cont (Just reply)  -- failure status+                    else (+                            for tos (\to next ->+                                    send ((("RCPT TO:"++) . angle_addr to) "") $+                                    receiveReply $ \reply ->+                                    if not $ equalsMailOK reply+                                        then cont (Just reply)  -- failure status+                                        else next+                                ) $+                            send "DATA" $+                            receiveReply $ \reply ->+                            if not $ equalsDataOK reply then+                                cont (Just reply)  -- failure status+                            else (+                                    let msgLines = formatMessage message in+                                    for msgLines (\line cont ->+                                            send (if line == "." then ". " else line) $+                                            cont+                                        ) $+                                    send "." $+                                    receiveReply $ \reply ->+                                    if not $ equalsMailOK reply then+                                        cont (Just reply)  -- failure status+                                    else+                                        cont Nothing  -- success+                                )+                        )                 )  atext_alphabet = Set.fromList $
SMTPClient.cabal view
@@ -1,12 +1,12 @@ name: SMTPClient-version: 0.2+version: 0.3 license: BSD3 license-file: LICENSE cabal-version: >= 1.2 copyright: (c) Stephen Blackheath author: Stephen Blackheath maintainer: http://blacksapphire.com/antispam/-stability: beta+stability: release-candidate synopsis: A simple SMTP client description:     A simple SMTP client
example.hs view
@@ -1,16 +1,11 @@ import Network.SMTP.ClientSession import Network.SMTP.Client import Network.Socket-import Text.ParserCombinators.Parsec.Rfc2822 import System.Time import System.IO import Data.Bits import Data.IORef --- To do: Find out the proper way of doing this.-intToPortNumber :: Int -> PortNumber-intToPortNumber v = PortNum $ fromIntegral $ ((v `shiftR` 8) .|. (v `shiftL` 8)) .&. 0xffff- myDomain = "example.com" smtpHost = "smtp.example.com"    -- <-- Your SMTP server here @@ -29,12 +24,16 @@              "Yours sincerely,\n"++              "Mr. Nobody\n")     addrs <- getAddrInfo Nothing (Just smtpHost) Nothing-    let SockAddrInet _ hostAddr = addrAddress $ (addrs !! 0)-    let sockAddr = SockAddrInet (intToPortNumber 25) hostAddr+    let SockAddrInet _ hostAddr = addrAddress (addrs !! 0)+        sockAddr = SockAddrInet (fromIntegral 25) hostAddr     putStrLn $ "connecting to "++show sockAddr-    sentRef <- newIORef 0-    sendSMTP_ (hPutStrLn stderr) (Just sentRef) myDomain+    sentRef <- newIORef []+    sendSMTP' (hPutStrLn stderr) (Just sentRef) myDomain         sockAddr [message]-    sent <- readIORef sentRef-    putStrLn $ "no of emails sent=" ++ show sent+    statuses <- readIORef sentRef+    -- If no exception was caught, statuses is guaranteed to be+    -- the same length as the list of input messages, therefore head won't fail here.+    case head statuses of+        Nothing     -> putStrLn "Message successfully sent"+        Just status -> putStrLn $ "Message send failed with status "++show status