6. Mailchimp to Salesforce Integration – Create Campaign Members

Business Problem

Mailchimp - Logo

Mailchimp is a great tool for people who are just starting out with email marketing and they currently use Salesforce as their CRM. We have done several implementations of Mailchimp and Salesforce and wanted to provide you with our best practice approach of how to set it up right the first time.

This is Part 6 of how to setup the Mailchimp to Salesforce integration where we will show you how to solve the following business challenge we ran into while implementing Mailchimp for Salesforce. Check out our Mailchimp How To section for additional tips on how to extend this integration.

  • Create Campaign Members & Update Member Statuses from MC Campaigns: This article will show you how to add campaign members to Salesforce campaigns from the Mailchimp subscriber activity records. This is useful if you would like to see the engagement of emails from the Contact in Salesforce under Campaign History or need this data here for reporting purposes.

How to Instructions

How to Create Campaign Members from Mailchimp Subscriber Activity

Our objective here is to setup Mailchimp and Salesforce to automatically add people to campaigns and update their Campaign Member Status based on their engagement with the email. This article piggy backs off of Part 3: Mailchimp to Salesforce Integration - Campaign Sync. If you have not completed the steps in this article, you will run into issues with this automation. You will need to complete Part 3, prior to completing this article.

The Mailchimp package has an object called MC Subscriber Activity which holds the relationship between the MC Subscriber and the MC Campaign. Each time a contact interacts with your email, whether they clicked, opened or unsusbcribed, a new record is written to this object.

We are going to use the records in this object to help us write code that will do the following:

  • Add Campaign Member records to a Salesforce Campaign if they do not already exist.
  • Update the Campaign Member Status each time their engagement changes.

First, make sure the Sync Settings in Mailchimp are set to the following:

  • MC Setup -> Audiences -> Sync Settings -> Choose one of the Keep Activity settings

Now, we need to configure an Apex Class and Apex Trigger that will create and update the Campaign member records. If you are not a developer, do not worry, we are going to provide you with the exact code and steps you need to do in Salesforce. We tried to use flow, but the flow was running into duplicate errors when processing more than one record for the Contact in the same batch. Apex is the only way we can automate this.

You will need to do this in a sandbox environment and deploy the two classes and trigger using change sets to production. Apex Classes and Apex triggers cannot be created directly in production.

  • Go to Setup -> Apex Triggers -> Developer Console
  • Click File -> New -> Apex Trigger
  • Name = MCSubscriberActivityTrigger
  • sObject = MC4SF__MC_Subscriber_Activity__c
  • Click Submit

Copy and paste the code below into this screen:

trigger MCSubscriberActivityTrigger on MC4SF__MC_Subscriber_Activity__c (after insert) {

    MCSubscriberActivityTriggerHandler handler = new MCSubscriberActivityTriggerHandler();

    if(trigger.IsAfter && trigger.IsInsert)
        handler.afterInsert(Trigger.new);
}
  • Click File -> Save
  • You can now close the developer console
  • Go to Setup -> Apex Classes -> New
  • Copy and paste the code below into this screen.
  • Click Save
public class MCSubscriberActivityTriggerHandler{

