diff --git a/packages/email/.npm/package/npm-shrinkwrap.json b/packages/email/.npm/package/npm-shrinkwrap.json index 4028b03706c..aebb613b70e 100644 --- a/packages/email/.npm/package/npm-shrinkwrap.json +++ b/packages/email/.npm/package/npm-shrinkwrap.json @@ -1,79 +1,14 @@ { "dependencies": { - "addressparser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/addressparser/-/addressparser-1.0.1.tgz", - "from": "addressparser@1.0.1" - }, - "buildmail": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/buildmail/-/buildmail-4.0.1.tgz", - "from": "buildmail@4.0.1" - }, - "httpntlm": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/httpntlm/-/httpntlm-1.6.1.tgz", - "from": "httpntlm@1.6.1" - }, - "httpreq": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/httpreq/-/httpreq-0.4.23.tgz", - "from": "httpreq@>=0.4.22" - }, - "iconv-lite": { - "version": "0.4.15", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", - "from": "iconv-lite@0.4.15" - }, - "libbase64": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/libbase64/-/libbase64-0.1.0.tgz", - "from": "libbase64@0.1.0" - }, - "libmime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/libmime/-/libmime-3.0.0.tgz", - "from": "libmime@3.0.0" - }, - "libqp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/libqp/-/libqp-1.1.0.tgz", - "from": "libqp@1.1.0" - }, - "mailcomposer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/mailcomposer/-/mailcomposer-4.0.1.tgz", - "from": "mailcomposer@4.0.1" - }, - "nodemailer-fetch": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/nodemailer-fetch/-/nodemailer-fetch-1.6.0.tgz", - "from": "nodemailer-fetch@1.6.0" - }, - "nodemailer-shared": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/nodemailer-shared/-/nodemailer-shared-1.1.0.tgz", - "from": "nodemailer-shared@1.1.0" - }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "from": "punycode@1.4.1" - }, - "smtp-connection": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/smtp-connection/-/smtp-connection-2.12.2.tgz", - "from": "smtp-connection@2.12.2" + "node4mailer": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/node4mailer/-/node4mailer-4.0.2.tgz", + "from": "node4mailer@4.0.2" }, "stream-buffers": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-0.2.5.tgz", "from": "stream-buffers@0.2.5" - }, - "underscore": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", - "from": "underscore@>=1.7.0 <1.8.0" } } } diff --git a/packages/email/email.js b/packages/email/email.js index ae8e652ebae..af3c633d692 100644 --- a/packages/email/email.js +++ b/packages/email/email.js @@ -1,6 +1,6 @@ var Future = Npm.require('fibers/future'); var urlModule = Npm.require('url'); -var SMTPConnection = Npm.require('smtp-connection'); +var nodemailer = Npm.require('node4mailer'); Email = {}; EmailTest = {}; @@ -8,61 +8,60 @@ EmailTest = {}; EmailInternals = { NpmModules: { mailcomposer: { - version: Npm.require('mailcomposer/package.json').version, - module: Npm.require('mailcomposer') + version: Npm.require('node4mailer/package.json').version, + module: Npm.require('node4mailer/lib/mail-composer') + }, + nodemailer: { + version: Npm.require('node4mailer/package.json').version, + module: Npm.require('node4mailer') } } }; -var mailcomposer = EmailInternals.NpmModules.mailcomposer.module; +var MailComposer = EmailInternals.NpmModules.mailcomposer.module; -var makePool = function (mailUrlString) { - var mailUrl = urlModule.parse(mailUrlString); - if (mailUrl.protocol !== 'smtp:' && mailUrl.protocol !== 'smtps:') +var makeTransport = function (mailUrlString) { + var mailUrl = urlModule.parse(mailUrlString, true); + + if (mailUrl.protocol !== 'smtp:' && mailUrl.protocol !== 'smtps:') { throw new Error("Email protocol in $MAIL_URL (" + mailUrlString + ") must be 'smtp' or 'smtps'"); + } - var port = +(mailUrl.port); - var auth = false; - if (mailUrl.auth) { - var parts = mailUrl.auth.split(':', 2); - auth = {user: parts[0], - pass: parts[1]}; + // Allow overriding pool setting, but default to true. + if (!mailUrl.query) { + mailUrl.query = {}; } - var pool = new SMTPConnection({ - port: port, // Defaults to 25 - host: mailUrl.hostname, // Defaults to "localhost" - secure: (port === 465) || (mailUrl.protocol === 'smtps:') - }); - Meteor.wrapAsync(pool.connect, pool)(); - if (auth) { - //_.bind(Future.wrap(pool.login), pool)(auth).wait(); - Meteor.wrapAsync(pool.login, pool)(auth); + if (!mailUrl.query.pool) { + mailUrl.query.pool = 'true'; } - pool._syncSend = Meteor.wrapAsync(pool.send, pool); - return pool; + var transport = nodemailer.createTransport( + urlModule.format(mailUrl)); + + transport._syncSendMail = Meteor.wrapAsync(transport.sendMail, transport); + return transport; }; -var getPool = function() { +var getTransport = function() { // We delay this check until the first call to Email.send, in case someone // set process.env.MAIL_URL in startup code. Then we store in a cache until // process.env.MAIL_URL changes. var url = process.env.MAIL_URL; if (this.cacheKey === undefined || this.cacheKey !== url) { this.cacheKey = url; - this.cache = url ? makePool(url) : null; + this.cache = url ? makeTransport(url) : null; } return this.cache; } -var next_devmode_mail_id = 0; +var nextDevModeMailId = 0; var output_stream = process.stdout; // Testing hooks EmailTest.overrideOutputStream = function (stream) { - next_devmode_mail_id = 0; + nextDevModeMailId = 0; output_stream = stream; }; @@ -70,27 +69,27 @@ EmailTest.restoreOutputStream = function () { output_stream = process.stdout; }; -var devModeSend = function (mc) { - var devmode_mail_id = next_devmode_mail_id++; +var devModeSend = function (mail) { + var devModeMailId = nextDevModeMailId++; var stream = output_stream; // This approach does not prevent other writers to stdout from interleaving. - stream.write("====== BEGIN MAIL #" + devmode_mail_id + " ======\n"); + stream.write("====== BEGIN MAIL #" + devModeMailId + " ======\n"); stream.write("(Mail not sent; to enable sending, set the MAIL_URL " + "environment variable.)\n"); - var readStream = mc.createReadStream(); + var readStream = new MailComposer(mail).compile().createReadStream(); readStream.pipe(stream, {end: false}); var future = new Future; readStream.on('end', function () { - stream.write("====== END MAIL #" + devmode_mail_id + " ======\n"); + stream.write("====== END MAIL #" + devModeMailId + " ======\n"); future.return(); }); future.wait(); }; -var smtpSend = function (pool, mc) { - pool._syncSend(mc.getEnvelope(), mc.createReadStream()); +var smtpSend = function (transport, mail) { + transport._syncSendMail(mail); }; /** @@ -105,28 +104,6 @@ EmailTest.hookSend = function (f) { sendHooks.push(f); }; -// Old comment below -/** - * Send an email. - * - * Connects to the mail server configured via the MAIL_URL environment - * variable. If unset, prints formatted message to stdout. The "from" option - * is required, and at least one of "to", "cc", and "bcc" must be provided; - * all other options are optional. - * - * @param options - * @param options.from {String} RFC5322 "From:" address - * @param options.to {String|String[]} RFC5322 "To:" address[es] - * @param options.cc {String|String[]} RFC5322 "Cc:" address[es] - * @param options.bcc {String|String[]} RFC5322 "Bcc:" address[es] - * @param options.replyTo {String|String[]} RFC5322 "Reply-To:" address[es] - * @param options.subject {String} RFC5322 "Subject:" line - * @param options.text {String} RFC5322 mail body (plain text) - * @param options.html {String} RFC5322 mail body (HTML) - * @param options.headers {Object} custom RFC5322 headers (dictionary) - */ - -// New API doc comment below /** * @summary Send an email. Throws an `Error` on failure to contact mail server * or if mail server returns an error. All fields should match @@ -135,10 +112,9 @@ EmailTest.hookSend = function (f) { * If the `MAIL_URL` environment variable is set, actually sends the email. * Otherwise, prints the contents of the email to standard out. * - * Note that this package is based on mailcomposer version `4.0.1`, so make - * sure to refer to the documentation for that version if using the - * `attachments` or `mailComposer` options. - * [Click here to read the mailcomposer 4.0.1 docs](https://github.com/nodemailer/mailcomposer/blob/v4.0.1/README.md). + * Note that this package is based on **mailcomposer 4**, so make sure to refer to + * [the documentation](https://github.com/nodemailer/mailcomposer/blob/v4.0.1/README.md) + * for that version when using the `attachments` or `mailComposer` options. * * @locus Server * @param {Object} options @@ -150,44 +126,29 @@ EmailTest.hookSend = function (f) { * @param {String} [options.messageId] Message-ID for this message; otherwise, will be set to a random value * @param {String} [options.subject] "Subject:" line * @param {String} [options.text|html] Mail body (in plain text and/or HTML) - * @param {String} [options.watchHtml] Mail body in HTML specific for Apple Watch - * @param {String} [options.icalEvent] iCalendar event attachment + * @param {String} [options.watchHtml] Mail body in HTML specific for Apple Watch + * @param {String} [options.icalEvent] iCalendar event attachment * @param {Object} [options.headers] Dictionary of custom headers * @param {Object[]} [options.attachments] Array of attachment objects, as * described in the [mailcomposer documentation](https://github.com/nodemailer/mailcomposer/blob/v4.0.1/README.md#attachments). - * @param {MailComposer} [options.mailComposer] A [MailComposer](https://github.com/andris9/mailcomposer) - * object (or its `compile()` output) representing the message to be sent. - * Overrides all other options. You can access the `mailcomposer` npm module at - * `EmailInternals.NpmModules.mailcomposer.module`. This module is a function - * which assembles a MailComposer object and immediately `compile()`s it. - * Alternatively, you can create and pass a MailComposer object via - * `new EmailInternals.NpmModules.mailcomposer.module.MailComposer`. + * @param {MailComposer} [options.mailComposer] A [MailComposer](https://nodemailer.com/extras/mailcomposer/#e-mail-message-fields) + * object representing the message to be sent. Overrides all other options. + * You can create a `MailComposer` object via + * `new EmailInternals.NpmModules.mailcomposer.module`. */ Email.send = function (options) { for (var i = 0; i < sendHooks.length; i++) if (! sendHooks[i](options)) return; - var mc; if (options.mailComposer) { - mc = options.mailComposer; - if (mc.compile) { - mc = mc.compile(); - } - } else { - // mailcomposer now automatically adds date if omitted - //if (!options.hasOwnProperty('date') && - // (!options.headers || !options.headers.hasOwnProperty('Date'))) { - // options['date'] = new Date().toUTCString().replace(/GMT/, '+0000'); - //} - - mc = mailcomposer(options); + options = options.mailComposer.mail; } - var pool = getPool(); - if (pool) { - smtpSend(pool, mc); + var transport = getTransport(); + if (transport) { + smtpSend(transport, options); } else { - devModeSend(mc); + devModeSend(options); } }; diff --git a/packages/email/email_tests.js b/packages/email/email_tests.js index 6fe9a309540..97c09776608 100644 --- a/packages/email/email_tests.js +++ b/packages/email/email_tests.js @@ -22,7 +22,7 @@ function canonicalize(string) { // Remove generated content for test.equal to succeed. return string.replace(/Message-ID: <[^<>]*>\r\n/, "Message-ID: <...>\r\n") .replace(/Date: (?!dummy).*\r\n/, "Date: ...\r\n") - .replace(/----[^\s"]+/g, "----..."); + .replace(/(boundary="|^--)--[^\s"]+?(-Part|")/mg, "$1--...$2"); } Tinytest.add("email - fully customizable", function (test) { @@ -55,7 +55,7 @@ Tinytest.add("email - fully customizable", function (test) { "\r\n" + "This is the body\n" + "of the message\n" + - "From us." + + "From us.\r\n" + "====== END MAIL #0 ======\n"); }); }); @@ -108,33 +108,23 @@ Tinytest.add("email - multiple e-mails same stream", function (test) { Tinytest.add("email - using mail composer", function (test) { smokeEmailTest(function (stream) { // Test direct MailComposer usage. - var mcs = [ - // Test with MailComposer object (without compiling). - new EmailInternals.NpmModules.mailcomposer.module.MailComposer({ - from: "a@b.com", - text: "body" - }), - // Test calling module as a function, which compiles MailComposer object. - EmailInternals.NpmModules.mailcomposer.module({ - from: "a@b.com", - text: "body" - }) - ]; - for (var i = 0; i < mcs.length; i++) { - Email.send({mailComposer: mcs[i]}); - test.equal(canonicalize(stream.getContentsAsString("utf8")), - "====== BEGIN MAIL #"+i+" ======\n" + - devWarningBanner + - "Content-Type: text/plain\r\n" + - "From: a@b.com\r\n" + - "Message-ID: <...>\r\n" + - "Content-Transfer-Encoding: 7bit\r\n" + - "Date: ...\r\n" + - "MIME-Version: 1.0\r\n" + - "\r\n" + - "body" + - "====== END MAIL #"+i+" ======\n"); - } + var mc = new EmailInternals.NpmModules.mailcomposer.module({ + from: "a@b.com", + text: "body" + }); + Email.send({mailComposer: mc}); + test.equal(canonicalize(stream.getContentsAsString("utf8")), + "====== BEGIN MAIL #0 ======\n" + + devWarningBanner + + "Content-Type: text/plain\r\n" + + "From: a@b.com\r\n" + + "Message-ID: <...>\r\n" + + "Content-Transfer-Encoding: 7bit\r\n" + + "Date: ...\r\n" + + "MIME-Version: 1.0\r\n" + + "\r\n" + + "body\r\n" + + "====== END MAIL #0 ======\n"); }); }); @@ -181,7 +171,7 @@ Tinytest.add("email - long lines", function (test) { "MIME-Version: 1.0\r\n" + "\r\n" + "This is a very very very very very very very very very very " + - "very very long =\r\ntext" + + "very very long =\r\ntext\r\n" + "====== END MAIL #0 ======\n"); }); }); @@ -208,7 +198,7 @@ Tinytest.add("email - unicode", function (test) { "Date: ...\r\n" + "MIME-Version: 1.0\r\n" + "\r\n" + - "I =E2=99=A5 Meteor" + + "I =E2=99=A5 Meteor\r\n" + "====== END MAIL #0 ======\n"); }); }); @@ -227,24 +217,24 @@ Tinytest.add("email - text and html", function (test) { "====== BEGIN MAIL #0 ======\n" + devWarningBanner + "Content-Type: multipart/alternative;\r\n" + - ' boundary="----..."\r\n' + + ' boundary="--...-Part_1"\r\n' + "From: foo@example.com\r\n" + "To: bar@example.com\r\n" + "Message-ID: <...>\r\n" + "Date: ...\r\n" + "MIME-Version: 1.0\r\n" + "\r\n" + - "----...\r\n" + + "----...-Part_1\r\n" + "Content-Type: text/plain\r\n" + "Content-Transfer-Encoding: 7bit\r\n" + "\r\n" + "*Cool*, man\r\n" + - "----...\r\n" + + "----...-Part_1\r\n" + "Content-Type: text/html\r\n" + "Content-Transfer-Encoding: 7bit\r\n" + "\r\n" + "Cool, man\r\n" + - "----...\r\n" + + "----...-Part_1--\r\n" + "====== END MAIL #0 ======\n"); }); }); diff --git a/packages/email/package.js b/packages/email/package.js index 36f09e16018..7e5167f350a 100644 --- a/packages/email/package.js +++ b/packages/email/package.js @@ -1,17 +1,14 @@ Package.describe({ summary: "Send email messages", - version: "1.2.0" + version: "1.2.1" }); Npm.depends({ - mailcomposer: "4.0.1", - // Using smtp-connection@2 (instead of latest) because it shares - // nodemailer-shared with mailcomposer@4: - "smtp-connection": "2.12.2", - "stream-buffers": "0.2.5"}); + node4mailer: "4.0.2", + "stream-buffers": "0.2.5" +}); Package.onUse(function (api) { - api.use('underscore', 'server'); api.export(['Email', 'EmailInternals'], 'server'); api.export('EmailTest', 'server', {testOnly: true}); api.addFiles('email.js', 'server');