Files
meteor/packages/email/email.js
2014-09-04 21:42:03 -07:00

173 lines
5.2 KiB
JavaScript

var Future = Npm.require('fibers/future');
var urlModule = Npm.require('url');
var MailComposer = Npm.require('mailcomposer').MailComposer;
Email = {};
EmailTest = {};
var makePool = function (mailUrlString) {
var mailUrl = urlModule.parse(mailUrlString);
if (mailUrl.protocol !== 'smtp:')
throw new Error("Email protocol in $MAIL_URL (" +
mailUrlString + ") must be 'smtp'");
var port = +(mailUrl.port);
var auth = false;
if (mailUrl.auth) {
var parts = mailUrl.auth.split(':', 2);
auth = {user: parts[0] && decodeURIComponent(parts[0]),
pass: parts[1] && decodeURIComponent(parts[1])};
}
var simplesmtp = Npm.require('simplesmtp');
var pool = simplesmtp.createClientPool(
port, // Defaults to 25
mailUrl.hostname, // Defaults to "localhost"
{ secureConnection: (port === 465),
// XXX allow maxConnections to be configured?
auth: auth });
pool._future_wrapped_sendMail = _.bind(Future.wrap(pool.sendMail), pool);
return pool;
};
// We construct smtpPool at the first call to Email.send, so that
// Meteor.startup code can set $MAIL_URL.
var smtpPoolFuture = new Future;;
var configured = false;
var getPool = function () {
// We check MAIL_URL in case someone else set it in Meteor.startup code.
if (!configured) {
configured = true;
AppConfig.configurePackage('email', function (config) {
// XXX allow reconfiguration when the app config changes
if (smtpPoolFuture.isResolved())
return;
var url = config.url || process.env.MAIL_URL;
var pool = null;
if (url)
pool = makePool(url);
smtpPoolFuture.return(pool);
});
}
return smtpPoolFuture.wait();
};
var next_devmode_mail_id = 0;
var output_stream = process.stdout;
// Testing hooks
EmailTest.overrideOutputStream = function (stream) {
next_devmode_mail_id = 0;
output_stream = stream;
};
EmailTest.restoreOutputStream = function () {
output_stream = process.stdout;
};
var devModeSend = function (mc) {
var devmode_mail_id = next_devmode_mail_id++;
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("(Mail not sent; to enable sending, set the MAIL_URL " +
"environment variable.)\n");
mc.streamMessage();
mc.pipe(stream, {end: false});
var future = new Future;
mc.on('end', function () {
stream.write("====== END MAIL #" + devmode_mail_id + " ======\n");
future['return']();
});
future.wait();
};
var smtpSend = function (pool, mc) {
pool._future_wrapped_sendMail(mc).wait();
};
/**
* Mock out email sending (eg, during a test.) This is private for now.
*
* f receives the arguments to Email.send and should return true to go
* ahead and send the email (or at least, try subsequent hooks), or
* false to skip sending.
*/
var sendHooks = [];
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
* [RFC5322](http://tools.ietf.org/html/rfc5322) specification.
* @locus Server
* @param {Object} options
* @param {String} options.from "From:" address (required)
* @param {String|String[]} options.to,cc,bcc,replyTo
* "To:", "Cc:", "Bcc:", and "Reply-To:" addresses
* @param {String} [options.subject] "Subject:" line
* @param {String} [options.text|html] Mail body (in plain text or HTML)
* @param {Object} [options.headers] Dictionary of custom headers
*/
Email.send = function (options) {
for (var i = 0; i < sendHooks.length; i++)
if (! sendHooks[i](options))
return;
var mc = new MailComposer();
// setup message data
// XXX support attachments (once we have a client/server-compatible binary
// Buffer class)
mc.setMessageOption({
from: options.from,
to: options.to,
cc: options.cc,
bcc: options.bcc,
replyTo: options.replyTo,
subject: options.subject,
text: options.text,
html: options.html
});
_.each(options.headers, function (value, name) {
mc.addHeader(name, value);
});
var pool = getPool();
if (pool) {
smtpSend(pool, mc);
} else {
devModeSend(mc);
}
};