  public void afterInsert(List<MC4SF__MC_Subscriber_Activity__c> newRecords) {

    Map<String,String> CampaignMemberStatusMap = new Map<String,String>();
    Map<String,CampaignMember> cmMCSAMap = new Map<String,CampaignMember>();
    Map<String,Contact> ctSubscriberMap = new Map<String,Contact>();
    List<Id> contactIdsList = new List<Id>();
    List<Id> campaignIdsList = new List<Id>();
    List<String> contactSubscriberList = new List<String>();
    Set<String> newCampaignMembersSet = new Set<String>();
    Set<String> existingCampaignMembersSet = new Set<String>();
    List<CampaignMember> CampaignMembersToUpdate = new List<CampaignMember>();
    List<CampaignMember> CampaignMembersToInsert = new List<CampaignMember>();

    CampaignMemberStatusMap.Put('open','Opened');
    CampaignMemberStatusMap.Put('click','Clicked');
    CampaignMemberStatusMap.Put('bounce','Bounced');
    CampaignMemberStatusMap.Put('unsub','Unsubscribed');
    CampaignMemberStatusMap.Put('sent','Sent');

    List<MC4SF__MC_Subscriber_Activity__c> subscriberActivityList = [Select Id,MC4SF__MC_Subscriber__c,MC4SF__MC_Campaign__c,MC4SF__MC_Campaign__r.Campaign__c,MC4SF__Action__c FROM MC4SF__MC_Subscriber_Activity__c WHERE Id IN :newRecords];
    for(MC4SF__MC_Subscriber_Activity__c mcsa:subscriberActivityList){
      if(mcsa.MC4SF__MC_Subscriber__c != NULL)
      contactSubscriberList.Add(mcsa.MC4SF__MC_Subscriber__c);
      System.debug(mcsa.MC4SF__MC_Campaign__r.Campaign__c);
      if(mcsa.MC4SF__MC_Campaign__r.Campaign__c != NULL)
      campaignIdsList.Add(mcsa.MC4SF__MC_Campaign__r.Campaign__c);
    }

    for(Contact ct: [Select Id,MC4SF__MC_Subscriber__c FROM Contact WHERE MC4SF__MC_Subscriber__c IN :contactSubscriberList]){
      ctSubscriberMap.Put(ct.MC4SF__MC_Subscriber__c,ct);
      contactIdsList.Add(ct.Id);
    }

    for(CampaignMember cm: [Select Id,ContactId,CampaignId,Status FROM CampaignMember WHERE ContactId IN :contactIdsList AND CampaignId IN :campaignIdsList])
    cmMCSAMap.Put(cm.CampaignId + '_' + cm.ContactId, cm);

    for(MC4SF__MC_Subscriber_Activity__c mcsa:subscriberActivityList){

      //Get the associated contact
      if(ctSubscriberMap.ContainsKey(mcsa.MC4SF__MC_Subscriber__c) && ctSubscriberMap.Get(mcsa.MC4SF__MC_Subscriber__c) != NULL && mcsa.MC4SF__MC_Campaign__c != NULL && mcsa.MC4SF__MC_Campaign__r.Campaign__c != NULL){
        Contact mcsaContact = ctSubscriberMap.Get(mcsa.MC4SF__MC_Subscriber__c);

        //Check if contact belongs to campaign already, if so, update it's status
        if(cmMCSAMap.ContainsKey(mcsa.MC4SF__MC_Campaign__r.Campaign__c + '_' + mcsaContact.Id) && cmMCSAMap.get(mcsa.MC4SF__MC_Campaign__r.Campaign__c + '_' + mcsaContact.Id) != NULL){
          CampaignMember existingCM = cmMCSAMap.get(mcsa.MC4SF__MC_Campaign__r.Campaign__c + '_' + mcsaContact.Id);
          //Update the campaign member only if the status conditions are met and this particular record is not being updated in the same transaction
          if((!(existingCM.Status == 'Clicked' && (mcsa.MC4SF__Action__c=='sent' || mcsa.MC4SF__Action__c=='open'))) &&
            (!existingCampaignMembersSet.Contains(mcsa.MC4SF__MC_Campaign__r.Campaign__c + '_' + mcsaContact.Id))
          ){
            if(CampaignMemberStatusMap.ContainsKey(mcsa.MC4SF__Action__c) && CampaignMemberStatusMap.Get(mcsa.MC4SF__Action__c) != NULL)
            existingCM.Status = CampaignMemberStatusMap.Get(mcsa.MC4SF__Action__c);
            CampaignMembersToUpdate.Add(existingCM);
            existingCampaignMembersSet.Add(mcsa.MC4SF__MC_Campaign__r.Campaign__c + '_' + mcsaContact.Id);
          }
        }
        //Else, add it to the campaign
        else{
          //Check if the record is already part of the current transaction
          if(!newCampaignMembersSet.Contains(mcsa.MC4SF__MC_Campaign__r.Campaign__c + '_' + mcsaContact.Id)){
            CampaignMember newCM = new CampaignMember(CampaignId=mcsa.MC4SF__MC_Campaign__r.Campaign__c,ContactId=mcsaContact.Id);
            if(CampaignMemberStatusMap.ContainsKey(mcsa.MC4SF__Action__c) && CampaignMemberStatusMap.Get(mcsa.MC4SF__Action__c) != NULL)
              newCM.Status=CampaignMemberStatusMap.Get(mcsa.MC4SF__Action__c);
            CampaignMembersToInsert.Add(newCM);
            newCampaignMembersSet.Add(mcsa.MC4SF__MC_Campaign__r.Campaign__c + '_' + mcsaContact.Id);
          }
        }
      }
    }

    if(CampaignMembersToUpdate.size() > 0)
      update CampaignMembersToUpdate;

    if(CampaignMembersToInsert.size() > 0)
      insert CampaignMembersToInsert;
  }
}
  • Repeat the same process to create the test class.
  • Setup -> Apex Classes -> New
  • Copy and paste the code below:
@isTest
public class MCSubscriberActivityTriggerHandler_Test{

