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:

  • I want visitors to be able to sign up for my weekly newsletter using a form which sits at the bottom of each of my articles.
  • When people sign up for my free email couse (which is not ready yet), they should get a series of emails and also be subscribed to my newsletter.
  • When I release a new version of my email course, I want people who already completed it to be able to take it again.
  • Existing subscribers of my newsletter should be able to take my email course when it comes out.

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

  • Maintaining a single newsletter list and eventually sending subscribers the email course cannot be done with groups and segments because MailChimp does not support triggers in the form of subscriber added to group or segment.
  • While synchronizing two separate lists (one for the newsletter and one for the email course) certainly works, it is more trouble than it's worth - unless you want to spam people with confirmation emails, double opt-in has to be suppressed when subscribing people to the email course list. In addition, you have to make sure that people click the confirmation link before sending them the email course (even if they signed up for both lists at once). And suppressing double opt-in is never a good idea.

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:

  • 0 means not subscribed
  • 1 means subscribed (but only in my local database)
  • 2 means transfered to MailChimp

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 digest is a small security action to not leave your door open too wide. This way, posting requests to your webhook controller is at least restricted to sources which know this digest (it is made known to MailChimp as part of the webhook URL, which in my case is http://www.relativkreativ.at/webhooks/mailchimp/abc).
  • Do not forget to turn Rails' CSRF protection off for this controller by using skip_before_action :verify_authenticity_token - otherwise POSTing from outside of your application will not be allowed and there will be no notifications coming through (took me quite some time to figure out why this was happening).
  • You can configure which types of events you want to get notified of when creating the webhook in the admin interface (so you do not have to handle all these cases).

The benefits of this solution

  • This techinque is extensible - with maintainable changes I can support other products people can subscribe to quite easily (another email course, a book, video training - you name it). That's why I named my first email course ec1: An update to this course would be named ec1.1, a completely new course would be named ec2.

  • No matter the number of products, all subscribers live on one list, which keeps things tidy.

  • I can create segments based on the specific value of ec1 and send an email only to people who have taken my email course. Segments are updated automatically.

  • I don't have to pay for double subscriptions (the same email address subscribed to different lists) as it would have been the case with using separate lists.

Get in the loop

Join my email list to get new articles delivered straight to your inbox and discounts on my products.

No spam — guaranteed.

You can unsubscribe at any time.

Got it, thanks a lot!

Please check your emails for the confirmation request I just sent you. Once you clicked the link therein, you will no longer see these signup forms.