Posted on Sep 19, 2012

Mastering customised emails with Sitecore ECM

We’ve recently been asked to create some highly customised and personalised email campaigns in Sitecore’s ECM (Email Campaign Manager). This module is part of DMS and allows you to tap into your website content and customise emails using the Sitecore API we’re all familiar with.

The thing is, documentation is pretty light on ECM and this became a bit of an R&D project.  SO I thought I should blog about it.  Hopefully this will help some of you get the job done a bit quicker.

The Requirements

So we have some users, who have given us various preferences.  They break down into three main lists:

  • Daily Digest recipients
  • Weekly Digest recipients
  • Alert recipients

The daily and weekly lists are what they say on the tin.  They’ll receive their emails on a schedule with any content which has been published since the last send.  The Alerts subscribers will be sent an email immediately when an appropriate item is published, so the client can syndicate content to their site and subscribers at the same time.

To add to the fun, all these users have individual preferences for their content interests, so as we’re dispatching the emails we’ll be customising the content.  For example, one user might get three news items, another only one, and some users won’t get an email at all.

Ultimately this job breaks down into four main areas:

  • We need to trigger email sends on a schedule
  • We need to trigger an email send when a content item is published
  • We need to build content which matches the user’s preferences
  • We need to ensure nobody gets an email then there is no content matching their preferences

Sending emails programmatically

For the digest emails we need to send emails every night, or once a week.  Easy enough, we thought.  Hook up a command method to Sitecore’s scheduler and get it to dispatch a message.  The API calls for this though, are not documented and after fishing around with Intellisense for a while we eventually gave in and popped Sitecore support an email.  The answer?   A few simple lines of code – easy when you know how…

Item itmMessage = db.GetItem([YOUR NEWSLETTER ITEM]);
MessageItem mi = Sitecore.Modules.EmailCampaign.Util.GetMessage(itmMessage);
Sitecore.Modules.EmailCampaign.SendingManager sm = new SendingManager(mi);
String strOut = sm.SendMessage();

Dispatching an email on item publish

For the alerts, we hook into Sitecore’s Publish:ItemProcessing pipeline to examine all items that are being published.  If we find a template which matches our alert content, we check it has the right tags and trigger the email building process.

Config:

<event name="publish:itemProcessing">
   <handler type="Dog.ECM.AlertPublish, Dog.ECM" method="OnItemProcessing" />
</event>

Processor:

public void OnItemProcessing( object sender , EventArgs args ) {

    var context = ( ( ItemProcessingEventArgs )args ).Context;

    Assert.IsNotNull( context , "Cannot get PublishItem context" );

    if ( context.PublishOptions.TargetDatabase.Name.Equals( webDatabase ) ) {

        var item = master.GetItem( context.ItemId );

        // In here is some logic to examine item for Alert criteria
        // i.e. template, tags etc so we don't process any old item

        // We look to see if the item had been published before.  This field should be blank
        if ( string.IsNullOrEmpty( item[ firstPublishedDateField ] ) ) {

            // Date published is null - update to now so we don't process this item again
            item.Editing.BeginEdit( );
            item[ firstPublishedDateField ] = DateTime.Now.ToString( "yyyyMMddTHHmmss" );
            item.Editing.EndEdit( true );

            // This method creates an email for send.
            // Duplicate a newsletter set up as a "master", edit its content based on
            // current item and give it a sensible name
            Item itemToEdit = createAlertNewsletter(item.Name, item);

            // This method publishes the new newsletter
            publishItem(itemToEdit);

            // Send the message
            MessageItem mi = Sitecore.Modules.EmailCampaign.Util.GetMessage(itemToEdit);
            Sitecore.Modules.EmailCampaign.SendingManager sm = new SendingManager(mi);
            String strOut = sm.SendMessage();

        }
    }
 }

Incidentally, as you might pick up from the above code, we’d already come across a challenge for our daily and weekly digests.  We needed to retrieve items published for the first time in the last 24 hours, or the last week.  After digging around the item standard fields we realised that __created takes the same date on the Production database as it has on Master.  We needed a date for “First Published”, so we could retrieve items which had been published maybe days after being authored, and ignore items which were being published for the second or third time.  This would ensure we didn’t send items out twice.  We added a date field to all content items in Sitecore and on publish, if that field isn’t populated, we add the current date.

Personalising the email content

Dispatching a message basically sends Sitecore into a big loop.  It iterates through the users in the role assigned as the opt-in list, and builds the message content as if it were a webpage being rendered by the user themselves.

That’s right, your sublayout code runs in the context of the userGreat!

User currUser = Sitecore.Context.User;
Profile = currUser.Profile;

This makes it pretty straight-forward to retrieve the user’s preferences and use it to search for matching content.

Filtering recipient lists

So now we’ve got our customised email and we know how to send it.  But we’re still sending it to everyone, regardless if we’ve not managed to retrieve any content for them!  We need to drop some users out of this particular send if there’s nothing relevant for them.

We took two approaches to this.  First we found the following post on Filtering ECM on Alistair Deneys’ blog.  This seemed to be a decent option, but on further examination we realised that this was running within the sending loop, and simply calling abort on the individual user.  There’s a comment by Thor Jakobsen that suggests the analytics stats might get screwed up doing it at this point and to look at the DispatchNewsletter pipeline.

In fact, for best efficiency the pipeline should be inserted right at the start of the DispatchNewsletter section.  At this point we have our subscriber list, and Sitecore hasn’t started looping through them yet.  Upfront then, we iterate through our list, perform some checks, and remove people as required.

Config:

<pipelines>
   <DispatchNewsletter>
      <processor type="Dog.ECM.ECMFilterRecipients, Dog.ECM" patch:before="processor[@type='Sitecore.Modules.EmailCampaign.Core.Pipelines.DispatchNewsletter.MoveToProcessing,    Sitecore.EmailCampaign']" />
   </DispatchNewsletter>
</pipelines

Code:

public class ECMFilterRecipients
 {
 	public void Process(BaseDispatchNewsletterArgs args)     
    {
        Database db = Database.GetDatabase("master");
        itmMessage = db.GetItem(args.Message.InnerItem.ID);
        
        // Get the master subscriber list
        List<Contact> contacts = args.Message.Subscribers;
        
        // Create a removal list 
        //(we can't remove from the master contacts list while iterating through it)
        List<Contact> toBeRemoved = new List<Contact>();
            
        
        // Loop through subscriber list
        foreach (Contact contact in contacts)
        {
			// Examine the user.  If they don't meet the criteria for the email
            // we add them to the removal list
            toBeRemoved.Add(contact);

		}
        
        // Now iterate through the removal list and remove the subscribers
        foreach (Contact clientId in toBeRemoved)
        {
            args.Message.Subscribers.Remove(clientId);
            args.Message.SubscribersNames.Remove(clientId.InnerUser.Name);
        }
        
        // That's it - the rest of the pipelines are processed with the filtered
        // recipient list
        
	}
}

Note that we have to remove from both the Subscribers collection, and the SubscriberName collection.  Not sure exactly why Sitecore maintains two lists but support clarified this point.

So there we have it.  There’s no doubt ECM is a complex beast, but it has some serious power at its fingertips.  Given the above pipeline processors and access to the subscribers profile, there’s really not much you can’t do in customising content for your audience.  Then let the analytics tell you how well it converted!