Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade to new Sendgrid NodeJS 4.x (including support for categories) #28

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jsbeautifyrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"indent_size":2,
"indent_with_tabs":false
}
13 changes: 2 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,6 @@ var options = {
api_key: 'SENDGRID_APIKEY'
}
}

// or

// username + password
var options = {
auth: {
api_user: 'SENDGRID_USERNAME',
api_key: 'SENDGRID_PASSWORD'
}
}

var mailer = nodemailer.createTransport(sgTransport(options));
```
Expand All @@ -52,7 +42,8 @@ var email = {
from: '[email protected]',
subject: 'Hi there',
text: 'Awesome sauce',
html: '<b>Awesome sauce</b>'
html: '<b>Awesome sauce</b>',
categories: ['Welcome Email', 'Receipt Email']
};

mailer.sendMail(email, function(err, res) {
Expand Down
13 changes: 5 additions & 8 deletions examples/simple.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ var sgTransport = require('../src/sendgrid-transport.js');

var options = {
auth: {
api_user: process.env['SENDGRID_USERNAME'],
api_key: process.env['SENDGRID_PASSWORD']
api_key: process.env['SENDGRID_API_KEY']
}
}

Expand All @@ -16,17 +15,15 @@ var email = {
subject: 'Hi there',
text: 'Awesome sauce',
html: '<b>Awesome sauce</b>',
attachments: [
{
attachments: [{
filename: 'test.txt',
path: __dirname + '/test.txt'
}
]
}]
};

mailer.sendMail(email, function(err, res) {
if (err) {
console.log(err)
if (err) {
console.log(err)
}
console.log(res);
});
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "nodemailer-sendgrid-transport",
"version": "0.2.0",
"version": "0.3.2",
"description": "SendGrid transport for Nodemailer",
"main": "src/sendgrid-transport.js",
"scripts": {
Expand All @@ -17,11 +17,12 @@
"author": "SendGrid <[email protected]> (sendgrid.com)",
"contributors": [
"Eddie Zaneski <[email protected]",
"Will Smidlein <[email protected]>"
"Will Smidlein <[email protected]>",
"Geoffroy Lesage <[email protected]>"
],
"license": "MIT",
"dependencies": {
"sendgrid": "^1.8.0"
"sendgrid": "git+https://github.com/glesage/sendgrid-nodejs.git"
},
"devDependencies": {
"chai": "^1.9.1",
Expand Down
192 changes: 73 additions & 119 deletions src/sendgrid-transport.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use strict';

var SendGrid = require('sendgrid');
var packageData = require('../package.json');

module.exports = function(options) {
Expand All @@ -14,156 +13,111 @@ function SendGridTransport(options) {
this.name = 'SendGrid';
this.version = packageData.version;

if (!this.options.auth.api_user) {
// api key
this.sendgrid = SendGrid(this.options.auth.api_key);
} else {
// username + password
this.sendgrid = SendGrid(this.options.auth.api_user, this.options.auth.api_key);
}
this.sendgrid = require('sendgrid')(this.options.auth.api_key);
}

// if in "name" <[email protected]> format, reformat to just [email protected]
function trimReplyTo(a) {
if (a.indexOf('<') >= 0 && a.indexOf('>') > 0) {
return a.substring(a.indexOf('<')+1, a.indexOf('>'));
}
return a;
if (a.indexOf('<') >= 0 && a.indexOf('>') > 0) {
return a.substring(a.indexOf('<') + 1, a.indexOf('>'));
}
return a;
}

SendGridTransport.prototype.send = function(mail, callback) {
var email = mail.data;

// reformat replyTo to replyto
if (email.replyTo) {
email.replyto = trimReplyTo(email.replyTo);
}

// fetch envelope data from the message object
var subject = mail.data.subject || '';
var addresses = mail.message.getAddresses();
var from = [].concat(addresses.from || addresses.sender || addresses['reply-to'] || []).shift();
var to = [].concat(addresses.to || []);
var cc = [].concat(addresses.cc || []);
var bcc = [].concat(addresses.bcc || []);
var categories = [].concat(mail.data.categories || []);

var email = {
personalizations: [],
content: [],
from: {}
};

// populate from and fromname
if (from) {
if (from.address) {
email.from = from.address;
email.from.email = from.address;
}

if (from.name) {
email.fromname = from.name;
email.from.name = from.name;
}
}

// populate to and toname arrays
email.to = to.map(function(rcpt) {
return rcpt.address || '';
});

email.toname = to.map(function(rcpt) {
return rcpt.name || '';
});

// populate cc and bcc arrays
email.cc = cc.map(function(rcpt) {
return rcpt.address || '';
});

email.bcc = bcc.map(function(rcpt) {
return rcpt.address || '';
});

// a list for processing attachments
var contents = [];

// email.text could be a stream or a file, so store it for processing
if (email.text) {
contents.push({
obj: email,
key: 'text'
});
}

// email.html could be a stream or a file, so store it for processing
if (email.html) {
contents.push({
obj: email,
key: 'html'
});
}
if (to && Array.isArray(to) && to.length > 0) {
var recipients = {
to: to.map(function(rcpt) {
return {
email: rcpt.address || '',
name: rcpt.name || ''
}
}),
subject: subject
}

// store attachments for processing, to fetch files, urls and streams
email.files = email.attachments;
[].concat(email.files || []).forEach(function(attachment, i) {
contents.push({
obj: email.files,
key: i,
isAttachment: true
});
});

// fetch values for text/html/attachments as strings or buffers
// this is an asynchronous action, so we'll handle it with a simple recursion
var _self = this;
var pos = 0;
var resolveContent = function() {

// if all parts are processed, send out the e-mail
if (pos >= contents.length) {
return _self.sendgrid.send(email, function(err, json) {
callback(err, json);
// populate cc arrays
if (cc && Array.isArray(cc) && cc.length > 0) {
var ccs = cc.map(function(rcpt) {
if (typeof rcpt === "string") return {
email: rcpt
};
return {
email: rcpt.address || '',
name: rcpt.name || ''
};
});
recipients.cc = ccs;
}

// get the next element from the processing list
var file = contents[pos++];
/*
We need to store a pointer to the original attachment object in case
resolveContent replaces it with the Stream value
*/
var prevObj = file.obj[file.key];
// ensure the object is an actual attachment object, not a string, buffer or a stream
if (prevObj instanceof Buffer ||  typeof prevObj === 'string' || (prevObj && typeof prevObj.pipe === 'function')) {
prevObj = {
content: prevObj
};
// populate bcc arrays
if (bcc && Array.isArray(bcc) && bcc.length > 0) {
var bccs = bcc.map(function(rcpt) {
if (typeof rcpt === "string") return {
email: rcpt
};
return {
email: rcpt.address || '',
name: rcpt.name || ''
};
});
recipients.bcc = bccs;
}

// use the helper function to convert file paths, urls and streams to strings or buffers
mail.resolveContent(file.obj, file.key, function(err, content) {
if (err) {
return callback(err);
}
email.personalizations.push(recipients);
}

if (!file.isAttachment) {
// overwrites email.text and email.html content
file.obj[file.key] = content;
} else {
// populate categories
if (categories && Array.isArray(categories) && categories.length > 0) {
email.categories = categories;
}

// If the object is a String or a Buffer then it is most likely replaces by resolveContent
if (file.obj[file.key] instanceof Buffer ||  typeof file.obj[file.key] === 'string') {
file.obj[file.key] = prevObj;
}
file.obj[file.key].content = content;
if (file.obj[file.key].path) {
if (!file.obj[file.key].filename) {
// try to detect the required filename from the path
file.obj[file.key].filename = file.obj[file.key].path.split(/[\\\/]/).pop();
}
delete file.obj[file.key].path;
}
// set default filename if filename and content-type are not set (allowed for Nodemailer but not for SendGrid)
if (!file.obj[file.key].filename && !file.obj[file.key].contentType) {
file.obj[file.key].filename = 'attachment-' + pos + '.bin';
}
}
// populate plain text content
if (mail.data.text) {
email.content.push({
value: mail.data.text,
type: 'text/plain'
});
}

resolveContent();
// populate html content
if (mail.data.html) {
email.content.push({
value: mail.data.html,
type: 'text/html'
});
};
}

// start the recursive function
resolveContent();
};
this.sendgrid.API(this.sendgrid.emptyRequest({
method: 'POST',
path: '/v3/mail/send',
body: email,
}), callback);
};
24 changes: 12 additions & 12 deletions test/sendgrid-transport-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ var pkg = require('../package.json');
var transport = null;

describe('sendgrid-transport', function() {
it('should take an api_user and api_key', function() {
transport = sgTransport({ 'auth': { api_user: 'test', api_key: 'test' } })
});
it('should take an apikey', function() {
transport = sgTransport({
'auth': {
api_key: 'test'
}
})
});

it('should take an apikey', function() {
transport = sgTransport({ 'auth': { api_key: 'test' } })
});

it('should have a name and version', function() {
expect(transport.name).to.eq('SendGrid')
expect(transport.version).to.eq(pkg.version)
});
});
it('should have a name and version', function() {
expect(transport.name).to.eq('SendGrid')
expect(transport.version).to.eq(pkg.version)
});
});