J
?
+

Building a Couchapp with SOCA Part 2: Sending Emails From Your Couch

Discover CouchDB’s externals API and how you can utilize it to send emails from your couchapp using Node.js

Getting External

For quite some time now, CouchDB has had the means of communicating with external programs over stdio using JSON. These external programs give couchapps the ability to perform functions that CouchDB does not support natively, like performing outgoing HTTP calls or sending email. CouchDB becomes aware of these programs through several configuration variables inside of the local.ini file. Performing a request to http://localhost:5984/my_db/_my_external will make CouchDB call the external along with any other data you pass on to the request. There are some limitations to external processes, however. Particularly, the data you pass to the external program must be UTF-8 encoded and both the request and the response must be valid JSON. I admit this isn’t all that bad, JSON rules after all, but perhaps the biggest downside to this is that external processes are all synchronous, so in other words, no event-driven pie for you.

Not to fear. Version 1.1.0 will debut the new features in the externals API, namely HTTP proxy handlers and OS daemon handlers. From the looks of it, the new externals API is supposed to replace the currently simple, but limited, externals API. The new API features are hott (yes, that’s a well deserved double ‘t’), allowing one to call external processes in any language and encoding one wishes to employ, start and respawn processes automatically, and log directly to the CouchDB log from these externals, amongst other niceties. And, apparently, you won’t have to restart CouchDB every time you make a change to the external, which is a very welcome addition. If you want to find out more about this, check out this excellent blog post by Paul Joseph Davis, the creator of the new API, here.

Node It Up

A few notes before we really get started. We’ll be creating a folder called external to house our external program’s code, but first we’ll have to prevent CouchDB from including it inside our design document. In your .couchapprc file, create an entry like so:

 "external": false 

Next create the external directory. Make sure you have node.js installed (version 0.4.x as of this writing) along with npm, the node package manager. We’ll be using the ndistro module to handle our dependencies for the mailer, so install it with:

$ npm install ndistro 

Create an .ndistro file in the external directory and enter the following:

module eleith emailjs

We’ll be using the emailjs module, as you can see. Lastly, create a file called mailer.js – now onto some code!

The Node Mailer Script

The mailer script is pretty simple. We first add the lib/node directory so our program can find the email module. Next, we require the necessary modules, open stdin to listen for data. Upon receiving any, we turn the data to JSON format and check that we received all the mandatory fields from the email form. Finally, we attempt to send the email, returning the appropriate response. The code looks like so:

require.paths.unshift(__dirname + "/lib/node");

var email = require('email'),
    sys = require('sys'),
    server = email.server.connect({ host: "localhost" });

var stdin = process.openStdin();

stdin.setEncoding('utf8');

stdin.on('data', function(d) {
    var data = JSON.parse(d);

    var response = function(code, msg) {
        var resp = {
            code: code,
            headers: { 'Content-Type': 'text/plain' },
            body: msg
        };

        return process.stdout.write(JSON.stringify(resp) + '\n');
    }

    if (!(data.form.email && data.form.sender && 
          data.form.message && data.form.subject)) {
        response(500, "Whoops! There was an error sending the email!\n");
    } else {
        server.send({
            text: data.form.message,
            from: data.form.email,
            to: "my@epic-email.com",
            subject: data.form.subject
        }, function(err, msg) {
            if (err) throw err;
            response(200, "Email sent!!\n");
        });
    }
});

Once you test the code and confirm that it works, copy the mailer.js and .ndistro files over to your local.d CouchDB directory (on my machine running Arch Linux, it’s in /etc/couchdb/), and run the ndistro command again. Alternatively, you could just copy the contents of the external directory to local.d. Make sure that the CouchDB user can access these files. Then, add the following configuration to your local.ini file:

[log]
level = debug

[external]
mailer = /usr/bin/node /etc/couchdb/local.d/mailer.js

