I finally have the time to update this blog again, and I would like to share my experiences with customizing the marketing email functions of MS CRM.
As we all know, sending mass marketing email from MS CRM is a bit of a challenge. The UI is hopeless, and there's no way to do HTML editing, previews, or to incorporate other functionality into the process of sending emails. Sending mass email from MS CRM involves creating a campaign activity, hook it up with marketing lists, and then click the "distribute campaign activity". Contradictory to the title, this function does not distribute the emails, but open up an email activity window. At this stage you have to design your HTML email in another system, save it to disk, open the file in Internet Explorer, select all, copy and paste it into the email activity window. Then you click send, without any possibility of preview or testing.
For professional marketing departments this means running their campaigns over and over again to mock up marketing lists for live testing until they find the results satisfactory. There's no possibility to retrieve a previous sent email, make the necessary changes, and redistribute it. You can use templates, but you can't edit them before sending.
Other systems are designed to use template sets tailored to the individual organisation's needs, and incorporates the process of testing and recycling mass emails. MS CRM is so far behind in this department. On the other hand, selecting members of marketing lists, and the concept of attaching different campaign activities to a campaign, making it possible to distribute a campaign message in different channels, has a lot of potential.
So, the basics of mass emailing in MS CRM is flawed, but there's always the possibility of customizing it, right? Or so I thought...
The first thing I tried was a third - party product which added click-through and HTML editing capabilities. It turned out it was flawed as well. Most importantly it didn't work, and secondly, it used unsupported customizations.
The second thing I tried was customizing the email activity. I added a few fields to do the following: 1) A checkbox to to indicate whether or not this was a test distribution. 2) A hidden ntext field to store the fetchXML of the last email distributed, and 3) A field for hypertext tagging for use by WebTrends or Google Analytics. I then started developing plugins which I registered on the email activity entity to accomodate somthing in the vincinity of the requested functionality.
After working with the plugins it became clear to me how flawed this part of MS CRM actually is. When you click "send" the campaign activity is distributed and the individual emails are created and sent. One should think it was possible to manipulate those emails both on the pre- and poststage in the child pipeline, but unfortunately it isn't.
What actually happens is this: when you click "send" the systems creates a SendBulkEmailRequest object, and attaches an email template to it. Where this email template comes from is unclear, but I suppose it is created temporarly based on the contents of the email activity window. The SendBulkEmailRequest the creates the individual emails, which are all empty in the pre-create execution stage. (The plugin is executed, but the body attribute is empty). They are then "filled" with the contents from the template, and attached to the SendBulkEmailRequest. If you register a plugin on post-stage, the emails contains the contents of the created template, but they are somehow already copied to the request. The plugin may manipulate them, but it has no consequences for the emails actually send.
In other words, it's impossible to create plugins on the email activity for marketing purposes.
Then why not create or register the plugin on the template entity? Somehow it's empty as well in the pre-create stage, and in the post-create stage it's too late. MS CRM reads the contents from the description window directly and somehow bypasses the execution pipeline.
So doing any customization or creating plugins for the template entity is futile as well.
The solution is to create a specialized ASP.NET page which you integrate into MS CRM, and which sends bulk emails through the web services. That way you have complete control with the code and the process - even though it's time-consuming this is generally the best approach
Showing posts with label Customizations. Show all posts
Showing posts with label Customizations. Show all posts
Saturday, October 24, 2009
Monday, June 1, 2009
How to schedule maintenance jobs with MS CRM
Quite often a need to do regular maintenance jobs on the data in MS CRM arises. It might be that the client requests some sort of regular data processing or that customer accounts should be evaluated each month. For instance one might want to deactivate customer accounts who hasn't prchased anything the last 24 months or so. You might handle this with a filter, but it's quite nice to have the ability to automatically deactive records based on explicit criterias.
MS CRM does not support such monitoring and scheduling jobs out-of-the-box, but it is however quite easy to do this through customization
Creating the maintenance job entity
First, you need a new entity to hold information on the maintenance jobs. Create an new entity, scope it to organization, and name it "maintenance jobs". Add the following attributes:
w entity should look like this:
Create the self-referencing workflows
The next step is to create workflows that is triggered by this entity, and which is able to call each other recurrently. You'll need two workflows for that job. One that's triggered by the creation, and another that is started by the first one which main task is to wait until the next recurrence and trigger the first one.
Be aware that there's a continous loop detection in MS CRM which restrains workflows from triggering themselves more than 7 times per hour or so
Create the first workflow, call it WF_MaintenanceJobStarter or something like that. Scope it to organisation, make sure it triggers on create for the maintenancejob entity and that it is also available to run as a child workflow
The workflow must contain the following activities

