Implementing plugins

There are 3 stages a plugin can hook to
1. ‘compile’ is the step where e-mail data is set but nothing has been done with it yet. At this step you can modify mail options, for example modify html content, add new headers etc. Example: nodemailer-markdown that allows you to use markdown source instead of text and html.
2. ‘stream’ is the step where message tree has been compiled and is ready to be streamed. At this step you can modify the generated MIME tree or add a transform stream that the generated raw e-mail will be piped through before passed to the transport object. Example: nodemailer-dkim that adds DKIM signature to the generated message.
3. Transport step where the raw e-mail is streamed to destination. Example: nodemailer-smtp-transport that streams the message to a SMTP server.

Including plugins

‘compile’ and ‘stream’ plugins can be attached with use(plugin) method

transporter.use(step, pluginFunc)

Where
transporter is a transport object created with createTransport
step is a string, either ‘compile’ or ‘stream’ that defines when the plugin should be hooked
pluginFunc is a function that takes two arguments: the mail object and a callback function

Plugin API

All plugins (including transports) get two arguments, the mail object and a callback function.

Mail object that is passed to the plugin function as the first argument is an object with the following properties:
data is the mail data object that is passed to the sendMail method
message is the BuildMail object of the message. This is available for the ‘stream’ step and for the transport but not for ‘compile’.
resolveContent is a helper function for converting Nodemailer compatible stream objects into Strings or Buffers

resolveContent()

If your plugin needs to get the full value of a param, for example the String value for the html content, you can use resolveContent() to convert Nodemailer compatible content objects to Strings or Buffers.

data.resolveContent(obj, key, callback)

Where
obj is an object that has a property you want to convert to a String or a Buffer
key is the name of the property you want to convert
callback is the callback function with (err, value) where value is either a String or Buffer, depending on the input

Example

function plugin(mail, callback){
    // if mail.data.html is a file or an url, it is returned as a Buffer
    mail.resolveContent(mail.data, 'html', function(err, html){
        if(err){
            return callback(err);
        }
        console.log('HTML contents: %s', html.toString());
        callback();
    });
};

‘compile’

Compile step plugins get only the mail.data object but not mail.message in the mail argument of the plugin function. If you need to access the mail.message as well use ‘stream’ step instead.

This is really straightforward, your plugin can modify the mail.data object at will and once everything is finished run the callback function. If the callback gets an error object as an argument, then the process is terminated and the error is returned to the sendMail callback.

Example

The following plugin checks if text value is set and if not converts html value to text by removing all html tags.

transporter.use('compile', function(mail, callback){
    if(!mail.text && mail.html){
        mail.text = mail.html.replace(/<[^>]*>/g, ' ');
    }
    callback();
});

See plugin-compile.js for a working example.

‘stream’

Streaming step is invoked once the message structure is built and ready to be streamed to the transport. Plugin function still gets mail.data but it is included just for the reference, modifying it should not change anything (unless the transport requires something from the mail.data, for example mail.data.envelope).

You can modify the mail.message object as you like, the message is not yet streaming anything (message starts streaming when the transport calls mail.message.createReadStream()).

In most cases you might be interested in the message.transform() method for applying transform streams to the raw message.

Example

The following plugin replaces all tabs with spaces in the raw message.

var transformer = new (require('stream').Transform)();
transformer._transform = function(chunk, encoding, done) {
    // replace all tabs with spaces in the stream chunk
    for(var i = 0; i < chunk.length; i++){
        if(chunk[i] === 0x09){
            chunk[i] = 0x20;
        }
    }
    this.push(chunk);
    done();
};

transporter.use('stream', function(mail, callback){
    // apply output transformer to the raw message stream
    mail.message.transform(transformer);
    callback();
});

See plugin-stream.js for a working example.

Additionally you might be interested in the message.getAddresses() method that returns the contents for all address fields as structured objects.

Example

The following plugin prints address information to console.

transporter.use('stream', function(mail, callback){
    var addresses = mail.message.getAddresses();
    console.log('From: %s', JSON.stringify(addresses.from));
    console.log('To: %s', JSON.stringify(addresses.to));
    console.log('Cc: %s', JSON.stringify(addresses.cc));
    console.log('Bcc: %s', JSON.stringify(addresses.bcc));
    callback();
});

Transports

Transports are objects that have a method send and properies name and version. Additionally, if the transport object is an Event Emitter, ‘log’ events are piped through Nodemailer. A transport object is passed to the nodemailer.createTransport(transport) method to create the transporter object.

transport.name

This is the name of the transport object. For example ‘SMTP’ or ‘SES’ etc.

transport.name = require('package.json').name;

transport.version

This should be the transport module version. For example ‘0.1.0’.

transport.version = require('package.json').version;

transport.send(mail, callback)

This is the method that actually sends out e-mails. The method is basically the same as ‘stream’ plugin functions. It gets two arguments: mail and a callback. To start streaming the message, create the stream with mail.message.createReadStream()

Callback function should return an info object as the second arugment. This info object should contain messageId value with the Message-Id header (without the surrounding < > brackets)

The following example pipes the raw stream to the console.

transport.send = function(mail, callback){
    var input = mail.message.createReadStream();
    var messageId = (mail.message.getHeader('message-id') || '').replace(/[<>\s]/g, '');
    input.pipe(process.stdout);
    input.on('end', function() {
        callback(null, {
            messageId: messageId
        });
    });
};

transport.close(args*)

If your transport needs to be closed explicitly, you can implement a close method.

This is purely optional feature and only makes sense in special contexts (eg. closing a SMTP pool).

transport.isIdle()

If your transport is able to notify about idling state by issuing 'idle' events then this method should return if the transport is still idling or not.

Wrapping up

Once you have a transport object, you can create a mail transporter out of it.

var nodemailer = require('nodemailer');
var transport = require('some-transport-method');
var transporter = nodemailer.createTransport(transport);
transporter.sendMail({mail data});

See minimal-transport.js for a working example.