module arsd.email; import std.net.curl; pragma(lib, "curl"); import std.base64; // SEE ALSO: std.net.curl.SMTP struct RelayInfo { string server; string username; string password; } class EmailMessage { void setHeader(string name, string value) { headers ~= name ~ ": " ~ value; } string[] to; string[] cc; string[] bcc; string from; string replyTo; string inReplyTo; string textBody; string htmlBody; string subject; string[] headers; private bool isMime = false; private bool isHtml = false; void setTextBody(string text) {} void setHtmlBody(string html) { isMime = true; isHtml = true; htmlBody = html; import arsd.htmltotext; if(textBody is null) textBody = htmlToText(html); } struct MimeAttachment { string type; string filename; const(void)[] content; } const(MimeAttachment)[] attachments; void addAttachment(string mimeType, string filename, in void[] content) { isMime = true; attachments ~= MimeAttachment(mimeType, filename, content); } string toString() { string boundary = "0016e64be86203dd36047610926a"; // FIXME assert(!isHtml || (isHtml && isMime)); auto headers = this.headers; string toHeader = "To: "; bool toHeaderOutputted = false; foreach(t; to) { if(toHeaderOutputted) toHeader ~= ", "; else toHeaderOutputted = true; toHeader ~= t; } if(to.length) headers ~= toHeader; if(subject !is null) headers ~= "Subject: " ~ subject; if(isMime) headers ~= "MIME-Version: 1.0"; if(attachments.length) headers ~= "Content-Type: multipart/mixed; boundary=" ~ boundary; else if(isHtml) headers ~= "Content-Type: multipart/alternative; boundary=" ~ boundary; else headers ~= "Content-Type: text/plain; charset=UTF-8"; string msg; msg.reserve(htmlBody.length + textBody.length + 1024); foreach(header; headers) msg ~= header ~ "\r\n"; if(msg.length) // has headers msg ~= "\r\n"; if(isMime) { msg ~= "--" ~ boundary ~ "\r\n"; msg ~= "Content-Type: text/plain; charset=UTF-8\r\n\r\n"; } msg ~= textBody; if(isMime) msg ~= "\r\n--" ~ boundary; if(isHtml) { msg ~= "\r\n"; msg ~= "Content-Type: text/html; charset=UTF-8\r\n\r\n"; msg ~= htmlBody; msg ~= "\r\n--" ~ boundary; } foreach(attachment; attachments) { assert(isMime); msg ~= "\r\n"; msg ~= "Content-Type: " ~ attachment.type ~ "\r\n"; msg ~= "Content-Disposition: attachment; filename=\""~attachment.filename~"\"\r\n"; msg ~= "Content-Transfer-Encoding: base64\r\n"; msg ~= "\r\n"; msg ~= Base64.encode(cast(const(ubyte)[]) attachment.content); msg ~= "\r\n--" ~ boundary; } if(isMime) msg ~= "--\r\n"; return msg; } void send(RelayInfo mailServer = RelayInfo("smtp://localhost")) { auto smtp = new SMTP(mailServer.server); const(char)[][] allRecipients = cast(const(char)[][]) (to ~ cc ~ bcc); // WTF cast smtp.mailTo(allRecipients); smtp.mailFrom = from; smtp.message = this.toString(); smtp.perform(); } } void email(string to, string subject, string message, string from) { auto msg = new EmailMessage(); msg.from = from; msg.to = [to]; msg.subject = subject; msg.textBody = message; msg.send(); }