The second workflows main task is to wait out the appropriate time established by the recurrence attribute before it calls the first workflow. You might have other recurrence patterns than those below, but remember that a workflow can't call itself more than 7 times per hour
You'll need to define the following workflow:

Now you have defined a workflow that calls it self recurrently based on the intervals you define in the maintenance job records. But it doesn't actually do anything. You need to add activities that will do the necessary maintenance for you at these intervals. Usually you'll need to code these as custom workflow activities, especially if you want to do maintenance on the entire database.
Another neat thing to remember is that when you add those activities make sure to call the looping workflow before that activity is executed. That way the maintenance job want halt if something goes wrong with your activity
You'll find a suitable custom workflow activity for this kind of job in this post.
MS CRM does not support such monitoring and scheduling jobs out-of-the-box, but it is however quite easy to do this through customization
Creating the maintenance job entity
First, you need a new entity to hold information on the maintenance jobs. Create an new entity, scope it to organization, and name it "maintenance jobs". Add the following attributes:
- A picklist attribute called jobtype - this will hold the different types of jobs you need to do. Add at least one value called "Deactivate accounts older than 24 months". This attribute is required
- A dateTime attribute called startdate. If you want to be able to run the job at specific times, choose "date and time" - format. Required
- A dateTime attribute called enddate. Optional
- A picklist attribute called recurrence - assign the values daily, weekly and monthly or other values you might need. Required
- A nText attribute called description in case you need to describe each job
- Keep the name attribute as it is
w entity should look like this:Create the self-referencing workflows
The next step is to create workflows that is triggered by this entity, and which is able to call each other recurrently. You'll need two workflows for that job. One that's triggered by the creation, and another that is started by the first one which main task is to wait until the next recurrence and trigger the first one.
Be aware that there's a continous loop detection in MS CRM which restrains workflows from triggering themselves more than 7 times per hour or so
Create the first workflow, call it WF_MaintenanceJobStarter or something like that. Scope it to organisation, make sure it triggers on create for the maintenancejob entity and that it is also available to run as a child workflow
The workflow must contain the following activities
- A Wait activity that waits until it's on or after staring time for the maintenance job
- A Check Condition activity that checks the job type of the maintenance job
- A Start Child Workflow activity that starts the looping activity. This isn't defined yet, so just leave this empty
- A check Condtion activity that checks if the maintenancejob has an end date
- If it has an end date a Wait activity will wait until that day and stop the workflow

The second workflows main task is to wait out the appropriate time established by the recurrence attribute before it calls the first workflow. You might have other recurrence patterns than those below, but remember that a workflow can't call itself more than 7 times per hour
You'll need to define the following workflow:
- Name: WF_ MaintenanceJobLooper. Scope: Organisation, Entity: maintenance job
- Make sure that it is available as a child workflow
- Define a Check Condition activity with three branches, one for each type of recurrence. Dail, weekly or monthly
- Underneath each Check Condition activity add a Wait Activity that waits a day, a week or a month after workflow execution time
- When the waiting activity is done, a Start child Workflow Activity is fired. This activity starts WF_MaintenanceJobStarter

