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 }