support starttls, update docs

This commit is contained in:
eleith 2011-02-24 15:02:24 -08:00
parent e41bca2d92
commit 3679d2b0f3
6 changed files with 220 additions and 79 deletions

119
Readme.md
View File

@ -1,60 +1,123 @@
#v0.1
###send emails, html and attachments from node.js to any smtp server
### emailjs
send emails, html and attachments from node.js to any smtp server
### Installing
npm install emailjs
npm install emailjs
# FEATURES
- works with SSL smtp servers (ex: gmail)
- works with smtp server authentication (PLAIN, LOGIN, CRAMMD5)
- works with SSL and TLS smtp servers (ex: gmail)
- supports smtp authentication (PLAIN, LOGIN, CRAMMD5)
- emails are queued and the queue is sent asynchronously
- supports sending html emails and emails with multiple attachments
- supports sending html emails and emails with multiple attachments (MIME)
- works with nodejs 3.8 and above
# REQUIRES
- access to an SMTP Server (ex: gmail)
# USAGE - text only emails
# API
## email.server.connect(options)
// options is an object with the following keys
options =
{
username // username for logging into smtp
password // password for logging into smtp
host // smtp host
port // smtp port (if null a standard port number will be used)
ssl // boolean or object {key, ca, cert} (if exists, ssl connection will be made)
tls // boolean (if true, starttls will be initiated)
timeout // max number of milliseconds to wait for smtp responses (defaults to 5000)
domain // domain to greet smtp with (defaults to os.hostname)
}
## email.server.send(message, callback)
// message can be a smtp.Message (as returned by email.message.create)
// or an object identical to the first argument accepted by email.message.create
// callback will be executed with (err, message)
// either when message is sent or an error has occurred
## email.message.create(headers)
// headers is an object with the following keys ('from' and 'to' are required)
// returns a Message object
headers =
{
text // text of the email
from // sender of the format (address or name <address> or "name" <address>)
to // recipients (same format as above), multiple recipients are separated by a comma
cc // carbon copied recipients (same format as above)
bcc // blind carbon copied recipients (same format as above)
subject // string subject of the email
}
## Message.attach_alternative(html)
// should only be called once
html // string representing the html version of the email message
## Message.attach(path, mime_type, name)
// can be called multiple times, each creating a new
// attachment on the email itself
path // string to where the file is located
mime_type // string of the file mime type
name // name to give the file as perceived by the recipient
# EXAMPLE USAGE - text only emails
var email = require("./path/to/emailjs/email");
var server = email.server.connect({
user:yourUSER,
password:yourPASS,
host:"smtp.gmail.com",
port:465,
domain:yourDOMAIN,
secure:true});
user: "username",
password:"password",
host: "smtp.gmail.com",
ssl: true
});
// send the message and get a callback with an error or details of the message that was sent
server.send({
text:"i hope this works",
from:yourUSER + "@gmail.com",
to:yourFRIEND,
subject:"testing emailjs"},
function(err, message) {
console.log(err || message);
});
text: "i hope this works",
from: "you <username@gmail.com>",
to: "someone <someone@gmail.com>, another <another@gmail.com>",
cc: "else <else@gmail.com>",
subject: "testing emailjs"
}, function(err, message) { console.log(err || message); });
# USAGE - html emails and attachments
# EXAMPLE USAGE - html emails and attachments
var email = require("./path/to/emailjs/email");
var server = email.server.connect({
user:yourUSER,
password:yourPASS,
host:"smtp.gmail.com",
port:465,
domain:yourDOMAIN,
secure:true});
user: "username",
password:"password",
host: "smtp.gmail.com",
ssl: true
});
var message = email.message.create("i hope this works", {from:yourUSER + "@gmail.com", to:yourFRIEND, subject:"testing emailjs"});
var headers = {
text: "i hope this works",
from: "you <username@gmail.com>",
to: "someone <someone@gmail.com>, another <another@gmail.com>",
cc: "else <else@gmail.com>",
subject: "testing emailjs"
};
// create the message
var message = email.message.create(headers);
// attach an alternative html email for those with advanced email clients
message.attach_alternative("i <i>hope</i> this works!");
// attach attachments because you can!
message..attach("path/to/file.zip", "application/zip", "renamed.zip");
message.attach("path/to/file.zip", "application/zip", "renamed.zip");
// send the message and get a callback with an error or details of the message that was sent
server.send(message, function(err, message) { console.log(err || message); });

