UNPKG

7.22 kBJavaScriptView Raw
1var
2 util = require('util'),
3 path = require('path'),
4 nodemailer = require('nodemailer'),
5 // emailTemplates = require('email-templates'),
6 task = require('./base'),
7 dataflows = require ('../');
8
9/**
10 * Batch sending of emails
11 * - supports templates
12 * - doesn't support guaranteed delivery of emails
13 * - doesn't support guaranteed sending of emails (sends to smtp transport)
14 * however, notifies transport errors to console as warnings.
15 *
16 * The task is completed as soon as all required fields for email sending are
17 * present and all emails are forwarded to smtp transport for sending.
18 *
19 * The task is failed or skipped (depends on @cfg {Boolean} important) if there are problems
20 * with template/contents or recipients list.
21 *
22 *
23 * @cfg {Object} fields - email fields to send. If we're using recipients list
24 * email fields structure will be used as defaults
25 *
26 * fields = {
27 * from: {String}, // optional, defaults to consumerConfig value : sender OR SMTP.auth.user
28 * to: {String}, // can be also named "email"
29 * cc: {String},
30 * bcc: {String},
31 * subject: {String},
32 * text: {String}, // text template
33 * html : {String}, // html template
34 * attachments: {Array}
35 * }
36 *
37 *
38 * @cfg {String} template - name of template
39 * template is ejs based, generates Text and HTML
40 * templates are to be located in <project_root>/templates/email
41 * see details: https://github.com/niftylettuce/node-email-templates
42 *
43 * ! template OR text OR html OR subject MUST be provided
44 *
45 *
46 * @cfg {Array} recipients - list for batch sending
47 *
48 * recipients = ["<email>", "<email>",...]
49 * recipients = [{
50 * email: {String}, // can be named "to" or otherwise (see below) substitutes email.to
51 * // name,... - other fields used in template
52 * }]
53 *
54 * @cfg {String} emailField - if present recipients[emailField] whill be taken as email.to
55 *
56 * ! email.to OR recipients MUST be provided
57 *
58*/
59
60// mail task need two things to get configured: transports and templates
61// without dataflows project you need to pass whole transport configuration
62// in transport key and can use full paths for templates (absolute or within current dir)
63
64var
65 mailConfig,
66 templatesDir = 'templates/email';
67
68if ('project' in dataflows) {
69 // TODO: use pathToVar to avoid try/catch
70 mailConfig = dataflows.config.service.mail;
71}
72
73
74function resolveTemplate (transConf) {
75
76}
77
78
79
80var mailTask = module.exports = function (config) {
81
82 this.init (config);
83
84};
85
86util.inherits (mailTask, task);
87
88mailTask.prototype.run = function () {
89
90 var
91 fields = this.fields,
92 recipients = this.recipients,
93 emails = [];
94
95 if (!recipients || recipients.length === 0) {
96 var email = this.checkFields (fields);
97 if (!email) {
98 return;
99 }
100 emails.push (email);
101 } else {
102 for (var recId = 0; recId < recipients.length; recId ++) {
103 var email = this.checkFields (recipients[recId], fields);
104 if (!email) {
105 return;
106 }
107 emails.push (email);
108 }
109 }
110
111 var transport = this.resolveTransport (this.transport);
112 if (!transport)
113 return;
114
115 this.transporter = this.createTransport (transport);
116
117 var sentCount = 0;
118
119 emails.forEach (function (email, idx) {
120
121 this.transporter.use ('compile', this.render.bind (this));
122
123 this.transporter.sendMail (email, function (error, response) {
124 if (error)
125 return this.failed (error);
126
127 this.emit ('log', 'OK: Email sent to ' + email.to);
128
129 sentCount ++;
130
131 if (sentCount === emails.length) {
132 this.completed ();
133 }
134 }.bind (this));
135
136 }.bind (this));
137
138}
139
140/**
141 * Check for all required fields to be present for email
142 * @param {Object|String} fields envelope fields like from, to and so on; assume `to` if string provided
143 * @param {Object} defaults envelope template
144 * @returns {Object} envelope with fields
145 */
146mailTask.prototype.checkFields = function (fields, defaults) {
147
148 var email = {};
149
150 if (fields.constructor === String) {
151 fields = {to: fields};
152 }
153
154 if (defaults)
155 for (var f in defaults) {
156 email[f] = defaults[f];
157 }
158
159 for (var f in fields) {
160 email[f] = fields[f];
161 }
162
163 email.to = email.email || email.to;
164 email.from = email.sender || email.from;
165
166 if (!email.to || !email.from || !email.subject)
167 return this.failed ('from, to and subject must be provided');
168 if (!email.text && !email.html)
169 return this.failed ('text or html template must be provided');
170
171 return email;
172}
173
174/**
175 * Creating transports for nodemail
176 * @param {String} transport configuration, can be plain
177 * (like {service: "gmail"}, or for plugin —
178 * {plugin: "ses", config: <config object>})
179 * @returns {Object} transporter
180 */
181mailTask.prototype.createTransport = function (transport) {
182 // TODO: create all needed transports from project config in app start
183 // to ensure every plugin will be loaded
184
185 if (transport === "test") {
186 return nodemailer.createTransport ({
187 name: 'testsend',
188 version: '1',
189 send: function(data, callback) {
190 callback();
191 }
192 });
193 }
194
195 if (transport.plugin) {
196 var transPlugin = require (transport.plugin);
197 return nodemailer.createTransport (transPlugin (transport.config));
198 }
199
200 return nodemailer.createTransport (transport);
201}
202
203/**
204 * Resolve transport by config key
205 * @param {String|Object} transConf key to resolve transport in config or complete transport configuration
206 * @returns {Object} transport configuration
207 */
208mailTask.prototype.resolveTransport = function (transConf) {
209 // you can use transport string only if dataflows project configuration defined
210 if (transConf === "test") {
211 return transConf;
212 } else if (transConf.constructor === String) {
213 if (!mailConfig) {
214 return this.failed ("you must supply transport configuration via dataflows.config");
215 }
216
217 return mailConfig.transports[transConf];
218 }
219
220 return transConf;
221}
222
223
224mailTask.prototype.render = function (mail, done) {
225
226 // TODO: use renderer
227 console.log ("STILL NO RENDERER FOR EMAIL");
228
229 if (!mail || !mail.data || !mail.data.html || mail.data.text) {
230 return done();
231 }
232
233 done();
234}
235
236mailTask.prototype._batchSend = function (email, recipients) {
237 var self = this,
238 emailField = self.emailField || "email";
239
240 console.log('Email setup:', email);
241 console.log('SMTP setup:', mailConfig.SMTP);
242
243 var sendMail = function (err, email) { self._sendMail(err, email); };
244
245 for (var i = 0; i < recipients.length; i++) {
246 var recipient = recipients[i];
247 email.to = recipient[emailField] || recipient.email || recipient.to;
248 if (!email.to || email.length < 6 || email.to.indexOf('@')<0) continue; // ugly skip bad emails
249 self._render(email, recipient, sendMail);
250 }
251
252 // TODO: track individual mail delivery if needed, for example if 'important' flag is set
253
254 self.emit('log', 'Emails sent to transport. Actual sending not guaranteed. See further log.');
255 self.completed(true);
256
257 }
258
259mailTask.prototype._sendMail = function (err, email) {
260 var self = this;
261
262 if (err) return self._err(err, 'warning');
263
264 self.emit('log', 'Sending email to ' + email.to);
265 transport.sendMail(email, function (error, response) {
266 if (error) self._err(error, 'warning');
267 else self.emit('log', 'OK: Email sent to ' + email.to);
268 });
269 }
270