1 module appbase.gateway.smtp.message; 2 3 import std.format; 4 import std.uuid : randomUUID; 5 import std.base64 : Base64; 6 7 import appbase.gateway.smtp.attachment; 8 9 /++ 10 Struct that holds name and address of a person holding e-mail box and is capable of sending messages. 11 +/ 12 struct Recipient { 13 string address; 14 string name; 15 } 16 17 /++ 18 Implements SmtpMessage compositor. 19 Allows to get string representation of message and also send it via SmtpClient. 20 SmtpMessage is used by `SmtpClient.send` high-level method in order to compose 21 and send mail via SMTP. 22 +/ 23 struct SmtpMessage { 24 static string boundary; // Parts delmiter in multipart message 25 Recipient sender; // Specifies name/address of a sender 26 Recipient[] recipients; // Specifies names/adresses of recipients 27 string subject; // Message subject 28 string message; // Message text (body) 29 string replyTo; // Messages chains maked (reply-to:) 30 SmtpAttachment[] attachments; // Attachments to message 31 32 /++ 33 Initializes boundary for parts in multipart/mixed message type. 34 Boundary is a random sequence of chars that must divide message 35 into parts: message, and attachments. 36 +/ 37 static this() { 38 boundary = randomUUID().toString(); 39 } 40 41 /++ 42 Add attachments to the `SmtpMessage`. 43 +/ 44 void attach(SmtpAttachment[] a...) { 45 attachments ~= a; 46 } 47 48 /++ 49 Builds string representation for a list of copies-to-send over SMTP. 50 +/ 51 private string cc() const { 52 string tCc = "Cc:\"%s\" <%s>\r\n"; 53 string cc = ""; 54 if (recipients.length > 1) { 55 foreach(recipient; recipients) { 56 cc ~= format(tCc, recipient.name, recipient.address); 57 } 58 } else { 59 cc = ""; 60 } 61 return cc; 62 } 63 64 /++ 65 Builds message representation in case we have multipart/mixed MIME-type 66 of the message to send. 67 +/ 68 private string messageWithAttachments() const { 69 const string crlf = "\r\n"; 70 return "Content-Type: text/html; charset=utf-8" ~ crlf 71 ~ "Content-Transfer-Encoding: base64" ~ crlf 72 ~ crlf 73 ~ cast(string) Base64.encode(cast(ubyte[]) message) ~ crlf 74 ~ crlf ~ "--" ~ SmtpMessage.boundary ~ crlf; 75 } 76 77 /++ 78 Partly converts attachments to string for SMTP protocol representation 79 +/ 80 private string attachmentsToString() const { 81 string result = ""; 82 foreach(ref a; attachments) { 83 result ~= a.toString(boundary); 84 } 85 return (result.length ? result[0..$ - 2] : "") ~ "\r\n"; 86 } 87 88 /++ 89 This method converts SmtpMessage struct to string representation. 90 This string representation is a ready-to-send representation for 91 SMTP protocol. 92 +/ 93 string toString() const { 94 const string tFrom = "From: \"%s\" <%s>\r\n"; 95 const string tTo = "To: \"%s\" <%s>\r\n"; 96 const string tSubject = "Subject: %s\r\n"; 97 const string mime = "MIME-Version: 1.0\r\n"; 98 const string tMultipart = format("Content-Type: multipart/mixed; boundary=\"%s\"\r\n", SmtpMessage.boundary); 99 const string tReplyTo = "Reply-To:%s\r\n"; 100 const string crlf = "\r\n"; 101 102 if (!attachments.length) { 103 return format(tFrom, sender.name, sender.address) 104 ~ format(tTo, recipients[0].name, recipients[0].address) 105 ~ cc() 106 ~ format(tSubject, buildBase64Msg(subject)) 107 ~ mime 108 ~ tMultipart 109 ~ format(tReplyTo, replyTo) ~ crlf 110 ~ "--" ~ boundary ~ crlf 111 ~ messageWithAttachments() ~ crlf; 112 } else { 113 return format(tFrom, sender.name, sender.address) 114 ~ format(tTo, recipients[0].name, recipients[0].address) 115 ~ cc() 116 ~ format(tSubject, buildBase64Msg(subject)) 117 ~ mime 118 ~ tMultipart 119 ~ format(tReplyTo, replyTo) ~ crlf 120 ~ "--" ~ boundary ~ crlf 121 ~ messageWithAttachments() 122 ~ attachmentsToString(); 123 } 124 } 125 126 private string buildBase64Msg(const string msg) const { 127 return "=?UTF-8?B?" ~ cast(string) Base64.encode(cast(ubyte[]) msg) ~ "?="; 128 } 129 }