Permalink
Browse files

Merged pull request #16 from bmeck/master.

starttls, mail routing events
  • Loading branch information...
2 parents 1034ae8 + ba3e952 commit ff3bb176ebe2acd78cf16c64c8dd03b0b88f83fa @andris9 andris9 committed Apr 28, 2011
Showing with 281 additions and 86 deletions.
  1. +3 −9 README.md
  2. +15 −7 examples/example.js
  3. +98 −49 lib/mail.js
  4. +1 −0 lib/mime.js
  5. +76 −21 lib/smtp.js
  6. +88 −0 lib/starttls.js
View
@@ -16,7 +16,7 @@ Nodemailer supports
- *HTML contents* as well as plain text alternative
- *Attachments*
- *Embedded images* in HTML
- - *SSL* (but not STARTTLS)
+ - *SSL* (and STARTTLS)
Installation
------------
@@ -56,7 +56,7 @@ Using *EmailMessage*
var nodemailer = require("nodemailer");
- var mail = new nodemailer.EmailMessage({
+ var mail = nodemailer.EmailMessage({
sender: "me@example.com",
to:"you@example.com"
});
@@ -109,12 +109,10 @@ If you want to use SSL (not TLS/STARTTLS, just SSL), you need to set the ssl par
pass: "my.password"
}
-Nodemailer supports SSL support, with two big caveats:
+Nodemailer supports SSL support, with one big caveat:
- You *must* be using nodejs v0.3+ and its TLS library
- - You *must* use SSL from the beginning, not TLS/STARTTLS negotiation
-For example for Gmail use port `465` and server `smtp.gmail.com` (SSL) but not port `587` which is for STARTTLS and thus doesn't work.
E-mail Message Fields
--------------------
@@ -222,10 +220,6 @@ Issues
Use [Nodemailer Issue tracker](https://github.com/andris9/Nodemailer/issues) to report additional shortcomings, bugs, feature requests etc.
-### TLS
-
-Node.JS v0.3.x doesn't support changing to a secure channel in the middle of a connection (STARTTLS). So when a server requires authentication and this must be done over TLS it's a problem.
-
### Charsets
Currently the only allowed charset is UTF-8.
View
@@ -8,22 +8,22 @@ nodemailer.SMTP = {
port: 465,
use_authentication: true,
ssl: true,
- user: "your.username@gmail.com",
- pass: "your_gmail_password"
+ user: undefined,
+ pass: undefined
}
// unique cid value for the embedded image
var cid = Date.now()+".image.png";
// Message object
var message = {
- sender: 'Example Test <test@example.com>',
- to: '"My Name" <mymail@example.com>',
+ sender: 'Example Test <bradley.meck@gmail.com>',
+ to: '"My Name" <bradley.meck@gmail.com>',
subject: "Nodemailer is unicode friendly ✔",
body: "Hello to myself!",
html:"<p><b>Hello</b> to myself <img src=\"cid:"+cid+"\"/></p>",
-
+ debug: true,
attachments:[
{
filename: "notes.txt",
@@ -54,5 +54,13 @@ var callback = function(error, success){
}
// Send the e-mail
-nodemailer.send_mail(message, callback);
-
+process.on("uncaughtException",function(e){console.log("Uncaught Exception",e.stack);})
+var mail;
+try {
+ mail = nodemailer.send_mail(message, callback);
+}
+catch(e) {
+ console.log("Caught Exception",e);
+}
+var oldemit = mail.emit;
+mail.emit = function() {console.log("Mail.emit",arguments);oldemit.apply(mail,arguments)}
View
@@ -1,7 +1,8 @@
var SMTPClient = require("./smtp").SMTPClient,
mimelib = require("./mime"),
- exec = require('child_process').exec;
-
+ exec = require('child_process').exec
+ util = require("util"),
+ EventEmitter = require("events").EventEmitter
/*
* Version constants
*/
@@ -45,6 +46,12 @@ exports.SMTP = {
pass: false
};
+exports.CommonServers = {
+ gmail:[],
+ yahoo:[],
+ hotmail:[]
+};
+
/**
* mail.sendmail -> Boolean | String
*
@@ -72,6 +79,7 @@ exports.EmailMessage = EmailMessage;
exports.send_mail = function(params, callback){
var em = new EmailMessage(params);
em.send(callback);
+ return em;
};
var gencounter = 0;
@@ -83,7 +91,8 @@ var gencounter = 0;
* Creates an object to send an e-mail.
*
* params can hold the following data
- *
+ *
+ * - **server** server to send message to (will default to exports.SMTP)
* - **sender** e-mail address of the sender
* - **headers** an object with custom headers.
* `{"X-Myparam": "test", "Message-ID":"12345"}`
@@ -101,7 +110,11 @@ var gencounter = 0;
* - **debug** if set, outputs the whole communication of SMTP server to console
*
* All the params can be edited/added after defining the object
- *
+ *
+ * Events:
+ * forward(oldAddr,newAddr) - was told to try new address by server.
+ * defer(addr) - server takes responsibility for delivery.
+ * retain(addr) - unable to send to mailbox.
* Usage:
* var em = EmailMessage();
* em.sender = '"Andris Reinman" <andris@node.ee>'
@@ -112,7 +125,9 @@ var gencounter = 0;
* NB! mail.SMTP needs to be set before sending any e-mails!
**/
function EmailMessage(params){
+ EventEmitter.call(this);
params = params || {};
+ this.SERVER = params.server || exports.SMTP;
this.sender = params.sender;
this.headers = params.headers || {};
this.to = params.to;
@@ -130,6 +145,8 @@ function EmailMessage(params){
this.callback = null;
}
+var utillib = require("util");
+util.inherits(EmailMessage,EventEmitter);
/**
* mail.EmailMessage#prepareVariables() -> undefined
@@ -140,7 +157,9 @@ EmailMessage.prototype.prepareVariables = function(){
if(this.html || this.attachments.length){
this.content_multipart = true;
this.content_mixed = !!this.attachments.length;
- this.content_boundary = "----NODEMAILER-"+(++gencounter)+"-"+Date.now();
+ //'=_' is not valid quoted printable
+ //'?' is not in any known base64 extension
+ this.content_boundary = "----NODEMAILER-?=_"+(++gencounter)+"-"+Date.now();
// defaults to multipart/mixed but if there's attachments with cid value set
// use multipart/related - mail clients hide the duplicates this way
@@ -275,7 +294,7 @@ EmailMessage.prototype.generateBody = function(){
}
var body_boundary = this.content_mixed?
- "----NODEMAILER-"+(++gencounter)+"-"+Date.now():
+ "----NODEMAILER-?=_"+(++gencounter)+"-"+Date.now():
this.content_boundary,
rows = [];
@@ -326,7 +345,7 @@ EmailMessage.prototype.generateBody = function(){
this.attachments[i].contents:
new Buffer(this.attachments[i].contents, "utf-8"),
disposition: "attachment",
- content_id: this.attachments[i].cid || ((++gencounter)+"."+Date.now()+"@"+exports.SMTP.hostname)
+ content_id: this.attachments[i].cid || ((++gencounter)+"."+Date.now()+"@"+this.SERVER.hostname)
};
@@ -339,8 +358,9 @@ EmailMessage.prototype.generateBody = function(){
rows.push("Content-Transfer-Encoding: base64");
rows.push("");
- // rows can't be too long, so base64 string will be cut to 78 char lines
- rows.push(current.contents.toString("base64").replace(/(.{78})/g,"$1\r\n"));
+ // quoted printable rfc says limit of a line is 76 (no say on whether that includes line breaks)
+ // matching by leaving 2 for line break
+ rows.push(current.contents.toString("base64").replace(/.{74}/g,"$&\r\n"));
}
@@ -445,56 +465,85 @@ EmailMessage.prototype.send = function(callback){
return;
}
- // use SMTP
- var smtp = new SMTPClient(exports.SMTP.host, exports.SMTP.port, {
- hostname: exports.SMTP.hostname,
- use_authentication: exports.SMTP.use_authentication,
- user: exports.SMTP.user,
- pass: exports.SMTP.pass,
- ssl: exports.SMTP.ssl,
+ var mail = this;
+ // use SERVER
+ var smtp = new SMTPClient(this.SERVER.host, this.SERVER.port, {
+ hostname: this.SERVER.hostname,
+ use_authentication: this.SERVER.use_authentication,
+ user: this.SERVER.user,
+ pass: this.SERVER.pass,
+ ssl: this.SERVER.ssl,
debug: this.debug
});
-
+ smtp.on("connection_stable", function() {mail.emit.apply(mail,["connection_stable"].concat([].slice.call(arguments)))})
smtp.on("error", function(error){
callback && callback(error, null);
});
-
- var commands = [];
- if(this.fromAddress){
- // should run once
- for(var i=0; i<this.fromAddress.length; i++){
- commands.push("MAIL FROM:<"+this.fromAddress[i]+">");
- }
- }
- if(this.toAddress){
- // should run once
- for(var i=0; i<this.toAddress.length; i++){
- commands.push("RCPT TO:<"+this.toAddress[i]+">");
- }
- }
- commands.push("DATA");
-
- process.nextTick(runCommands);
-
- // performs a waterfall of SMTP commands
- function runCommands(){
- var command = commands.shift();
- if(command){
- smtp.send(command, function(error, message){
- if(!error){
- //console.log("Command '"+command+"' sent, response:\n"+message);
- process.nextTick(runCommands);
- }else{
- //console.log("Command '"+command+"' ended with error\n"+error.message);
+ var i = 0;
+ //concat if you need to do forwards
+ var toAddress = this.toAddress.concat();
+ var fromAddress = this.fromAddress;
+ function nextSender() {
+ if(i === fromAddress.length) {
+ i = 0;
+ nextRecipient();
+ }
+ else {
+ smtp.send("MAIL FROM:<"+fromAddress[i++]+">", function(error, message) {
+ if(error) {
smtp.close();
process.nextTick(function(){
callback && callback(error, null);
});
- }
- });
- }else
- process.nextTick(sendBody);
+ return;
+ }
+ process.nextTick(nextSender);
+ });
+ }
+ }
+ function nextRecipient() {
+ if(i === toAddress.length) {
+ smtp.send("DATA",function(error, message) {
+ process.nextTick(sendBody);
+ });
+ }
+ else {
+ var address = toAddress[i++];
+ smtp.send("RCPT TO:<"+address+">", function(error, message) {
+ if(error) {
+ var forwardAddress;
+ //Empty addresses are valid (for server sent notifications).
+ if(forwardAddress = error.message.match(/^551.*try\s+([<][^>]*[>])/)) {
+ mail.emit("forward",address,forwardAddress[1]);
+ toAddress.splice(i,0,forwardAddress[1])
+ process.nextTick(nextRecipient);
+ return;
+ }
+ //Not all error codes are true failures (we may be able to still send it to other recipients)
+ else if(error.message.match(/^(?:552|451|452|500|503|421)/)){
+ smtp.close();
+ process.nextTick(function(){
+ callback && callback(error, null);
+ });
+ return;
+ }
+ else {
+ mail.emit("retain",address);
+ process.nextTick(nextRecipient);
+ return;
+ }
+ }
+ if(message && /^251/.test(message.test)) {
+ mail.emit("defer",address);
+ }
+ else {
+ mail.emit("send",address);
+ }
+ process.nextTick(nextRecipient);
+ });
+ }
}
+ process.nextTick(nextSender);
// Sends e-mail body to the SMTP server and finishes up
function sendBody(){
View
@@ -159,6 +159,7 @@ this.encodeQuotedPrintable = function(str, mimeWord, charset){
if(!mimeWord){
// lines might not be longer than 76 bytes, soft break: "=\r\n"
var lines = str.split(/\r?\n/);
+ str.replace(/(.{73}(?!\r?\n))/,"$&=\r\n")
for(var i=0, len = lines.length; i<len; i++){
if(lines[i].length>76){
lines[i] = this.foldLine(lines[i],76, false, true).replace(/\r\n/g,"=\r\n");
Oops, something went wrong.

0 comments on commit ff3bb17

Please sign in to comment.