Basic email support

This commit is contained in:
David Glasser
2012-08-16 19:41:36 -07:00
parent 46d296450c
commit e86d282354
7 changed files with 168 additions and 10 deletions

View File

@@ -20,10 +20,9 @@
// matches exclude, and if it doesn't exist yet, you should watch for
// it to appear)
//
// The application launcher is expected to execute /main.js with node,
// setting the PORT and MONGO_URL environment variables. The enclosed
// node application is expected to do the rest, including serving
// /static.
// The application launcher is expected to execute /main.js with node, setting
// various environment variables (such as PORT and MONGO_URL). The enclosed node
// application is expected to do the rest, including serving /static.
var files = require('./files.js');
var packages = require('./packages.js');
@@ -578,6 +577,7 @@ _.extend(Bundle.prototype, {
" $ npm install fibers\n" +
" $ export MONGO_URL='mongodb://user:password@host:port/databasename'\n" +
" $ export ROOT_URL='http://example.com'\n" +
" $ export MAIL_URL='smtp://user:password@mailhost:port/'\n" +
" $ node main.js\n" +
"\n" +
"Use the PORT environment variable to set the port where the\n" +

View File

@@ -66,8 +66,9 @@ var run = function () {
// check environment
var port = process.env.PORT ? parseInt(process.env.PORT) : 80;
var mongo_url = process.env.MONGO_URL;
if (!mongo_url)
// check for a valid MongoDB URL right away
if (!process.env.MONGO_URL)
throw new Error("MONGO_URL must be set in environment");
// webserver
@@ -88,9 +89,6 @@ var run = function () {
Fiber(function () {
// (put in a fiber to let Meteor.db operations happen during loading)
// pass in database info
__meteor_bootstrap__.mongo_url = mongo_url;
// load app code
_.each(info.load, function (filename) {
var code = fs.readFileSync(path.join(bundle_dir, filename));

View File

@@ -539,6 +539,9 @@ server, we can recommend our friends at [MongoHQ](http://mongohq.com).
$ PORT=3000 MONGO_URL=mongodb://localhost:27017/myapp node bundle/main.js
Other packages may require other environment variables (for example, the `email`
package requires a `MAIL_URL` environment variable).
{{#warning}}
For now, bundles will only run on the platform that the bundle was
created on. To run on a different platform, you'll need to rebuild

111
packages/email/email.js Normal file
View File

@@ -0,0 +1,111 @@
Email = {};
(function () {
var Future = __meteor_bootstrap__.require('fibers/future');
// js2-mode AST blows up when parsing 'future.return()', so alias.
Future.prototype.ret = Future.prototype.return;
var urlModule = __meteor_bootstrap__.require('url');
var MailComposer = __meteor_bootstrap__.require('mailcomposer').MailComposer;
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 = __meteor_bootstrap__.require('simplesmtp');
return simplesmtp.createClientPool(
port, // Defaults to 25
mailUrl.hostname, // Defaults to "localhost"
{ secureConnection: (port === 465),
// XXX allow maxConnections to be configured?
auth: auth });
};
var smtpPool = null;
if (process.env.MAIL_URL) {
smtpPool = makePool(process.env.MAIL_URL);
};
Email._next_devmode_mail_id = 0;
// Overridden by tests.
Email._output_stream = process.stdout;
var devModeSend = function (mc) {
var devmode_mail_id = Email._next_devmode_mail_id++;
// This approach does not prevent other writers to stdout from interleaving.
Email._output_stream.write("====== BEGIN MAIL #" + devmode_mail_id +
" ======\n");
mc.streamMessage();
mc.pipe(Email._output_stream, {end: false});
var future = new Future;
mc.on('end', function () {
Email._output_stream.write("====== END MAIL #" + devmode_mail_id +
" ======\n");
future.ret();
});
future.wait();
};
var smtpSend = function (mc) {
var future = new Future;
smtpPool.sendMail(mc, function (err, responseObj) {
future.ret([err, responseObj]);
});
var errAndResponse = future.wait();
// XXX figure out error handling
if (errAndResponse[0])
throw errAndResponse[0];
console.log(errAndResponse[1]);
};
/**
* Send an email.
*
* Connects to the mail server configured via the MAIL_URL environment
* variable. If unset, prints formatted message to stdout. May yield.
*
* @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)
*/
Email.send = function (options) {
var mc = new MailComposer();
// setup message data
// XXX support HTML body
// XXX support attachments
// XXX support arbitrary headers
mc.setMessageOption({
from: options.from,
to: options.to,
cc: options.cc,
bcc: options.bcc,
replyTo: options.replyTo,
subject: options.subject,
text: options.text
});
if (smtpPool) {
smtpSend(mc);
} else {
devModeSend(mc);
}
};
})();

View File

@@ -0,0 +1,33 @@
streamBuffers = __meteor_bootstrap__.require('stream-buffers');
Tinytest.add("email - dev mode smoke test", function (test) {
var old_stream = Email._output_stream;
try {
Email._output_stream = new streamBuffers.WritableStreamBuffer;
Email._next_devmode_mail_id = 0;
Email.send({
from: "foo@example.com",
to: "bar@example.com",
cc: ["friends@example.com", "enemies@example.com"],
subject: "This is the subject",
text: "This is the body\nof the message\nFrom us."
});
// XXX brittle if mailcomposer changes header order, etc
test.equal(Email._output_stream.getContentsAsString("utf8"),
"====== BEGIN MAIL #0 ======\n" +
"MIME-Version: 1.0\r\n" +
"From: foo@example.com\r\n" +
"To: bar@example.com\r\n" +
"Cc: friends@example.com, enemies@example.com\r\n" +
"Subject: This is the subject\r\n" +
"Content-Type: text/plain; charset=utf-8\r\n" +
"Content-Transfer-Encoding: quoted-printable\r\n" +
"\r\n" +
"This is the body\r\n" +
"of the message\r\n" +
"From us.\r\n" +
"====== END MAIL #0 ======\n");
} finally {
Email._output_stream = old_stream;
}
});

13
packages/email/package.js Normal file
View File

@@ -0,0 +1,13 @@
Package.describe({
summary: "Send email messages"
});
Package.on_use(function (api) {
api.add_files('email.js', 'server');
});
Package.on_test(function (api) {
api.use('email', 'server');
api.use('tinytest');
api.add_files('email_tests.js', 'server');
});

View File

@@ -17,4 +17,4 @@ _.extend(Meteor._RemoteCollectionDriver.prototype, {
// singleton
// XXX kind of hacky
Meteor._RemoteCollectionDriver = new Meteor._RemoteCollectionDriver(__meteor_bootstrap__.mongo_url);
Meteor._RemoteCollectionDriver = new Meteor._RemoteCollectionDriver(process.env.MONGO_URL);