Gathering detailed insights and metrics for mailsplit
Gathering detailed insights and metrics for mailsplit
Gathering detailed insights and metrics for mailsplit
Gathering detailed insights and metrics for mailsplit
npm install mailsplit
Module System
Min. Node Version
Typescript Support
Node Version
NPM Version
23 Stars
89 Commits
11 Forks
6 Watching
1 Branches
6 Contributors
Updated on 14 Sept 2024
JavaScript (99.89%)
Shell (0.11%)
Cumulative downloads
Total Downloads
Last day
-6.3%
135,151
Compared to previous day
Last week
1.3%
789,987
Compared to previous week
Last month
5.4%
3,332,556
Compared to previous month
Last year
25.8%
36,012,060
Compared to previous year
Split an email message stream into structured parts and join these parts back into an email message stream. If you do not modify the parsed data then the rebuilt message should be an exact copy of the original.
This is useful if you want to modify some specific parts of an email, for example to add tracking images or unsubscribe links to the HTML part of the message without changing any other parts of the email.
Supports both <CR><LF> and <LF> (or mixed) line endings. Embedded rfc822 messages are also parsed, in this case you would get two sequential 'node' objects with no 'data' or 'body' in between (first 'node' is for the container node and second for the root node of the embedded message).
In general this module is a primitive for building e-mail parsers/handlers like mailparser. Alternatively you could use it to parse other MIME-like structures, for example mbox files or multipart/form-data uploads.
See rewrite-html.js for an usage example where HTML content is modified on the fly (example script adds a link to every text/html node)
Install from npm
npm install mailsplit --save
Splitter
is a transformable stream where input is a byte stream and output is an object stream.
1let Splitter = require('mailsplit').Splitter; 2let splitter = new Splitter(options);
Where
'data' event emits the next parsed object from the message stream.
'node'
means that we reached the next mime node and the previous one is completely processed'data'
provides us multipart body parts, including boundaries. This data is not directly related to any specific multipart node, basically it includes everything between the end of one normal node and the header of next node'body'
provides us next chunk for the last seen 'node'
element'body'
and 'data'
partsElement with type 'node'
has a bunch of header related methods and properties, see below.
Example
1let Splitter = require('mailsplit').Splitter; 2let splitter = new Splitter(); 3// handle parsed data 4splitter.on('data', data => { 5 switch (data.type) { 6 case 'node': 7 // node header block 8 process.stdout.write(data.getHeaders()); 9 break; 10 case 'data': 11 // multipart message structure 12 // this is not related to any specific 'node' block as it includes 13 // everything between the end of some node body and between the next header 14 process.stdout.write(data.value); 15 break; 16 case 'body': 17 // Leaf element body. Includes the body for the last 'node' block. You might 18 // have several 'body' calls for a single 'node' block 19 process.stdout.write(data.value); 20 break; 21 } 22}); 23// send data to the parser 24someMessagStream.pipe(splitter);
If the data object has type='node'
then you can modify headers for that node (headers can be modified until the data object is passed over to a Joiner
)
You can manipulate specific header keys as well using the headers
object
["Subject: This is subject line"]
)"This is subject line"
)1
as the value, then it will update the second) or if no relative key index is specified, then it will remove all header value matches found for the key and append one at the last matching key index found with the specified value. If a relative key index is specified and it does not exist then it will be replaced (eg if there are two headers of X-Foo-Bar
and you pass 2
, meaning it will update the third, no updates will be made since the third did not exist)Additionally you can check the details of the node with the following properties automatically parsed from the headers:
'attachment'
, 'inline'
or false
if not setJoiner
is a transformable stream where input is the object stream form Splitter
and output is a byte stream.
1let Splitter = require('mailsplit').Splitter; 2let Joiner = require('mailsplit').Joiner; 3let splitter = new Splitter(); 4let joiner = new Joiner(); 5// pipe a message source to splitter, then joiner and finally to stdout 6someMessagStream 7 .pipe(splitter) 8 .pipe(joiner) 9 .pipe(process.stdout);
Rewriter
is a simple helper class to modify nodes that match a filter function. You can pipe a Splitter stream directly into a Rewriter and pipe Rewriter output to a Joiner.
Rewriter takes the following argument:
filterFunc
returns trueOnce Rewriter finds a matching node, it emits the following event:
data
data.node
includes the current node with headersdata.decoder
is the decoder stream that you can read data fromdata.encoder
is the encoder stream that you can write data to. Whatever you write into that stream will be encoded properly and inserted as the content of the current node1let Splitter = require('mailsplit').Splitter; 2let Joiner = require('mailsplit').Joiner; 3let Rewriter = require('mailsplit').Rewriter; 4let splitter = new Splitter(); 5let joiner = new Joiner(); 6let rewriter = new Rewriter(node => node.contentType === 'text/html'); 7rewriter.on('node', data => { 8 // manage headers with node.headers 9 node.headers.add('X-Processed-Time', new Date.toISOString()); 10 // do nothing, just reencode existing data 11 data.decoder.pipe(data.encoder); 12}); 13// pipe a message source to splitter, then rewriter, then joiner and finally to stdout 14someMessagStream 15 .pipe(splitter) 16 .pipe(rewriter) 17 .pipe(joiner) 18 .pipe(process.stdout);
Streamer
is a simple helper class to stream nodes that match a filter function. You can pipe a Splitter stream directly into a Streamer and pipe Streamer output to a Joiner.
Streamer takes the following argument:
filterFunc
returns trueOnce Streamer finds a matching node, it emits the following event:
data
data.node
includes the current node with headers (informational only, you can't modify it)data.decoder
is the decoder stream that you can read data fromdata.done
is a function you must call once you have processed the stream1let Splitter = require('mailsplit').Splitter; 2let Joiner = require('mailsplit').Joiner; 3let Streamer = require('mailsplit').Streamer; 4let fs = require('fs'); 5let splitter = new Splitter(); 6let joiner = new Joiner(); 7let streamer = new Streamer(node => node.contentType === 'image/jpeg'); 8streamer.on('node', data => { 9 // write to file 10 data.decoder.pipe(fs.createWriteStream(data.node.filename || 'image.jpg')); 11 data.done(); 12}); 13// pipe a message source to splitter, then streamer, then joiner and finally to stdout 14someMessagStream 15 .pipe(splitter) 16 .pipe(streamer) 17 .pipe(joiner) 18 .pipe(process.stdout);
Parsing and re-building messages is not fast but it isn't slow either. On my Macbook Pro I got around 22 MB/second (single process, single parsing queue) when parsing random messages from my own email archive. Time spent includes file calls to find and load random messages from disk.
Streaming 20000 random messages through a plain PassThrough
Done. 20000 messages [1244 MB] processed in 10.095 s. with average of 1981 messages/sec [123 MB/s]
Streaming 20000 random messages through Splitter and Joiner
Done. 20000 messages [1244 MB] processed in 55.882 s. with average of 358 messages/sec [22 MB/s]
Dual licensed under MIT or EUPLv1.1+
No vulnerabilities found.
No security vulnerabilities found.