26
demo.js
View File

@ -4,18 +4,19 @@ var os = require('os');
SMTP =
{
USER: '',
PASS: '',
USER: 'username',
PASS: 'password',
HOST: "smtp.gmail.com",
SECURE: true,
PORT: 465
PORT: null, // emailjs will use default SMTP port standards (587, 465, 25) where appropriate
SSL: false, // use ssl from begin to end on smtp connection, accepts object of (key, ca, certs) as well...
TLS: true // use STARTTLS encrypting stream after initial smtp connection
};
MESSAGE =
{
DOMAIN: os.hostname(),
FROM: '',
TO: '',
FROM: 'username@gmail.com',
TO: 'person1 <person1@example.com>, person2 <person2@example.com>, person3 <person2@example.com>',
SUBJECT: 'testing emailjs',
TEXT: 'i hope this works',
HTML: 'i <i>hope</i> <b>this</b> works',
@ -28,9 +29,16 @@ MESSAGE =
};
var server = email.server.connect({user:SMTP.USER, password:SMTP.PASS, host:SMTP.HOST, port:SMTP.PORT, domain:MESSAGE.DOMAIN, secure:SMTP.SECURE});
var msg = email.message.create(MESSAGE.TEXT, {from:MESSAGE.FROM, to:MESSAGE.TO, subject:MESSAGE.SUBJECT});
var server = email.server.connect({
user: SMTP.USER,
password: SMTP.PASS,
host: SMTP.HOST,
port: SMTP.PORT,
tls: SMTP.TLS,
ssl: SMTP.SSL,
domain: MESSAGE.DOMAIN});
var msg = email.message.create({text:MESSAGE.TEXT, from:MESSAGE.FROM, to:MESSAGE.TO, subject:MESSAGE.SUBJECT});
msg.attach_alternative(MESSAGE.HTML).attach(ATTACH.PATH, ATTACH.TYPE, ATTACH.NAME);
server.send(msg, function(err, message) { console.log(message); });
server.send(msg, function(err, message) { console.log(err || message); });

View File

@ -1,7 +1,7 @@
{
"name": "emailjs",
"description": "send emails, html and attachments from node.js to any smtp server",
"version": "0.1.0",
"version": "0.1.2",
"author": "eleith",
"contributors" : [
],

View File

@ -82,6 +82,12 @@ Client.prototype =
callback: callback || function() {}
};
if(msg.header["cc"])
stack.to = stack.to.concat(address.parse(msg.header["cc"]));
if(msg.header["bcc"])
stack.to = stack.to.concat(address.parse(msg.header["bcc"]));
self.queue.push(stack);
self._poll();
}
@ -114,7 +120,7 @@ Client.prototype =
_sendrcpt: function(stack)
{
var self = this, to = stack.to.shift().address;
self.smtp.rcpt(self._sendsmtp(stack, stack.to.length ? self._sendrcpt : self._senddata), '<' + to + '>');
self.smtp.rcpt(self._sendsmtp(stack, stack.to.length ? self._sendrcpt : self._senddata), '<'+ to +'>');
},
_senddata: function(stack)

View File

