UNPKG

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