  private static testMethod void testCreateSubscriberActivity() {

    List<RecordType> hrecTypeList = [Select Id FROM RecordType WHERE Name='Household Account' AND SObjectType = 'Account'];

    Account newAccount = new Account(Name='Test Account');
    if(hrecTypeList != NULL && hrecTypeList.size() > 0)
    newAccount.RecordTypeId = hrecTypeList[0].Id;
    insert newAccount;

    Contact newContact = new Contact(FirstName='Test',LastName='Contact',AccountId=newAccount.Id);
    insert newContact;

    /*Campaign newCampaign = new Campaign(Name='Test Campaign');
    insert newCampaign;*/

    MC4SF__MC_Campaign__c mcCampaign = new MC4SF__MC_Campaign__c(MC4SF__MailChimp_ID__c='123');
    insert mcCampaign;

    MC4SF__MC_Campaign__c cp = [Select Id,Campaign__c FROM MC4SF__MC_Campaign__c WHERE Id=:mcCampaign.Id];

    CampaignMember newCM = new CampaignMember(CampaignId=cp.Campaign__c,ContactId=newContact.Id,Status='Not Clicked');
    insert newCM;

    MC4SF__MC_List__c mcAudience = new MC4SF__MC_List__c(MC4SF__MailChimp_ID__c='123');
    insert mcAudience;

    MC4SF__MC_Subscriber__c newSubscriber = new MC4SF__MC_Subscriber__c(MC4SF__Email2__c='test@test.com',MC4SF__MC_List__c=mcAudience.Id);
    insert newSubscriber;

    newContact.MC4SF__MC_Subscriber__c=newSubscriber.Id;
    update newContact;

    MC4SF__MC_Subscriber_Activity__c subActivity = new MC4SF__MC_Subscriber_Activity__c(MC4SF__MC_Subscriber__c=newSubscriber.Id,MC4SF__MC_Campaign__c=mcCampaign.Id,MC4SF__Action__c='click');
    insert subActivity;

  }

}

Now that you have all of the code in place, lets review what it is doing.

Each time an MC Subscriber Activity record is created, the code will look to see if the Contact is already in the Campaign on the MC Campaign record. If it is not, it will add the campaign member. If it is, it will check the campaign member status and update it. We have some conditions in place for this because we do not it to overwrite a 'Clicked' status if the Action on the MC Subscrier Activity record is 'open'.

  • Now is a good time to mention that you should have standardized Statuses for Campaign Members. We highly recommend following the steps in our How to Setup Campaigns in Salesforce article. This article will show you how to easily maintain standard statues for all of your campaign types using an awesome free Appexchange package called Campaign Member Status Walloper.

The MC Subscriber Activity record uses the following values in the Action field to tell you what the person did. Here are the definitions and expected values we received from Mailchimp support:

  • open
  • click
  • bounce
  • unsub
  • sent

We do not want to use the above statuses for our email campaigns, we want to use these statuses:

  • Opened
  • Clicked
  • Bounced
  • Unsubscribed
  • Sent

The code contains a map where we transpose the values. You can modify this section of the code if you prefer to have different statuses or want to add more.

Congratulations! You have just completed creating automation to add campaign members to your Salesforce Campaigns. Now its time to test! If you must test this in production because you are already using Mailchimp, then you must deploy these components via a change set to production. If you are not familiar with how to do this, follow the steps below.

How to Deploy Your Components to Production

In the sandbox, go to Setup -> Outbound Change Sets -> New

DeploytoProd1
  • Enter Name - this can be whatever you want
DeploytoProd2

 

  • Click Add
DeploytoProd3
  • Select Component Type = Apex Trigger
  • Check the box next to Apex Trigger = MCSubscriberActivityTrigger
  • Click Add to Change Set
DeploytoProd4
  • Select Component Type = Apex Classes
  • Check the boxes next to Apex Classes = MCSubscriberActivityTriggerHandler and MCSubscriberTriggerHandlerTest
  • Click Add to Change Set
DeploytoProd5

 

 

  • Click Upload
DeploytoProd6
  • Select Production -> Upload
DeploytoProd7

If you do not see Production in this screen, you will need to update the Deployment Settings in production for this sandbox. Follow these steps:

  • Go to Production -> Setup -> Deployment Settings -> Edit next to the sandbox you are deploying from.
UpdateDeploymentSettings1
  • Select Allow Inbound Changes -> Save
UpdateDeploymentSettings2
  • Go to Production -> Setup -> Inbound Change Sets -> Change Sets Awaiting Deployment -> Deploy
  • It may take anywhere from 30 seconds to 5 minutes to see your change set in production. You will receive an email when it is ready to deploy.
