Automated Multi-Wave Campaigns In Salesforce Marketing (Summer ’09 part 2)
Jun 16, 2009
Have you ever wanted to automatically email prospects or
customers based on rules you set up?
Ever wanted to email both leads and contacts without two separate
templates and processes? In Summer ’09,
it’s as easy as creating a few campaign member workflow rules!
In part
1, we walked through how triggers can be used to summarize campaign
information. In this example, we’ll show
you how two workflow rules and a trigger can be used to auto-email a second
reminder to non-respondents. If any
pieces seem tricky, your SFDC Administrator can help you set up these steps.
Continuing our example, Whitney has already created a parent campaign “2009-Q3-Webinar” and a child campaign “2009-Q3-Webinar-Email Reminder”. Both of these campaigns include the Status "No Response-2nd Email".
Whitney can work with her SFDC admin to create a
workflow email alert to auto-email anyone that is added to the campaign. However, we only want those that haven’t registered for the webinar to
receive this second email. With Summer ’09, it’s now easy to auto-email
campaign members with a workflow email alert, regardless of whether they are a
lead or contact!
We’ll walk through the key steps to set up this
multi-campaign automation:
1) Create
the time–dependent workflow rule, which delays the action by seven days
2) Create
an after update trigger, which clones this campaign member to the “Reminder
Email” campaign when the workflow fires
3) Create
a workflow email alert, which auto-emails campaign members added to the
“Reminder Email” campaign
Step 1: Workflow rule fires seven days after first email sent
First, we want to create a rule that fires seven days after
the person was first emailed, which in this case happened as soon as they were
added to the “TechCo Webinar Email” campaign.
To create this workflow rule, navigate to Setup-->Create-->Workflow
& Approvals-->Workflow
Rules-->Workflow
Rule-->New
Rule, then select Campaign Member.
Next, select the Campaign Member object:
After naming the rule, it’s time to set the rule criteria. For this rule, we want the seven-day delay to
start counting from when the record was created, so under Evaluation Criteria,
we select “Only when a record is created.”
For the Rule Criteria, we select only members added to the campaign with
a status of “Sent” and Campaign Name equal to “2009-Q3-Webinar”—the name of our campaign.
Now that you have specified the conditions for the workflow
rule to fire, specify what happens when it fires, and when. In this example, we set the “when”, by
clicking “Add Time Trigger, ” then entering seven days after the campaign
member created date.
Next, set the action that will happen when the trigger fires. In this case, we want it to update the Status
to “No Response–2nd Email.” From “Add Workflow Action,” we select
“New Field Update” and enter our criteria, then click “Save.”
Back on the Edit Rule page, click Done. Finally, activate
the rule by clicking “Activate.”
![]()

