1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
---------------------------------------------------------------------------
--- This library contains operations for sending emails.
--- The implementation might need to be adapted to the local
--- environment.
---
--- @author Michael Hanus
--- @version May 2018
---------------------------------------------------------------------------

module Mail ( sendMail, MailOption(..), sendMailWithOptions )
 where

import Directory ( doesFileExist )
import FilePath  ( (</>) )
import IOExts    ( execCmd )
import IO        ( hClose, hPutStrLn )
import List      ( splitOn )
import System    ( getEnviron )

--- Sends an email via mailx command.
--- @param from - the email address of the sender
--- @param to - the email address of the recipient
--- @param subject - the subject of the email
--- @param contents - the contents of the email
sendMail :: String -> String -> String -> String -> IO ()
sendMail from to subject = sendMailWithOptions from subject [TO to]

--- Options for sending emails.
--- @cons CC  - recipient of a carbon copy
--- @cons BCC - recipient of a blind carbon copy
--- @cons TO  - recipient of the email
data MailOption = CC String | BCC String | TO String
 deriving Eq

--- Sends an email via mailx command and various options.
--- Note that multiple options are allowed, e.g., more than one CC option
--- for multiple recipient of carbon copies.
---
--- Important note: The implementation of this operation is based on the
--- command "mailx" and must be adapted according to your local environment!
---
--- @param from - the email address of the sender
--- @param subject - the subject of the email
--- @param options - send options, e.g., multiple recipients
--- @param contents - the contents of the email
sendMailWithOptions :: String -> String -> [MailOption] -> String -> IO ()
sendMailWithOptions from subject options contents = do
  mailcmdexists <- fileInPath "mailx"
  if mailcmdexists
    then
      -- if mailx has the option -r:
      --execMailCmd ("mailx -n -r \"" ++ from ++ "\" -s \"" ++ subject++"\" "++
      -- if mailx has the option -a:
      execMailCmd
        ("mailx -n -a \"From: " ++ from ++ "\" -s \"" ++ subject ++ "\" " ++
         (if null bccs then "" else "-b \""++bccs++"\" ") ++
         (if null ccs  then "" else "-c \""++ccs++"\" ") ++ "\"" ++ tos ++ "\"")
        contents
    else error "Command 'mailx' not found in path!"
 where
   tos  = unwords [ s | TO  s <- options ]
   ccs  = unwords [ s | CC  s <- options ]
   bccs = unwords [ s | BCC s <- options ]

--- Executes a command to send an email and pass the contents via stdin.
--- Note that \r characters in the contents are removed due to problems
--- with such contents in some Unix environments.
execMailCmd :: String -> String -> IO ()
execMailCmd cmd contents = do
  (sin,_,_) <- execCmd cmd
  hPutStrLn sin (filter isUnixChar contents)
  hClose sin
 where
  isUnixChar c = c /= '\r'

---------------------------------------------------------------------------
-- Auxiliaries:

--- Checks whether a file exists in one of the directories on the PATH.
fileInPath :: String -> IO Bool
fileInPath file = do
  path <- getEnviron "PATH"
  let dirs = splitOn ":" path
  (liftIO (any id)) $ mapIO (doesFileExist . (</> file)) dirs

---------------------------------------------------------------------------