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.
Table of contents
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.
Disclaimer
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
end
end
end
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 subscribed1
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'
subscription.save
when 'unsubscribe'
# Unsubscribe
subscription.destroy
when 'upemail'
# People changing their email address
subscription.email_address = params[:data][:new_email]
subscription.save
end
end
render nothing: true, status: 200
end
end
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
- otherwisePOST
ing 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 namedec1.1
, a completely new course would be namedec2
.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.
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.