@ -15,13 +15,15 @@ var generate_boundary = function()
return text;
};
var Message = function(text, headers)
var Message = function(headers)
{
this.attachments = [];
this.text = text;
this.text = headers.text;
this.html = null;
this.header = {"message-id":"<" + (new Date()).getTime() + "." + process.pid + "@" + os.hostname() +">"};
delete headers.text;
for(var header in headers)
{
// allow any headers the user wants to set??
@ -239,7 +241,11 @@ var MessageStream = function(message)
var data = [];
for(var header in self.message.header)
data = data.concat([header, ": ", self.message.header[header], CRLF]);
{
// do not output BCC in the headers...
if(!/bcc/i.test(header))
data = data.concat([header, ": ", self.message.header[header], CRLF]);
}
self.emit('data', data.join(''));
output_process(output_data);

View File

@ -7,12 +7,14 @@ var os = require('os');
var tls = require('tls');
var util = require('util');
var events = require('events');
var starttls = require('./tls');
var SMTPResponse = require('./response');
var SMTPError = require('./error');
var SMTP_PORT = 25;
var SMTP_SSL_PORT = 465;
var SMTP_TLS_PORT = 587;
var CRLF = "\r\n";
var AUTH_METHODS = {PLAIN:'PLAIN', CRAM_MD5:'CRAM-MD5', LOGIN:'LOGIN'};
var TIMEOUT = 5000;
@ -68,14 +70,15 @@ var SMTP = function(options)
this.sock = null;
this.timeout = options.timeout || TIMEOUT;
this.secure = options.secure || false;
this.features = null;
this._state = SMTPState.NOTCONNECTED;
this._secure = false;
this.loggedin = (options.user && options.password) ? false : true;
this.domain = options.domain || os.hostname();
this.host = options.host || 'localhost';
this.port = options.port || options.secure ? SMTP_SSL_PORT : SMTP_PORT;
this.ssl = options.ssl;
this.port = options.port || (options.ssl ? SMTP_SSL_PORT : options.tls ? SMTP_TLS_PORT : SMTP_PORT);
this.ssl = options.ssl || false;
this.tls = options.tls || false;
// keep private
SMTP_USER = options.user;
@ -103,32 +106,35 @@ SMTP.prototype =
{
options = options || {};
var self = this;
var self = this, connect_timeout = null;
self.host = host || self.host;
self.port = port || self.port;
self.secure = options.secure || self.secure;
self.ssl = options.ssl || self.ssl;
if(self._state != SMTPState.NOTCONNECTED)
self.quit();
var connected = function(err)
{
clearTimeout(connect_timeout);
if(!err)
{
if(self.secure)
log("connected: " + self.host + ":" + self.port);
if(self.ssl && !self.tls)
{
// if key/ca/cert was passed and ssl is used, check if authorized is false
if(self.ssl && !self.sock.authorize)
// if key/ca/cert was passed in, check if connection is authorized
if(typeof(self.ssl) != 'boolean' && !self.sock.authorized)
{
self.close(true);
caller(callback, {code:SMTPError.CONNECTIONAUTH, message:"could not establish an ssl connection", error:err});
return;
}
else
self._secure = true;
}
log("connected: " + self.host + ":" + self.port);
}
else
{
@ -143,7 +149,7 @@ SMTP.prototype =
if(!err && msg.code == '220')
{
log("response: " + data);
log(data);
// might happen first, so no need to wait on connected()
self._state = SMTPState.CONNECTED;
@ -156,38 +162,42 @@ SMTP.prototype =
log("response (error): " + err);
self.close(true);
caller(callback, {code:err.code, error:err.error});
caller(callback, {code:err.code, error:err.error, message:err.message});
}
else
{
log("response (data): " + data);
self.quit();
caller(callback, {code:SMTPError.BadResponse, message:"bad response on connection", smtp:data, error:err});
caller(callback, {code:SMTPError.BADRESPONSE, message:"bad response on connection", smtp:data, error:err});
}
}
};
var timedout = function()
{
if(self._state != SMTPState.CONNECTED)
{
self.close(true);
caller(callback, {code:SMTPError.TIMEDOUT, message:"timedout while connecting to smtp server"});
}
};
self._state = SMTPState.CONNECTING;
if(self.secure)
if(self.ssl)
{
// object, may contain 'key' and/or 'ca' and/or 'cert'
if(self.ssl)
self.sock = tls.connect(self.port, self.host, self.ssl, connected);
else
self.sock = tls.connect(self.port, self.host, connected);
self.sock = tls.connect(self.port, self.host, self.ssl, connected);
}
else
{
self.sock = net.Socket();
self.sock.connect(self.port, self.host, connected);
}
connect_timeout = setTimeout(timedout, self.timeout);
SMTPResponse.watch(self.sock);
self.sock.setTimeout(self.timeout);
self.sock.once('response', response);
},
@ -198,11 +208,11 @@ SMTP.prototype =
if(self.sock && self._state == SMTPState.CONNECTED)
{
log("send: " + str);
log(str);
var response = function(err, data)
{
log("response: " + (data || err));
log((data || err));
if(err)
self.close(true);
@ -253,15 +263,49 @@ SMTP.prototype =
this.command("helo " + (domain || this.domain), callback);
},
/*
// STARTTLS is not supported since node net api doesn't support upgrading a socket to a secure socket
// use ssl instead of tls. the benefit is that the entire communication will be encrypted from the beginning
starttls: function(callback, domain)
// */
starttls: function(callback)
{
this.command("starttls", callback);
var self = this,
response = function(err, data)
{
if(!err)
{
var secured_socket = null;
var secured_timer = null;
var secured = function()
{
clearTimeout(secured_timer);
self._secure = true;
self.sock = secured_socket;
SMTPResponse.watch(self.sock);
caller(callback, err);
};
var timeout = function()
{
caller(callback, {code:SMTPError.TIMEDOUT, message:"connection timedout during STARTTLS handshake"});
};
secured_timer = setTimeout(timeout, self.timeout);
secured_socket = starttls.secure(self.sock, self.ssl, secured);
}
else
{
caller(callback, err);
}
};
this.command("starttls", response, [220]);
},
*/
ehlo: function(callback, domain)
{
@ -295,8 +339,22 @@ SMTP.prototype =
self.features[parse[1].toLowerCase()] = parse[2] || true;
}
});
caller(callback, null, data);
if(self.tls && !self._secure)
{
var secured = function(err, data)
{
if(!err)
self.ehlo(callback, domain);
else
caller(callback, err, data);
};
self.starttls(secured);
}
else
caller(callback, null, data);
}
else
{
@ -336,8 +394,7 @@ SMTP.prototype =
rcpt: function(callback, to)
{
// SMTP 'rcpt' command -- indicates 1 recipient for self mail
this.command("rcpt TO:" + to, callback, [250, 251]);
this.command("RCPT TO:" + to, callback, [250, 251]);
},
data: function(callback)
@ -347,11 +404,12 @@ SMTP.prototype =
data_end: function(callback)
{
this.command(CRLF + "." + CRLF, callback);
this.command(CRLF + ".", callback);
},
message: function(data)
{
log(data);
this.sock.write(data);
},
@ -448,7 +506,7 @@ SMTP.prototype =
for(var i = 0; i < preferred.length; i++)
{
if((self.features["auth"]).indexOf(preferred[i]) != -1)
if((self.features["auth"] || "").indexOf(preferred[i]) != -1)
{
method = preferred[i];
break;
@ -497,7 +555,7 @@ SMTP.prototype =
self.command("AUTH " + AUTH_METHODS.PLAIN + " " + encode_plain(login.user, login.password), response, [235, 503]);
else if(!method)
caller(callback, {code:SMTPError.AUTHNOTSUPPORTED, message:"authorization no supported", smtp:data});
caller(callback, {code:SMTPError.AUTHNOTSUPPORTED, message:"no form of authorization supported", smtp:data});
};
self.ehlo_or_helo_if_needed(initiate, domain);
@ -515,9 +573,9 @@ SMTP.prototype =
}
this._state = SMTPState.NOTCONNECTED;
this._secure = false;
this.sock = null;
this.features = null;
this.secure = false;
this.loggedin = false;
},