e1884afb9daf0c9fa2df33b67b30e81f82ab79a9
[dead/halcyon.git] / src / Mail.hs
1 -- |Email functions and data types.
2
3 module Mail
4 where
5
6 import Control.Concurrent
7 import Control.Exception (evaluate)
8 import Data.List (intercalate)
9 import Data.Time (formatTime, getZonedTime)
10 import System.Exit
11 import System.Locale (defaultTimeLocale, rfc822DateFormat)
12 import System.Process
13 import System.IO (hClose, hGetContents, hPutStr)
14
15
16 type Header = String
17
18 -- | A crude model of an RFC821 email message.
19 data Message = Message { headers :: [Header],
20 subject :: String,
21 body :: String,
22 from :: String,
23 to :: String }
24 deriving (Eq)
25
26 -- | The default headers attached to each message. The MIME junk is
27 -- needed for UTF-8 to work properly. Note that your mail server
28 -- should support the 8BITMIME extension.
29 default_headers :: [Header]
30 default_headers = ["MIME-Version: 1.0",
31 "Content-Type: text/plain; charset=UTF-8",
32 "Content-Transfer-Encoding: 8bit"]
33
34 -- | Showing a message will print it in roughly RFC-compliant
35 -- form. This form is sufficient for handing the message off to
36 -- sendmail (or compatible).
37 instance Show Message where
38 show m =
39 concat [ formatted_headers,
40 "Subject: " ++ (subject m) ++ "\n",
41 "From: " ++ (from m) ++ "\n",
42 "To: " ++ (to m) ++ "\n",
43 "\n",
44 (body m) ]
45 where
46 formatted_headers =
47 if (length (headers m) == 0)
48 then ""
49 else (intercalate "\n" (headers m)) ++ "\n"
50
51
52 -- |Pad a string on the left with zeros until the entire string has
53 -- length n.
54 pad_left :: String -> Int -> String
55 pad_left str n
56 | n < (length str) = str
57 | otherwise = (replicate num_zeros '0') ++ str
58 where num_zeros = n - (length str)
59
60
61
62 -- | Constructs a 'String' in RFC822 date format for the current
63 -- date/time.
64 rfc822_now :: IO String
65 rfc822_now = do
66 date <- getZonedTime
67 return $ formatTime defaultTimeLocale rfc822DateFormat date
68
69
70
71 -- |Takes a message as an argument, and passes it to the system's
72 -- sendmail (or compatible) binary.
73 sendmail :: FilePath -> Message -> IO (String, String, ExitCode)
74 sendmail sendmail_path message = do
75 let sendmail_args = ["-f",
76 (from message),
77 (to message)]
78
79 (inh, outh, errh, ph) <-
80 runInteractiveProcess sendmail_path sendmail_args Nothing Nothing
81
82 outm <- newEmptyMVar
83 outs <- hGetContents outh
84
85 errm <- newEmptyMVar
86 errs <- hGetContents errh
87
88 _ <- forkIO $ hPutStr inh (show message) >> hClose inh
89 _ <- forkIO $ evaluate (length outs) >> putMVar outm ()
90 _ <- forkIO $ evaluate (length errs) >> putMVar errm ()
91
92 readMVar outm
93 readMVar errm
94
95 ec <- waitForProcess ph
96 return (outs, errs, ec)
97
98
99 -- |The 'sendmail' function returns a three-tuple of its outputs,
100 -- errors, and exit codes. This function pretty-prints one of those
101 -- three-tuples.
102 print_sendmail_result :: (String, String, ExitCode) -> IO ()
103 print_sendmail_result (outs, errs, ec) = do
104 case ec of
105 ExitSuccess -> return ()
106 _ -> putStrLn $ concat ["Output: " ++ outs,
107 "\nErrors: " ++ errs,
108 "\nExit Code: " ++ (show ec)]