# buildmail Low level rfc2822 message composer that streams output. Define your own mime tree, no magic included. Ported from [MailBuild](https://github.com/whiteout-io/mailbuild) of the [emailjs.org](http://emailjs.org/) project. This port uses similar API but is for Node only and streams the output. ## Usage Install with npm npm install buildmail Require in your scripts ```javascript var BuildMail = require('buildmail'); ``` ## API Create a new `BuildMail` object with ```javascript var builder = new BuildMail(contentType [, options]); ``` Where * **contentType** - define the content type for created node. Can be left blank for attachments (content type derived from `filename` option if available) * **options** - an optional options object * **filename** - *String* filename for an attachment node * **baseBoundary** - *String* shared part of the unique multipart boundary (generated randomly if not set) * **keepBcc** - *Boolean* If true keep the Bcc value in generated headers (default is to remove it) ## Methods The same methods apply to the root node created with `new BuildMail()` and to any child nodes. ### createChild Creates and appends a child node to the node object ```javascript node.createChild(contentType, options) ``` The same arguments apply as with `new BuildMail()`. Created node object is returned. **Example** ```javascript new BuildMail('multipart/mixed'). createChild('multipart/related'). createChild('text/plain'); ``` Generates the following mime tree: ``` multipart/mixed ↳ multipart/related ↳ text/plain ``` ### appendChild Appends an existing child node to the node object. Removes the node from an existing tree if needed. ```javascript node.appendChild(childNode) ``` Where * **childNode** - child node to be appended Method returns appended child node. **Example** ```javascript var childNode = new BuildMail('text/plain'), rootNode = new BuildMail('multipart/mixed'); rootnode.appendChild(childNode); ``` Generates the following mime tree: ``` multipart/mixed ↳ text/plain ``` ## replace Replaces current node with another node ```javascript node.replace(replacementNode) ``` Where * **replacementNode** - node to replace the current node with Method returns replacement node. **Example** ```javascript var rootNode = new BuildMail('multipart/mixed'), childNode = rootNode.createChild('text/plain'); childNode.replace(new BuildMail('text/html')); ``` Generates the following mime tree: ``` multipart/mixed ↳ text/html ``` ## remove Removes current node from the mime tree. Does not make a lot of sense for a root node. ```javascript node.remove(); ``` Method returns removed node. **Example** ```javascript var rootNode = new BuildMail('multipart/mixed'), childNode = rootNode.createChild('text/plain'); childNode.remove(); ``` Generates the following mime tree: ``` multipart/mixed ``` ## setHeader Sets a header value. If the value for selected key exists, it is overwritten. You can set multiple values as well by using `[{key:'', value:''}]` or `{key: 'value'}` structures as the first argument. ```javascript node.setHeader(key, value); ``` Where * **key** - *String|Array|Object* Header key or a list of key value pairs * **value** - *String* Header value Method returns current node. **Example** ```javascript new BuildMail('text/plain'). setHeader('content-disposition', 'inline'). setHeader({ 'content-transfer-encoding': '7bit' }). setHeader([ {key: 'message-id', value: 'abcde'} ``` Generates the following header: ``` Content-type: text/plain Content-Disposition: inline Content-Transfer-Encoding: 7bit Message-Id: ``` ## addHeader Adds a header value. If the value for selected key exists, the value is appended as a new field and old one is not touched. You can set multiple values as well by using `[{key:'', value:''}]` or `{key: 'value'}` structures as the first argument. ```javascript node.addHeader(key, value); ``` Where * **key** - *String|Array|Object* Header key or a list of key value pairs * **value** - *String* Header value Method returns current node. **Example** ```javascript new BuildMail('text/plain'). addHeader('X-Spam', '1'). setHeader({ 'x-spam': '2' }). setHeader([ {key: 'x-spam', value: '3'} ]); ``` Generates the following header: ``` Content-type: text/plain X-Spam: 1 X-Spam: 2 X-Spam: 3 ``` ## getHeader Retrieves the first mathcing value of a selected key ```javascript node.getHeader(key) ``` Where * **key** - *String* Key to search for **Example** ```javascript new BuildMail('text/plain').getHeader('content-type'); // text/plain ``` ## buildHeaders Builds the current header info into a header block that can be used in an e-mail ```javascript var headers = node.buildHeaders() ``` **Example** ```javascript new BuildMail('text/plain'). addHeader('X-Spam', '1'). setHeader({ 'x-spam': '2' }). setHeader([ {key: 'x-spam', value: '3'} ]).buildHeaders(); ``` returns the following String ``` Content-Type: text/plain X-Spam: 3 Date: Sat, 21 Jun 2014 10:52:44 +0000 Message-Id: <1403347964894-790a5296-0eb7c7c7-6440334f@localhost> MIME-Version: 1.0 ``` If the node is the root node, then `Date` and `Message-Id` values are generated automatically if missing ## setContent Sets body content for current node. If the value is a string and Content-Type is text/* then charset is set automatically. If the value is a Buffer or a Stream you need to specify the charset yourself. ```javascript node.setContent(body) ``` Where * **body** - *String|Buffer|Stream|Object* body content If the value is an object, it should include one of the following properties * **path** - path to a file that will be used as the content * **href** - URL that will be used as the content **Example** ```javascript new BuildMail('text/plain').setContent('Hello world!'); new BuildMail('text/plain; charset=utf-8').setContent(fs.createReadStream('message.txt')); ``` ## build Builds the rfc2822 message from the current node. If this is a root node, mandatory header fields are set if missing (Date, Message-Id, MIME-Version) ```javascript node.build(callback) ``` Callback returns the rfc2822 message as a Buffer **Example** ```javascript new BuildMail('text/plain').setContent('Hello world!').build(function(err, mail){ console.log(mail.toString('ascii')); }); ``` Returns the following string: ``` Content-type: text/plain Date: Message-Id: MIME-Version: 1.0 Hello world! ``` ## createReadStream If you manage large attachments you probably do not want to generate but stream the message. ```javascript var stream = node.createReadStream(options) ``` Where * **options** - *Object* optional Stream options (ie. `highWaterMark`) **Example** ```javascript var message = new BuildMail(); message.addHeader({ from: 'From ', to: 'receiver1@example.com', cc: 'receiver2@example.com' }); message.setContent(fs.createReadStream('message.txt')); message.createReadStream().pipe(fs.createWriteStream('message.eml')); ``` ## transform If you want to modify the created stream, you can add transform streams that the output will be piped through. ```javascript node.transform(transformStream) ``` Where * **transformStream** - *Stream* or *Function* Transform stream that the output will go through before returing with `createReadStream`. If the value is a function the function should return a transform stream object when called. **Example** ```javascript var PassThrough = require('stream').PassThrough; var message = new BuildMail(); message.addHeader({ from: 'From ', to: 'receiver1@example.com', cc: 'receiver2@example.com' }); message.setContent(fs.createReadStream('message.txt')); message.transform(new PassThrough()); // add a stream that the output will be piped through message.createReadStream().pipe(fs.createWriteStream('message.eml')); ``` ## setEnvelope Set envelope object to use. If one is not set, it is generated based ong the headers. ```javascript node.setEnvelope(envelope) ``` Where * **envelope** is an envelope object in the form of `{from:'address', to: ['addresses']}` ## getEnvelope Generates a SMTP envelope object. Makes sense only for root node. ```javascript var envelope = node.generateEnvelope() ``` Method returns the envelope in the form of `{from:'address', to: ['addresses']}` **Example** ```javascript new BuildMail(). addHeader({ from: 'From ', to: 'receiver1@example.com', cc: 'receiver2@example.com' }). getEnvelope(); ``` Returns the following object: ```json { 'from': 'from@example.com', 'to': ['receiver1@example.com', 'receiver2@example.com'] } ``` ## getAddresses Returns an address container object. Includes all parsed addresses from From, Sender, To, Cc, Bcc and Reply-To fields. While `getEnvelope()` returns 'from' value as a single address (the first one encountered) then `getAddresses` return all values as arrays, including `from`. Additionally while `getEnvelope` returns only `from` and a combined `to` value then `getAddresses` returns all fields separately. Possbile return values (all arrays in the form of `[{name:'', address:''}]`): * **from** * **sender** * **'reply-to'** * **to** * **cc** * **bcc** If no addresses were found for a particular field, the field is not set in the response object. **Example** ```javascript new BuildMail(). addHeader({ from: 'From ', to: '"Receiver" receiver1@example.com', cc: 'receiver2@example.com' }). getAddresses(); ``` Returns the following object: ```javascript { from: [{ name: 'From', address: 'from@example.com' }], to: [{ name: 'Receiver', address: 'receiver1@example.com' }], cc: [{ name: '', address: 'receiver2@example.com' }] } ``` ## Notes ### Addresses When setting address headers (`From`, `To`, `Cc`, `Bcc`) use of unicode is allowed. If needed the addresses are converted to punycode automatically. ### Attachments For attachments you should minimally set `filename` option and `Content-Disposition` header. If filename is specified, you can leave content type blank - if content type is not set, it is detected from the filename. ```javascript new BuildMail('multipart/mixed'). createChild(false, {filename: 'image.png'}). setHeader('Content-Disposition', 'attachment'); ``` Obviously you might want to add `Content-Id` header as well if you want to reference this attachment from the HTML content. ### MIME structure Most probably you only need to deal with the following multipart types when generating messages: * **multipart/alternative** - includes the same content in different forms (usually text/plain + text/html) * **multipart/related** - includes main node and related nodes (eg. text/html + referenced attachments). Also requires a `type` parameter that indicates the Content-Type of the *root* element in the node * **multipart/mixed** - includes other multipart nodes and attachments, or single content node and attachments **Examples** One content node and an attachment ``` multipart/mixed ↳ text/plain ↳ image/png ``` Content node with referenced attachment (eg. image with `Content-Type` referenced by `cid:` url in the HTML) ``` multipart/related ↳ text/html ↳ image/png ``` Plaintext and HTML alternatives ``` multipart/alternative ↳ text/html ↳ text/plain ``` One content node with referenced attachment and a regular attachment ``` multipart/mixed ↳ multipart/related ↳ text/plain ↳ image/png ↳ application/x-zip ``` Alternative content with referenced attachment for HTML and a regular attachment ``` multipart/mixed ↳ multipart/alternative ↳ text/plain ↳ multipart/related ↳ text/html ↳ image/png ↳ application/x-zip ``` ## License **MIT**