Browse Source

reply-to working

master
pvincent 3 years ago
parent
commit
ea4da3c270
  1. 1
      cagette.hxml
  2. 2
      lang/master/tpl/messages/default.mtt
  3. 6
      src/controller/Messages.hx
  4. 133
      src/sugoi/mail/Mail.hx
  5. 25
      src/sugoi/mail/SendEmailMailer.hx
  6. 68
      src/sugoi/mail/SmtpMailer.hx

1
cagette.hxml

@ -21,6 +21,7 @@
-lib tink_streams:0.2.1 -lib tink_streams:0.2.1
-lib tink_syntaxhub:0.4.3 -lib tink_syntaxhub:0.4.3
-lib tink_typecrawler:0.7.0 -lib tink_typecrawler:0.7.0
-lib thx.text:0.2.1
## GIT ## GIT
# -lib sugoi:git:https://git.artcode.re/cagetters/sugoi.git # -lib sugoi:git:https://git.artcode.re/cagetters/sugoi.git

2
lang/master/tpl/messages/default.mtt

@ -29,7 +29,7 @@
<div class="col-md-9"> <div class="col-md-9">
<div class="article"> <div class="article">
<h3>::_("Send an e-mail")::</h3> <h3>::_("Send an e-mail")::</h3>
::raw form:: ::raw form::
</div> </div>
</div> </div>

6
src/controller/Messages.hx