DeploytoProd8
  • Select Default -> Deploy
DeploytoProd9
  • The Deployment Details screen will run all of the apex tests and it will show two green circles when it has successfully deployed. You may have a different number in the second green circle, it just depends on the amount of code you have in your org.
DeploytoProd10

Great job! You have just successfully deployed your code to production and now you are ready to make sure everything is working in production. Yay!

 

How to Test the Automation

Since Mailchimp can only be connected to one environment, this testing is probably being done directly in production. Make sure to send the test email to contacts with your own email address.

Follow these steps to test the automation:

  • Create a campaign in MailChimp and send it to at least three recipients that have email addresses you have access to or get someone to help you test. Make sure they are test contacts.
  • Do different actions on the email for each recipient. Ex: #1 - should open the email, #2 - should open and click the email #3 - should not open the email
  • Go to MC Setup tab in Salesforce -> Campaigns -> Click Refresh MC Campaigns button. You should see your test campaign show up in the list.
Refresh MC Campaigns
  • Click the Gear icon in Salesforce on the top right hand side of the screen -> Developer Console
RunMCBatch
  • Select Debug -> Open Execute Anonymous Window
RunMCBatch1
  • Copy and paste the code below into the screen and click Execute. Thanks for this tip Spencer Widman!
  • This step prevents us from having to wait for the daily sync between Mailchimp and Salesforce. By running this line of code in the developer console, we are making the batch process start that triggers the MC Subscriber Activity records to sync from Mailchimp to Salesforce.
Database.executeBatch(new MC4SF.ActivityBatch(), 50);
RunMCBatch2

Give it a minute or two and check your campaign to see if the people were added to it.

If they weren't in the campaign, check the MC Subscriber Activity tab in Salesforce to see if any records came in after running the batch. If there are no records in the tab, confirm the batch ran successfully by checking the Apex Jobs.

You may run into errors if the Campaign__c field on the MC Campaign record is blank. Refer to this article Part 3: Mailchimp to Salesforce Integration - Campaign Sync on how to get this field updated automatically.

If everything worked, then we recommend running one more test with the real sync and then you should be ready to go.

 

Any comments, questions, concerns – let us know in the comments! We would love to hear from you!

Share This Story, Choose Your Platform!

About the Author

Cheryl Fernandes

Cheryl is a certified Salesforce Application Architect and is the Lead Salesforce Consultant at Blu Ninjas. She has been working with Salesforce for 10 years and has helped companies in financial services, insurance and beauty industries implement solutions on the platform. Process builder and flow are her favorite declarative tools, these tools are a game changer for anyone who does not know how to code.

2 thoughts on “6. Mailchimp to Salesforce Integration – Create Campaign Members”

  1. Spencer Widman

    I love your How To’s and was extremely excited to see the recent updates. I am preparing for a Mailchimp implementation and have been building it out in a sandbox. I have rebuilt everything to align with your new guides which are amazing (thank you for all of the work and the amazing writeups). I think I have everything working with the exception of Integration 6 (adding or updating campaign members). Every time the flow runs I get an error that “DUPLICATE_VALUE: Attempted to add an entity ‘0035600000ZvoGz’ to a campaign ‘70156000000Gd4g’ more than once.” I do not see how I am triggering the duplicate record and consequently when it fails no campaign members are created. Have you had anyone else run into this issue? I went back through all 7 guides to make sure I hadn’t made an error anywhere and I do not see any issues. Any ideas? I know that may be a pretty broad question since you cannot actually see my flow. I have narrowed it down to the flow triggering off of the MC Subscriber Activity. When I disable this flow I do not get the error. I did find it odd that when this flow fails, no subscriber activity is brought in. When it is disable the activity is brought in.

    1. Hi Spencer,

      I setup some tests yesterday and was able to recreate your issue. The scenario is: a Subscriber is sent the email and opens or clicks the email within the same day. The process to sync the activity records from Mailchimp to Salesforce only happens once per day. When the this process runs, it tries to create one MC Subscriber Activity record for each action the subscriber took in a single batch. The flow is trying to process both records at the same time and this is the reason for the error. I have made several updates to this article to address this issue. I have run tests and I believe this fixes the issue, but we definitely need to watch what happens closely on a live org. Let me know how the updates go and if the issue has been resolved for you. I have never setup a fault path to reprocess the record like this, so hopefully we do not encounter a CPU limit issue. Our implementation does not go live for a couple of weeks so I have not seen this work with large data volumes yet.

      Thank you for letting us know about this!!

Leave a Reply

Your email address will not be published.

Scroll to top