Michael Trojanek (relativkreativ) — Bootstrapper and creator of things

This article was published on September 1st 2014 and takes about 7 minutes to read.

Use it with caution — it is probably still valid, but it has not been updated for over a year.

Advanced use of merge tags to manage subscribers in MailChimp

Learn how to use MailChimp's merge tags to not only save additional lists but to simplify list management if your needs grow more complex.

While building a free email course, I stumpled upon a scenario which I thought was quite common:

Nothing too fancy but it turned out that this is not the easiest thing to do:

After a quick support chat with MailChimp (hello to Desmond if you are listening), I came up with the following solution.


This article is targeted at developers who are already somewhat familiar with how MailChimp works. If you are interested and can't follow, I suggest diving into their excellent documentation first.

I am using MailChimp's API instead of their form builder because I'm a fan of seamless integration. While it may be possible to implement the solution outlined here with their forms, I have no idea how to do it.

Part 1: A subscription model

I have a subscription model in my application whose migration looks like this:

class CreateSubscriptions < ActiveRecord::Migration
  def change
    create_table :subscriptions do |t|
      t.string  "first_name",    null: false
      t.string  "email_address", null: false
      t.boolean "opted_in",      default: false
      t.integer "email_course",  default: 0
      t.integer "newsletter",    default: 1

While first_name and email_address should be pretty self-explanatory, email_course and newsletter are a little more interesting. Both of these fields can have one of three values:

As you may have noticed, the default value for newsletter is 1, so whenever a new subscription is created, it is automatically assigned to the newsletter.

Part 2: A background job

A small Ruby script is run periodically via cron and checks for new subscriptions (a new subscription is defined by having either newsletter == 1 or email_course == 1). If there are any, they are transfered to MailChimp with an API call to their lists/batch-subscribe function and the corresponding values of email_course and/or newsletter are set to 2 (meaning transfered to MailChimp), which is easy because the API call returns a list of email addresses which were successfully added or updated in the following form:

  'adds' => [
    { 'email' => 'someone1@example.com' },
    { 'email' => 'someone2@example.com' }
  'updates' => [
    { 'email' => 'someone3@example.com' }

The trick here is to use a merge tag for subscribing people to the email course:

When the value of email_course is 1, the merge tag ec1 (meaning email course 1) is set to 1 (MailChimp does not support boolean values for merge tags).

So, If you are subscribing two new visitors ("Person 1" just for the newsletter, "Person 2" for newsletter and email course), your request JSON needs to look something like this:

    'email' => { 'email' => 'person1@example.com' },
    'merge_vars' => { 'fname' => 'Person 1' }
    'email' => { 'email' => 'person2@example.com' },
    'merge_vars' => { 'fname' => 'Person 2', 'ec1' => 1 }

In MailChimp's Automation area you can then create a custom workflow, add all emails for the email course, set its trigger to Merge field value changed and configure it to run when the value of ec1 is 1 (this will also trigger for new subscribers with an initial ec1 value of 1).

In case you are wondering why I do this in a background job, I outlined the benefits in a previous article.

Part 3: Webhooks

While it is not strictly necessary, I also use MailChimp's webhooks to be notified of various list events, because I like to keep my local database and MailChimp's in sync.

Webhooks are quite easy to setup - basically you add a WebhooksController and define an action for MailChimp, which has to respond GET (for webhook verification) and POST (for actual data):

class WebhooksController < ApplicationController
  skip_before_action :verify_authenticity_token

  DIGEST = 'abc'

  def mailchimp
    if request.post? && params[:digest] == DIGEST
      subscription = Subscription.find_or_initialize_by(email_address: params[:data][:email])

      case params[:type]
        when 'cleaned'
          # Hard bounces
          subscription.destroy if params[:data][:reason] == 'hard'
        when 'subscribe', 'profile'
          # New subscriptions or updates to existing ones
          subscription.first_name = params[:data][:merges][:FNAME]
          subscription.opted_in = true
          subscription.newsletter = 2
          subscription.email_course = 2 if params[:data][:merges][:EC1] == '1'
        when 'unsubscribe'
          # Unsubscribe
        when 'upemail'
          # People changing their email address
          subscription.email_address = params[:data][:new_email]

    render nothing: true, status: 200

The corresponding routes:

get  '/webhooks/mailchimp/:digest' => 'webhooks#mailchimp'
post '/webhooks/mailchimp/:digest' => 'webhooks#mailchimp'

There are a few things to take note of here:

The benefits of this solution

Get in the loop!

Join my email list to get new articles delivered straight to your inbox.

No spam — guaranteed. You can leave at any time.