Custom mail subscriptions using MailChimp

MailChimp is a pretty awesome service. In a project I was working on, I wanted to create multiple lists for different types of email newsletters. The website I was using was powered by Express and Node.js. Here's a little bit of code to show how to manage subscriptions for multiple lists.

The first part was creating a sever layer to interact with MailChimp. This service provides access to the lists attached to your account. It also allows you to subscribe to different a lists at the same time.

var Q             = require('q')  
  , _             = require('underscore')
  , MailChimpAPI  = require('mailchimp').MailChimpAPI
  , apikey        = 'MAIL_CHIMP_API_KEY'
  , api             

  , subscribe
  ;


api = new MailChimpAPI(apikey, { version : '2.0' });

/**
 * Retrieves the lists that are available
 * 
 * @param {function} [next] 
 * @return {promise}
 */
exports.lists = function(next) {  
  var deferred = Q.defer();

  api.call('lists', 'list', function(err, result) {
    if(err) deferred.reject(err);
    // result in form 
    // { total: 2, data: [ ], errors: [ ] }
    else deferred.resolve(result.data);
  });  

  return deferred.promise.nodeify(next);
};

/** 
 * Subscribes an email address to the list
 * 
 * @param {object} opts
 * @param {string} opts.id - the list id
 * @param {string} opts.email - the email address
 * @param {string} opts.fname - the first name
 * @param {string} opts.lname - the last name
 * @param {function} [next]
 * @return {promise}
 */
exports.subscribe = subscribe = function(opts, next) {  
  var deferred = Q.defer()
    , params = {}
    ;

  params = {
    id: opts.id,
    email: { 
      email: opts.email 
    },
    merge_vars: {
      fname: opts.fname,
      lname: opts.lname
    },
    double_optin: false,
    update_existing: true
  };

  api.call('lists', 'subscribe', params, function(err, result) {
    if(err) deferred.reject(err);
    else deferred.resolve(result);
  });

  return deferred.promise.nodeify(next);
};

/** 
 * Subscribes an email address to the lists
 *  
 * @param {array} opts.ids - the list of ids
 * @param {string} opts.email - the email address
 * @param {string} opts.fname - the first name
 * @param {string} opts.lname - the last name
 * @param {function} [next]
 * @return {promise}
 */
exports.subscribeMany = function(ids, opts, next) {  
  var funcs;    

  funcs = ids.map(function(id) {
    var newOpts = _.clone(opts);
    newOpts.id = id;
    return function() {      
      return subscribe(newOpts);
    };
  });

  /* jshint es5:false */
  /* jshint newcap:false */
  return funcs
    .reduce(Q.when, Q())
    .nodeify(next);
  /* jshint newcap:true */
};

With the above service in place, I then created an express controller method that accepts calls from the client side, validates the information, and uses the service to perform the appropriate action (GET or POST).

var mailSvc   = require('../mail-service');

/** 
 * Adds the routes to the Express Application
 * 
 * @param {Express} app
 */
exports.addRoutes = function(app) {  
  app.post  ('/api/mail', subscribe);  
};

exports.subscribe = subscribe;

/**
 * API endpoint to subscribes the user to the specified lists
 * Outputs the corresponding JSON response depending on
 * the action taken.
 * 
 * @param {ExpressRequest} req
 * @param {ExpressReponse} res
 */
function subscribe(req, res) {  
  var opts
    , verrors
    , lists
    ;

  req.assert('lists', 'At least one list must be selected').isArray().arrayHasValue();
  req.assert('email', 'Invalid email address').isEmail();
  req.assert('firstname', 'Invalid firstname').notEmpty();
  req.assert('lastname', 'Invalid lastname').notEmpty();

  verrors = req.validationErrors();
  if(verrors) {
    res.status(422).send(verrors);
    return;
  }

  lists = req.body.lists;
  opts = {    
    email: req.body.email,
    fname: req.body.firstname,
    lname: req.body.lastname
  };  

  mailSvc.subscribeMany(lists, opts, function(err, result) {          
    if(err) {       
      res.status(500).send(err);            
    } else {
      res.send(result);
    }
  });
}

You can use then construct a user interface however you would like!

comments powered by Disqus