Now you have defined a workflow that calls it self recurrently based on the intervals you define in the maintenance job records. But it doesn't actually do anything. You need to add activities that will do the necessary maintenance for you at these intervals. Usually you'll need to code these as custom workflow activities, especially if you want to do maintenance on the entire database.
Another neat thing to remember is that when you add those activities make sure to call the looping workflow before that activity is executed. That way the maintenance job want halt if something goes wrong with your activity
You'll find a suitable custom workflow activity for this kind of job in this post.
Sunday, May 31, 2009
Set customer rating by workflow
Have you ever been tasked with implementing a system for rating the customer acccounts in MS CRM? Many clients have different rules for rating their MS CRM accounts. The concept is basically that the account is given a rating in different disciplines, and the total determines the general standing of the account.
This is often accomplished in JavaScript, but in my opinion this is a rather poor approach. First of all because I hate JavaScript, but more important because JavaScript only fires when a user opens the account form. There's a lot of scenarios where this isn't sufficient:
- You're importing data from other sources and wants to use them in a campaign without actually open them
- You have lot's and lot's of accounts in your database, but they are seldom opened - in that case you should probably consider some sort of maintenance function
- Your system's integrated with other systems, who might change information without human intervention
- Etc
Anyway, the following code will accomplish the task:
[CrmWorkflowActivity("Rating")]
public class Rating : SequenceActivity
{
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
IContextService contextService = (IContextService)executionContext.GetService(typeof(IContextService));
IWorkflowContext context = contextService.Context;
ICrmService crmService = context.CreateCrmService();
//Possible ratings
char[] ratings = new char[] { 'A', 'B', 'C' };
//Combinations:
string rating = this.CriteriaOne.ToUpper () + this.CriteriaOne.ToUpper () + this.CriteriaOne.ToUpper ();
//A equals 1, B equals 2 and C equals 3. Max 9, min 3. 3-5 equals a total A, 6-7 equals B 8-9 equals C
//Alternately a single A criteria can generate a total rating of A if this is specified
int i = 0;
bool A = false;
foreach (char c in rating)
{
if (c == 'A')
{
i = i + 1;
A = true;
}
if (c == 'B')
{
i = i + 2;
}
if (c == 'C')
{
i = i + 3;
}
}
if(i<6) totalrating = "A"> 5) && (i< totalrating = "B"> 7)
{
this.TotalRating = "C";
}
if (this.ARating.Value)
{
if (A)
{
this.TotalRating = "A";
}
}
return ActivityExecutionStatus.Closed;
}
public static DependencyProperty CriteriaOneProperty = DependencyProperty.Register ("CriteriaOne", typeof (System.String), typeof (Rating));
[CrmInput("Criteria One")]
public string CriteriaOne
{
get
{
return base.GetValue (CriteriaOneProperty).ToString ();
}
set
{
base.SetValue (CriteriaOneProperty, value);
}
}
public static DependencyProperty CriteriaTwoProperty = DependencyProperty.Register ("CriteriaTwo", typeof (System.String), typeof (Rating));
[CrmInput ("Criteria Two")]
public string CriteriaTwo
{
get
{
return base.GetValue (CriteriaTwoProperty).ToString ();
}
set
{
base.SetValue (CriteriaTwoProperty, value);
}
}
public static DependencyProperty CriteriaThreeProperty = DependencyProperty.Register ("CriteriaThree", typeof (System.String), typeof (Rating));
[CrmInput ("Criteria Three")]
public string CriteriaThree
{
get
{
return base.GetValue (CriteriaThreeProperty).ToString ();
}
set
{
base.SetValue (CriteriaThreeProperty, value);
}
}
public static DependencyProperty ARatingProperty = DependencyProperty.Register ("ARating", typeof (CrmBoolean), typeof (Rating));
[CrmInput ("Return always A if one criteria is A)]
public CrmBoolean ARating
{
get
{
return (CrmBoolean)base.GetValue (ARatingProperty);
}
set
{
base.SetValue (ARatingProperty, value);
}
}
public static DependencyProperty TotalRatingProperty = DependencyProperty.Register ("TotalRating", typeof (System.String), typeof (Rating));
[CrmOutput ("Total Rating")]
public string TotalRating
{
get
{
return this.GetValue (TotalRatingProperty).ToString ();
}
set
{
this.SetValue (TotalRatingProperty, value);
}
}
}
The code reads three different criterias from picklists, evaluates them and returns the average which can be used to generate a total rating. Some clients would like an 'A'-rating in one criteria to override the others, so this is a configurable option.
If you need more criterias than three, just add more properties and assign them other values.
Labels:
Custom Workflow Activities,
Customizations,
rating,
Workflow
Subscribe to:
Posts (Atom)