UNPKG

6.38 kBJavaScriptView Raw
1'use strict';
2
3var AWS = require('aws-sdk');
4var packageData = require('../package.json');
5
6// expose to the world
7module.exports = function (options) {
8 return new SESTransport(options);
9};
10
11var THROTTLE_DELAY = 5;
12
13/**
14 * <p>Generates a Transport object for Amazon SES with aws-sdk</p>
15 *
16 * <p>Possible options can be the following:</p>
17 *
18 * <ul>
19 * <li><b>ses</b> - instantiated AWS SES object. If not provided then one is generated using provided information.</li>
20 * <li><b>accessKeyId</b> - AWS access key (optional)</li>
21 * <li><b>secretAccessKey</b> - AWS secret (optional)</li>
22 * <li><b>region</b> - optional region (defaults to <code>'us-east-1'</code>)
23 * </ul>
24 *
25 * @constructor
26 * @param {Object} optional config parameter for the AWS SES service
27 */
28function SESTransport(options) {
29 options = options || {};
30 var serviceUrlRegion = [].concat(/(.*)email(.*)\.(.*).amazonaws.com/i.exec(options.ServiceUrl) || [])[3];
31
32 this.options = options;
33
34 if (!options.ses) {
35 this.options.accessKeyId = options.accessKeyId || options.AWSAccessKeyID;
36 this.options.secretAccessKey = options.secretAccessKey || options.AWSSecretKey;
37 this.options.sessionToken = options.sessionToken || options.AWSSecurityToken;
38 this.options.apiVersion = '2010-12-01';
39 this.options.region = options.region || serviceUrlRegion || 'us-east-1';
40
41 if (options.httpOptions) {
42 this.options.httpOptions = options.httpOptions;
43 }
44 this.ses = new AWS.SES(this.options);
45 } else {
46 this.ses = options.ses;
47 }
48
49 this.rateLimit = Number(options.rateLimit) || false;
50 this.queue = [];
51 this.sending = false;
52 this.currentConnections = 0;
53 this.maxConnections = Number(options.maxConnections) || Infinity;
54
55 this.name = 'SES';
56 this.version = packageData.version;
57}
58
59/**
60 * Appends the message to the queue if rate limiting is used, or passes directly to the sending function
61 *
62 * @param {Object} mail Mail object
63 * @param {Function} callback Callback function to run when the sending is completed
64 */
65SESTransport.prototype.send = function (mail, callback) {
66 // SES strips this header line by itself
67 mail.message.keepBcc = true;
68
69 if (this.rateLimit) {
70 this.queue.push({
71 mail: mail,
72 callback: callback
73 });
74 this.processQueue();
75 } else if (this.currentConnections < this.maxConnections) {
76 this.sendMessage(mail, callback);
77 } else {
78 setTimeout(this.send.bind(this, mail, callback), THROTTLE_DELAY);
79 }
80};
81
82/**
83 * Sends the next message from the queue
84 */
85SESTransport.prototype.processQueue = function () {
86 if (this.sending) {
87 return;
88 }
89
90 if (!this.queue.length) {
91 return;
92 }
93
94 this.sending = true;
95 var item = this.queue.shift();
96
97 this.sendMessage(item.mail, function () {
98 var args = Array.prototype.slice.call(arguments);
99
100 if (typeof item.callback === 'function') {
101 setImmediate(function () {
102 item.callback.apply(null, args);
103 });
104 }
105 }.bind(this));
106
107 setTimeout(function sendNextMail() {
108 if (this.currentConnections < this.maxConnections) {
109 this.sending = false;
110 this.processQueue();
111 } else {
112 setTimeout(sendNextMail.bind(this), THROTTLE_DELAY);
113 }
114 }.bind(this), Math.ceil(1000 / this.rateLimit));
115};
116
117/**
118 * <p>Compiles a BuildMail message and forwards it to handler that sends it.</p>
119 *
120 * @param {Object} mail Mail object
121 * @param {Function} callback Callback function to run when the sending is completed
122 */
123SESTransport.prototype.sendMessage = function (mail, callback) {
124 this.generateMessage(mail.message.createReadStream(), (function (err, raw) {
125 if (err) {
126 return typeof callback === 'function' && callback(err);
127 }
128 this.handleMessage(mail, raw, callback);
129 }).bind(this));
130};
131
132/**
133 * <p>Compiles and sends the request to SES with e-mail data</p>
134 *
135 * @param {Object} mail Mail object
136 * @param {String} raw Compiled raw e-mail as a string
137 * @param {Function} callback Callback function to run once the message has been sent
138 */
139SESTransport.prototype.handleMessage = function (mail, raw, callback) {
140 var params = {
141 RawMessage: { // required
142 Data: new Buffer(raw, 'utf-8') // required
143 }
144 };
145 if (this.options.source) {
146 params.Source = this.options.source;
147 }
148 if (mail.data.Destinations) {
149 params.Destinations = mail.data.Destinations;
150 }
151 this.currentConnections++;
152 this.ses.sendRawEmail(params, function (err, data) {
153 this.currentConnections--;
154 this.responseHandler(err, mail, data, callback);
155 }.bind(this));
156};
157
158/**
159 * <p>Handles the response for the HTTP request to SES</p>
160 *
161 * @param {Object} err Error object returned from the request
162 * @param {Object} mail Mail object
163 * @param {Object} data De-serialized data returned from the request
164 * @param {Function} callback Callback function to run on end
165 */
166SESTransport.prototype.responseHandler = function (err, mail, data, callback) {
167 if (err) {
168 if (!(err instanceof Error)) {
169 err = new Error('Email failed: ' + err);
170 }
171 return typeof callback === 'function' && callback(err, null);
172 }
173 return typeof callback === 'function' && callback(null, {
174 envelope: mail.data.envelope || mail.message.getEnvelope(),
175 messageId: data && data.MessageId && data.MessageId + '@' + this.options.region + '.amazonses.com'
176 });
177};
178
179/**
180 * <p>Compiles the BuildMail object to a string.</p>
181 *
182 * <p>SES requires strings as parameter so the message needs to be fully composed as a string.</p>
183 *
184 * @param {Object} mailStream BuildMail stream
185 * @param {Function} callback Callback function to run once the message has been compiled
186 */
187
188SESTransport.prototype.generateMessage = function (mailStream, callback) {
189 var chunks = [];
190 var chunklen = 0;
191
192 mailStream.on('data', function (chunk) {
193 chunks.push(chunk);
194 chunklen += chunk.length;
195 });
196
197 mailStream.on('error', function(err) {
198 callback(err);
199 });
200
201 mailStream.on('end', function () {
202 callback(null, Buffer.concat(chunks, chunklen).toString());
203 });
204};