1 /*******************************************************************************
2 
3     Class containing a single function which sends a email by spawning a child
4     process that executes the command sendmail.
5 
6     Copyright:
7         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
8         All rights reserved.
9 
10     License:
11         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
12         Alternatively, this file may be distributed under the terms of the Tango
13         3-Clause BSD License (see LICENSE_BSD.txt for details).
14 
15 *******************************************************************************/
16 
17 module ocean.net.email.EmailSender;
18 
19 
20 
21 import ocean.core.Array : append;
22 
23 import ocean.meta.types.Qualifiers;
24 import ocean.io.Stdout;
25 import ocean.sys.Process;
26 import ocean.core.ExceptionDefinitions : ProcessException;
27 
28 
29 class EmailSender
30 {
31     /***************************************************************************
32 
33         Spawned process
34 
35     ***************************************************************************/
36 
37     private Process process;
38 
39 
40     /***************************************************************************
41 
42         Temporary buffers used for formatting multiple email addresses into a
43         single buffer containing comma-separated entries
44 
45     ***************************************************************************/
46 
47     private mstring recipients_buf;
48     private mstring cc_buf;
49     private mstring bcc_buf;
50 
51 
52     /***************************************************************************
53 
54         Constructor that creates the reusable process
55 
56     ***************************************************************************/
57 
58     public this ( )
59     {
60         this.process = new Process();
61     }
62 
63 
64     /***************************************************************************
65 
66         Spawns a child process that sends an email using sendmail.
67 
68         Accepts 2D arrays for the recipient list, cc list, and bcc list,
69         automatically comma-separates them, and passes them to the other
70         overload of this method. Use this method if it is more convenient for
71         you to pass the required lists in 2D buffers, rather than in
72         comma-separated 1D buffers as required by sendmail.
73 
74         Params:
75             sender = the sender of the email
76             recipients = the recipient(s) of the email
77             subject = the email subject
78             msg_body = the email body
79             reply_to = an optional Reply To. default empty
80             mail_id = an optional mail id/In-Reply-To. default empty
81             cc = an optional cc. default empty
82             bcc = an optional bcc. default empty
83 
84         Returns:
85             true if the mail was sent without any errors, otherwise false
86 
87     ***************************************************************************/
88 
89     public bool sendEmail ( cstring sender, cstring[] recipients,
90         cstring subject, cstring msg_body, cstring reply_to = null,
91         cstring mail_id = null, cstring[] cc = null, cstring[] bcc = null )
92     {
93         void format_entries_buf ( cstring[] param_to_format,
94                                   ref mstring buf )
95         {
96             auto first_entry = true;
97 
98             buf.length = 0;
99             assumeSafeAppend(buf);
100 
101             foreach ( entry; param_to_format )
102             {
103                 if ( !first_entry )
104                 {
105                     buf ~= ", ";
106                 }
107                 else
108                 {
109                     first_entry = false;
110                 }
111 
112                 buf ~= entry;
113             }
114         }
115 
116         format_entries_buf(recipients, this.recipients_buf);
117 
118         format_entries_buf(cc, this.cc_buf);
119 
120         format_entries_buf(bcc, this.bcc_buf);
121 
122         return this.sendEmail(sender, this.recipients_buf, subject, msg_body,
123             reply_to, mail_id, this.cc_buf, this.bcc_buf);
124     }
125 
126 
127     /***************************************************************************
128 
129         Spawns a child process that sends an email using sendmail.
130 
131         If multiple entries need to be passed in the recipient list, cc list,
132         or bcc list, then the entries need to be comma separated. There exists
133         an overloaded version of this function which takes these lists as 2D
134         arrays instead of regular arrays (with each entry being passed in a
135         different index), and automatically performs the comma separation.
136 
137         Params:
138             sender = the sender of the email
139             recipients = the recipient(s) of the email, multiple entries need to
140                 be comma separated
141             subject = the email subject
142             msg_body = the email body
143             reply_to = an optional Reply To. default empty
144             mail_id = an optional mail id/In-Reply-To. default empty
145             cc = an optional cc, multiple entries need to be comma separated
146                 (defaults to null)
147             bcc = an optional bcc, multiple entries need to be comma separated
148                 (defaults to null)
149 
150         Returns:
151             true if the mail was sent without any errors, otherwise false
152 
153     ***************************************************************************/
154 
155     public bool sendEmail ( cstring sender, cstring recipients,
156         cstring subject, cstring msg_body, cstring reply_to = null,
157         cstring mail_id = null, cstring cc = null, cstring bcc = null )
158     {
159         Process.Result result;
160 
161         with (this.process)
162         {
163             try
164             {
165                 setArgs("sendmail", "-t", "-f", sender).execute();
166 
167                 stdin.write("From: ");
168                 stdin.write(sender);
169                 stdin.write("\nTo: ");
170                 stdin.write(recipients);
171                 if ( cc != null )
172                 {
173                     stdin.write("\nCc: ");
174                     stdin.write(cc);
175                 }
176                 if ( bcc != null )
177                 {
178                     stdin.write("\nBcc: ");
179                     stdin.write(bcc);
180                 }
181                 stdin.write("\nSubject: ");
182                 stdin.write(subject);
183                 if ( reply_to != null)
184                 {
185                     stdin.write("\nReply-To: ");
186                     stdin.write(reply_to);
187                 }
188                 if ( mail_id != null)
189                 {
190                     stdin.write("\nIn-Reply-To: ");
191                     stdin.write(mail_id);
192 
193                 }
194                 stdin.write("\nMime-Version: 1.0");
195                 stdin.write("\nContent-Type: text/html; charset=UTF-8\n");
196                 stdin.write(msg_body);
197                 stdin.close();
198                 result = process.wait;
199             }
200             catch ( ProcessException e )
201             {
202                 Stderr.formatln("Process '{}' ({}) exited with reason {}, "
203                   ~ "status {}", programName, pid, cast(int) result.reason,
204                     result.status);
205                 return false;
206             }
207         }
208         return true;
209     }
210 }
211 
212 version (unittest)
213 {
214     import ocean.core.Tuple: Tuple;
215 }
216 
217 /// EmailSender simple usage
218 unittest
219 {
220     void sendReport ()
221     {
222         auto reporter = new EmailSender();
223 
224         reporter.sendEmail("notification@example.com", "test@example.com",
225                            "Notification test report", "This is a test report",
226                            "noreply@example.com");
227     }
228 }
229 
230 // Ensure D2 const correctness
231 unittest
232 {
233     void sendReport ()
234     {
235         auto reporter = new EmailSender();
236 
237         alias Tuple!(cstring, istring, mstring) ArgTypes;
238 
239         foreach (ArgType; ArgTypes)
240         {
241             ArgType email_from = "notification@example.com".dup;
242             ArgType email_to = "test@example.com".dup;
243             ArgType email_subject = "Notification test report".dup;
244             ArgType email_body = "This is a test report".dup;
245             ArgType email_reply_to = "noreply@example.com".dup;
246 
247             reporter.sendEmail(email_from, email_to, email_subject, email_body,
248                                email_reply_to);
249         }
250     }
251 }