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 }