[httpd_db_handlers]
_mailer = {couch_httpd_external, handle_external_req, <<"mailer">>}

Hop on over to your Sammy.js code and add a route for handling email requests. I’ll assume you already have an email form template that you invoke somewhere before being able to send the email from your app. The code for the route will look something like this:

Restart CouchDB and push your changes with soca push. If everything goes well, you will have your external process for sending emails.

this.post('#/contact', function () {
        var ctx = this,
            data = {
                sender:  this.params.sender,
                subject: this.params.subject,
                email:   this.params.email,
                message: this.params.message
            };

        $.ajax({
            type: 'POST',
            url: '/_mailer',
            data: data,
            success: function(resp) {
                ctx.flashNow('success', resp);
                ctx.clearForm('#contact-form');
            },
            error: function(request, status, error) {
                ctx.flashNow('error', request.responseText);
            }
        });
    });

Mail, Using the New Externals API

As mentioned earlier in the article, version 1.1.0 of CouchDB will be getting the new awesomesauce externals API. If you’re feeling adventurous and want to start playing with it, clone the repo from github and checkout branch 1.1.x, build from source and you’re good to go. Now, for our case, using the new API requires a few simple changes. First, we’ll need to tell our program to listen on a given port, and move the code for sending the email inside the createServer function:

require.paths.unshift(__dirname + "/lib/node");

var email = require('email'),
    http = require('http'),
    sys = require('sys'),
    server = email.server.connect({ host: "localhost" });

var s = http.createServer(function(req, resp) {
    var data;

    var response = function(code, msg) {
    resp.writeHead(code, { 'Content-Type': 'application/plain-text' });
        resp.end(msg);
    }
 
    req.on('data', function(chunk) {
        data = JSON.parse(chunk);
    });

    req.on('end', function () {
        if (!(data.form.email && data.form.sender && 
              data.form.message && data.form.subject)) {
            response(500, "Whoops! There was an error sending the email!\n");
        } else {
            server.send({
                text: data.form.message,
                from: data.form.email,
                to: "Me <my@epic-email.com>",
                subject: data.form.subject
            }, function(err, msg) {
                if (err) throw err;
                response(200, "Email sent!!\n");
            });
        }
    });
});

var stdin = process.openStdin();

stdin.resume();
stdin.setEncoding('utf8');

stdin.on('data', function(d) {
    s.listen(parseInt(JSON.parse(d)));
});

stdin.on('exit', function () {
    process.exit(0);
});

Then, change/add to your local.ini configuration the following:

Restart CouchDB, just to make it aware of the new external. Test it with curl:

If everything went well, you’ll have a shiny new mailer daemon that uses the latest awesomesauce from CouchDB.

[os_daemons]
async_mailer = /etc/couchdb/local.d/async_mailer/mailer.js

[async_mailer]
port = 9000

[httpd_global_handlers]
_async_mailer = {couch_httpd_proxy, handle_proxy_req, <<"http://127.0.0.1:9000">>}

$ curl -X POST http://localhost:5984/_async_mailer -H 'Content-Type: application/json' \ 
-d '{ "form": { "message": "testing", "sender": "lou", "email": "lou@mail.com", \ 
"subject": "testing emailjs from couchdb os daemon" } }'

Conclusion

We notched the difficulty up a bit in this article to discuss how to send emails using CouchDB’s externals API. We saw how to implement a mailer program for both the “old” and “new” externals API using Node.js.

Hide
blog comments powered by Disqus

About This Blog

Just your average hacker blog… with a twist. Why go to great lengths to decorate each article when I'm just talking about code? Simply because epic open–source projects demand that one talk about them with a matching scale of epicness. Or at least they should, and that's my aim here.

To those curious about me, apart from being a web developer and designer, I'm also a vampire. For the stalkers, Fail Whale calls me kuroi_kenshi, Octocat calls me hostsamurai and mere–mortals call me Lou.

Creative Commons License

Contact Me