Step 2: Trigger
clones non-respondents to new campaign
Phew! Now you might be wondering one of two questions:
1) Why can’t I just use an email alert on this campaign once the status changes?
2) What happens if someone already registered – won’t we be overwriting the status??
The reason we can’t simply create two workflow rules is that
workflow cannot trigger workflow upon save.
We get around that problem by using this trigger to add the person to a
new campaign. The added bonus is that
it’s a cleaner record of who was emailed and who wasn’t.
Our trigger resolves the second question as it checks if the
status before the update was Sent, and if the new status is No Response–2nd
Email. If so, it puts this prospect into
a second campaign called “2009-Q3-Webinar-Email Reminder.” If not, it reverts the status, as we don’t
want to overwrite RSVP-Yes statuses!
trigger CloneCampaignMember on CampaignMember (before update) {
for (Integer i = 0; i <
Trigger.new.size(); i++) {
if
(Trigger.old[i].Status == 'Sent' && Trigger.new[i].Status == 'No
Response-2nd Email') {
CampaignMember nurtureCM= Trigger.new[i].clone(false);
try{ //try lets the trigger recover from a problem
- such as if the campaign isn't found
Campaign c =
[SELECT Id FROM Campaign WHERE Name = '2009-Q3-Webinar-Email Reminder'];//pick
the right campaign
nurtureCM.CampaignID = c.Id; //assign this campaign to the new member
nurtureCM.Status = 'No Response-2nd Email'; //set status for the new member
}catch(Exception
e){ // this 'catches' an error if the insert fails so that an ugly message
doesn't occur
System.debug('Campaign
named 2009-Q3-Webinar-Email Reminder does not exist: ' + e); //this prints the
error to the system log
}//try
try{
insert
nurtureCM; //Copy the newmember to the database
}catch(DMLException
e){ // this 'catches' an error if the insert fails so that an ugly message
doesn't occur
System.debug('Campaign Member insert failed: ' + e); //this prints the
error to the system log
}//try
}else {
if (Trigger.new[i].Status
== 'No Response-2nd Email'){
try{ //try lets the trigger recover from a problem
CampaignMember revertStatusCM= Trigger.new[i].clone(false);
revertStatusCM.Status=Trigger.old[i].status;
update revertStatusCM;
}catch(DMLException e){ // this 'catches' an error if the insert fails
so that an ugly message doesn't occur
System.debug('Campaign Member status reversion failed:' + e); //this
prints the error to the system log
}//try
}//if 2
}//if 1
}//for
}//trigger
Also, add a test case for the trigger. Note this code below is not best practice as it doesn't have any system.asserts, which actually validate that the code is working as expected:
@isTest
private class Summer09AutomatedCampaignsTests{
static testMethod void verifyCloneTrigger(){
// Perform our data preparation.
List<Lead> leads = new List<lead>{};
for(Integer i = 0; i < 10; i++){
Lead l = new Lead(LastName = 'Test Lead ' + i,Company='Company '+i);
leads.add(l);
}
Campaign c= new Campaign(Name='2009-Q3-Event-Dreamforce 2009');
insert c;
Campaign c2= new Campaign(Name='2009-Q3-Event-Dreamforce 2009-Email Reminder');
insert c2;
campaign c3= [SELECT Id FROM Campaign WHERE Id= '70130000000AZVJ'];
List<CampaignMember> campaignmembers = new List<CampaignMember>{};
// Start the test, this changes governor limit context to
// that of trigger rather than test.
// Insert the Account records that cause the trigger to execute.
insert leads;
for(Integer i = 0; i < 10; i++){
Id LeadIdi=leads[i].id;
CampaignMember cm = new CampaignMember(CampaignID = c3.Id,LeadId = LeadIdi,Status='Sent');
campaignmembers.add(cm);
}
insert campaignmembers;
test.startTest();
for(Integer i = 0; i < 10; i++){
campaignmembers[i].status='Sent';
}
update campaignmembers;
for(Integer i = 0; i < 10; i++){
campaignmembers[i].status='No Response - 2nd Email';
}
update campaignmembers;
for(Integer i = 0; i < 10; i++){
campaignmembers[i].status='RSVP-Yes';
}
update campaignmembers;
// Stop the test, this changes limit context back to test from trigger.
test.stopTest();
// Query the database for the newly inserted records.
List<Lead> insertedLeads = [SELECT LastName, Company,LeadSource
FROM Lead
WHERE Id IN :leads];
// Assert that the Description fields contains the proper value now.
/* for(l :insertedLeads){
System.assertEquals(
null,
l.LeadSource);
}
*/
}
}
Step 3: Workflow
auto-emails a reminder
Now that the members are set to be cloned, the last step is
to create an email alert workflow rule.
Similar to the above, first we create the rule, which we want to fire
when the member is added to the Email Reminder campaign with a status of “No
Response-2nd Email”:
Then we set the action – what we want to happen when this
rule fires. We want to send an email, so
we pick “New Email Alert.”

We select our reminder email template, and
under Recipient Type, select “Email Field.”
The field “Email Field:Email” is the Lead/Contact email address, which
is the one we want. Finally we can send
one email to both leads and contacts at once!
To finish, click “Done” then click “Activate” to activate the
rule.
The Payoff
With this automated flow set up, leads or contacts added to
the campaign, who still have the status of Sent after seven days, will be sent
a reminder email. This is one example of
how you can use the building blocks of campaign member triggers and workflow to
create multi-wave campaign. You could
copy and tweak any of the steps above to accomplish more complex flows, such a
series of nurturing emails sent to new leads that haven’t been converted into
opportunities. Also, once the first flow
is built, you can easily clone the pieces or change the filters for your next
campaign!
In the next post, you’ll see how to avoid lead dupes by
leveraging Salesforce Sites as your campaign member registration pages!

We are unable to add the value to the Campaign Member Status Picklist as described. It only allows Responded or Sent, but has not way of allowing additional values to be populated in the list.
Posted by: cmsproducts | June 25, 2009 at 04:10 PM
To add or change Campaign Member Status values that are available, you go to the Campaign, and click on "Advanced Setup".
You can't add them directly from the setup tab as the Campaign determines which status values are available vs. one set for all campaigns. Send me a note if you have any problems:
jkucera at salesforce dot com
Posted by: John Kucera | June 25, 2009 at 04:38 PM
My apologies. I was not looking in the right place. This solved my problem.
Posted by: Gary Stockton | June 29, 2009 at 03:06 PM
OK, I'm still struggling a little bit.
I implemented section1, however, It's not clear where in Salesforce I would go to implement section2. Would you be more specific for Step 2: Trigger clones non-respondents to new campaign.
Posted by: cmsproducts | June 29, 2009 at 05:08 PM
To summarize our offline email thread:
Setup-->Customize-->Campaigns-->Campaign Members-->Triggers-->New is the route to create new triggers.
To have the "new" button for triggers appear, you must be:
1) have the "Author Apex" permission checked (all sys admins have this)
2) Be in a development, sandbox, or trial org - Apex cannot be written in Active orgs, it must be written in one of these other types of orgs & deployed to your Active production org
Posted by: John Kucera | June 30, 2009 at 10:21 AM
John -
As part of the Apex Trigger Upload as a package instructions you left out that the system will NOT let you simply deploy your new Apex trigger from the sandbox to the production org. One has to also create an Apex Test Class and run it through testMethods before it'll allow it to be deployed. This Test Class creation is additional coding and software development, which a simple user shouldn't have to do or should have to figure out. It would be nice if you provide us all with the last piece of the puzzle to get this feature running, and give us the testMethod Apex Class code so we can deploy this feature properly. Thanks.
Posted by: Tommy | August 06, 2009 at 12:35 PM
Hi John,
Images in this posting and the previous post (Step 2) are missing. The instructions are less than clear without the screenshots.
Also, I have to agree with Tommy. A Test class is more than helpful in these examples - it's essential.
Thanks.
Aiden
Posted by: Aiden | August 24, 2009 at 09:02 AM
Thanks for the comments - I've added a test case framework for triggers like this to the post. Note that you should add system.asserts to the test code above to check if your trigger works as you intend it to.
Posted by: John Kucera | August 25, 2009 at 01:33 PM
Hi John,
Is there a way to mass email campaign members via sfdc's gui that would pass along the campaign member id and other fields from the campaign member object?
Thanks.
Aiden
Posted by: Aiden | September 02, 2009 at 09:42 AM