@ -51,7 +51,7 @@ class Messages extends Controller {
// send mail confirmation link // send mail confirmation link
var e = new sugoi.mail.Mail(); var e = new sugoi.mail.Mail();
e.setSender(App.config.get("default_email"));
e.setSender(App.config.get("default_email"), t._("Cagette.net"));
e.setSubject(subject); e.setSubject(subject);
var replyTo = form.getValueOf("senderMail"); var replyTo = form.getValueOf("senderMail");
@ -62,8 +62,6 @@ class Messages extends Controller {
for (x in mails) for (x in mails)
e.addRecipient(x); e.addRecipient(x);
// sender : default email ( explicitly tells that the server send an email on behalf of the user )
// e.setHeader("Sender", App.config.get("default_email"));
var text:String = form.getValueOf("text"); var text:String = form.getValueOf("text");
var html = app.processTemplate("mail/message.mtt", {text: text, group: app.user.amap, list: getListName(listId)}); var html = app.processTemplate("mail/message.mtt", {text: text, group: app.user.amap, list: getListName(listId)});
e.setHtmlBody(html); e.setHtmlBody(html);
@ -84,6 +82,8 @@ class Messages extends Controller {
lm.insert(); lm.insert();
throw Ok("/messages", t._("The message has been sent")); throw Ok("/messages", t._("The message has been sent"));
} else {
App.log('Administrator #${app.user.id} <${senderName}> prepares sending an email from choices list');
} }
view.form = form; view.form = form;

133
src/sugoi/mail/Mail.hx

@ -1,45 +1,53 @@
package sugoi.mail; package sugoi.mail;
using Lambda; using Lambda;
class Mail implements IMail
{
public var title : String;
public var htmlBody : String;
public var textBody : String;
var headers : Map<String,String>;
var sender : {name:String,email:String,?userId:Int};
var recipients : Array<{name:String,email:String,?userId:Int}>;
class Mail implements IMail {
public var title:String;
public var htmlBody:String;
public var textBody:String;
var headers:Map<String, String>;
var sender:{name:String, email:String, ?userId:Int};
var recipients:Array<{name:String, email:String, ?userId:Int}>;
static public function fromString(name:String, email:String) {
if (name == null)
return email;
else {
var finalName = thx.text.Diactrics.clean(name);
return '${finalName} <${email}>';
}
}
public function new() { public function new() {
recipients = []; recipients = [];
headers = new Map(); headers = new Map();
} }
public function getRecipients(){
public function getRecipients() {
return recipients; return recipients;
} }
public function setSender(email, ?name,?userId) {
if(!isValid(email)) throw "invalid sender email : \""+email+"\"";
sender = {name:name,email:email,userId:userId};
public function setSender(email, ?name, ?userId) {
if (!isValid(email))
throw "invalid sender email : \"" + email + "\"";
sender = {name: name, email: email, userId: userId};
return this; return this;
} }
public function setReplyTo(email, ?name) { public function setReplyTo(email, ?name) {
if(!isValid(email)) throw "invalid reply-to email : \""+email+"\"";
setHeader("Reply-To","<"+email+">"+(name==null?"":name));
if (!isValid(email))
throw "invalid reply-to email : \"" + email + "\"";
setHeader("Reply-To", fromString(name, email));
} }
public function setSubject(s:String) { public function setSubject(s:String) {
title = s; title = s;
return this; return this;
} }
/** /**
* can add one or more recipient * can add one or more recipient
* @param email * @param email
@ -47,11 +55,12 @@ class Mail implements IMail
* @param ?userId * @param ?userId
*/ */
public function addRecipient(email:String, ?name:String, ?userId:Int) { public function addRecipient(email:String, ?name:String, ?userId:Int) {
if(!isValid(email)) throw "invalid recipient \""+email+"\"";
recipients.push( {email:email, name:name, userId:userId } );
if (!isValid(email))
throw "invalid recipient \"" + email + "\"";
recipients.push({email: email, name: name, userId: userId});
return this; return this;
} }
/** /**
* alias to addRecipient() * alias to addRecipient()
* @param email * @param email
@ -62,24 +71,24 @@ class Mail implements IMail
addRecipient(email, name, userId); addRecipient(email, name, userId);
return this; return this;
} }
public static function isValid( addr : String ){
public static function isValid(addr:String) {
var reg = ~/^[^()<>@,;:\\"\[\]\s[:cntrl:]]+@[A-Z0-9][A-Z0-9-]*(\.[A-Z0-9][A-Z0-9-]*)*\.(xn--[A-Z0-9]+|[A-Z]{2,8})$/i; var reg = ~/^[^()<>@,;:\\"\[\]\s[:cntrl:]]+@[A-Z0-9][A-Z0-9-]*(\.[A-Z0-9][A-Z0-9-]*)*\.(xn--[A-Z0-9]+|[A-Z]{2,8})$/i;
return addr != null && reg.match(addr); return addr != null && reg.match(addr);
} }
public function setHeader(k:String, v:String) { public function setHeader(k:String, v:String) {
headers.set(k, v); headers.set(k, v);
return this; return this;
} }
/** /**
* generate a custom key for transactionnal emails, valid during the current day * generate a custom key for transactionnal emails, valid during the current day
*/ */
public function getKey() { public function getKey() {
return haxe.crypto.Md5.encode(App.config.get("key")+recipients[0].email+(Date.now().getDate())).substr(0,12);
return haxe.crypto.Md5.encode(App.config.get("key") + recipients[0].email + (Date.now().getDate())).substr(0, 12);
} }
/** /**
* render html from a template + vars * render html from a template + vars
* @param tpl A Template path * @param tpl A Template path
@ -88,7 +97,8 @@ class Mail implements IMail
public function setHtmlBodyWithTemplate(tpl, ctx:Dynamic) { public function setHtmlBodyWithTemplate(tpl, ctx:Dynamic) {
var app = App.current; var app = App.current;
var tpl = app.loadTemplate(tpl); var tpl = app.loadTemplate(tpl);
if( ctx == null ) ctx = { };
if (ctx == null)
ctx = {};
ctx.HOST = App.config.HOST; ctx.HOST = App.config.HOST;
ctx.key = getKey(); ctx.key = getKey();
ctx.senderName = sender.name; ctx.senderName = sender.name;
@ -98,28 +108,27 @@ class Mail implements IMail
ctx.recipients = recipients; ctx.recipients = recipients;
CSSInlining(ctx); CSSInlining(ctx);
htmlBody = tpl.execute(ctx); htmlBody = tpl.execute(ctx);
} }
public function setHtmlBody(s) { public function setHtmlBody(s) {
htmlBody = s; htmlBody = s;
return this; return this;
} }
public function setTextBodyWithTemplate(tpl, ctx:Dynamic) {
public function setTextBodyWithTemplate(tpl, ctx:Dynamic) {
var app = App.current; var app = App.current;
var tpl = app.loadTemplate(tpl); var tpl = app.loadTemplate(tpl);
if( ctx == null ) ctx = { };
if (ctx == null)
ctx = {};
ctx.HOST = App.config.HOST; ctx.HOST = App.config.HOST;
ctx.key = getKey(); ctx.key = getKey();
textBody = tpl.execute(ctx); textBody = tpl.execute(ctx);
return this; return this;
} }
function CSSInlining(ctx) { function CSSInlining(ctx) {
// CSS inlining // CSS inlining
var css : Map<String,Array<String>> = new Map();
var css:Map<String, Array<String>> = new Map();
ctx.addStyle = function(sel:String, style:String) { ctx.addStyle = function(sel:String, style:String) {
sel = sel.toLowerCase(); sel = sel.toLowerCase();
if (css.exists(sel)) if (css.exists(sel))
@ -130,11 +139,11 @@ class Mail implements IMail
} }
var applyStyleRec = null; var applyStyleRec = null;
applyStyleRec = function(x:Xml) { applyStyleRec = function(x:Xml) {
if (x.nodeType==Xml.Element) {
if (x.nodeType == Xml.Element) {
var name = x.nodeName.toLowerCase(); var name = x.nodeName.toLowerCase();
if( css.exists(name) )
if (x.get("style")!=null)
x.set("style", x.get("style")+";"+css.get(name).join(";"));
if (css.exists(name))
if (x.get("style") != null)
x.set("style", x.get("style") + ";" + css.get(name).join(";"));
else else
x.set("style", css.get(name).join(";")); x.set("style", css.get(name).join(";"));
for (n in x) for (n in x)
@ -143,40 +152,38 @@ class Mail implements IMail
} }
ctx.applyStyle = function(raw:String) { ctx.applyStyle = function(raw:String) {
var x = Xml.parse(raw); var x = Xml.parse(raw);
for(n in x)
for (n in x)
applyStyleRec(n); applyStyleRec(n);
return x.toString(); return x.toString();
} }
} }
public function getSubject(){
public function getSubject() {
return title; return title;
} }
public function getTitle(){
public function getTitle() {
return getSubject(); return getSubject();
} }
public function getHtmlBody(){
public function getHtmlBody() {
return htmlBody; return htmlBody;
} }
public function getTextBody(){
public function getTextBody() {
return textBody; return textBody;
} }
public function setTextBody(t){
public function setTextBody(t) {
textBody = t; textBody = t;
return this; return this;
} }
public function getHeaders(){
public function getHeaders() {
return headers; return headers;
} }
public function getSender(){
public function getSender() {
return sender; return sender;
} }
}
}

25
src/sugoi/mail/SendEmailMailer.hx

@ -40,17 +40,13 @@ class SendEmailMailer implements IMailer {
return mailer; return mailer;
} }
private function fromString(name:String, email:String) {
return (name == null) ? email : '${name} <${email}>';
}
public function send(e:sugoi.mail.IMail, ?params:Dynamic, ?callback:MailerResult->Void) { public function send(e:sugoi.mail.IMail, ?params:Dynamic, ?callback:MailerResult->Void) {
var to:String; var to:String;
var bcc:String; var bcc:String;
var recipientCounts = e.getRecipients().length; var recipientCounts = e.getRecipients().length;
if (recipientCounts > 1) { if (recipientCounts > 1) {
to = fromString(e.getSender().name, e.getSender().email);
to = sugoi.mail.Mail.fromString(e.getSender().name, e.getSender().email);
bcc = [for (i in e.getRecipients()) i.email].join(","); bcc = [for (i in e.getRecipients()) i.email].join(",");
App.log('recipients has been transformed to ${recipientCounts} bcc'); App.log('recipients has been transformed to ${recipientCounts} bcc');
App.log(bcc); App.log(bcc);
@ -72,26 +68,35 @@ class SendEmailMailer implements IMailer {
"-o", "-o",
"message-format=html", "message-format=html",
"-o", "-o",
"X-Mailer=CagettePei",
"message-header=X-Mailer: CagettePei",
"-f", "-f",
fromString(e.getSender().name, e.getSender().email),
sugoi.mail.Mail.fromString(e.getSender().name, e.getSender().email),
"-t", "-t",
to, to,
"-bcc", "-bcc",
bcc, bcc,
"-u", "-u",
e.getSubject(), e.getSubject(),
"-m",
'<html>${e.getHtmlBody()}</html>',
]; ];
// FIME: deal with replyTo
if (e.getHeaders().exists("Reply-To")) {
var replyTo = e.getHeaders()["Reply-To"];
App.log('replyTo "$replyTo" detected');
args.push("-o");
args.push('message-header=Reply-To: ${replyTo}');
}
args.push("-m");
args.push('<html>${e.getHtmlBody()}</html>');
// if (App.config.DEBUG) // if (App.config.DEBUG)
// App.log('args=${args.join(" ")}'); // App.log('args=${args.join(" ")}');
var exitCode = Sys.command("sendemail", args); var exitCode = Sys.command("sendemail", args);
// var exitCode = 0; // var exitCode = 0;
var summary = 'email from="${fromString(e.getSender().name, e.getSender().email)}"> subject=<${e.getSubject()}>';
var summary = 'email from="${sugoi.mail.Mail.fromString(e.getSender().name, e.getSender().email)}" subject=<${e.getSubject()}>';
if (exitCode == 0) if (exitCode == 0)
App.log('$summary successfully sent'); App.log('$summary successfully sent');
else else

68
src/sugoi/mail/SmtpMailer.hx

@ -1,68 +0,0 @@
package sugoi.mail;
import tink.core.Future;
import tink.core.Noise;
import sugoi.mail.IMailer;
import smtpmailer.Address;
/**
* Send emails thru SMTP by using ben merckx's library
* @ref https://github.com/benmerckx/smtpmailer
*/
class SmtpMailer implements IMailer
{
var m : smtpmailer.SmtpMailer;
public function new(){}
public function init(?conf:{smtp_host:String,smtp_port:Int,smtp_user:String,smtp_pass:String}) :IMailer
{
m = new smtpmailer.SmtpMailer({
host: conf.smtp_host,
port: conf.smtp_port,
auth: {
username: conf.smtp_user,
password: conf.smtp_pass
}
});
return this;
}
public function send(e:sugoi.mail.IMail,?params:Dynamic,?callback:MailerResult->Void)
{
var surprise = m.send({
subject: e.getSubject(),
/*from: e.getSender().email,
to: Lambda.array(Lambda.map(e.getRecipients(), function(x) return smtpmailer.Address.ofString(x.email) )),
//headers : e.getHeaders(),*/
from: new Address({address:e.getSender().email}),
to: Lambda.array(Lambda.map(e.getRecipients(), function(x) return new Address({address:x.email}) )),
headers : e.getHeaders(),
content: {
text: e.getTextBody(),
html: e.getHtmlBody()
}/*,
attachments: []*/
});
if (callback != null){
surprise.handle(function(s){
var map = new MailerResult();
switch(s){
case Success(_):
map.set("*",Success(Sent));
case Failure(e):
map.set("*",Failure(GenericError(e)));
}
callback(map);
});
}
}
}
Loading…
Cancel
Save