Wednesday, September 1, 2010

Send reminder emails from SharePoint Workflow for overdue tasks

The Problem

A requirement of the workflow I have been working on is to send reminder emails when a task is approaching the due date or overdue. The workflow in question has numerous replicator activities with an execution type of Parallel. The typical, at least all the examples I could find, would be to use a ListenerActivity with one leg waiting on an OnTaskChanged event and the other leg contains a delay activity followed by the necessary activities to send out an email. My problem was, when the delay activity completes and that particular leg kicks off, it is the OWSTimer running those events. I don’t have SPContext and I cannot tell which instance of my replicator sequence is running. I don’t have a reference back to my actual workflow task and as a result, I have no way of knowing if a task is completed or overdue etc. I was really struggling trying to figure out how I was going to accomplish this requirement. Whatever I did, I was going to have to repeat it for several different replicators in my workflow.

The Solution

My solution was to basically develop a “multi-threaded” workflow. To do this I used a ConditionedActivityGroup workflow object. Below is the structure of one of the Sequences in my CAG.

I’m going to explain what each of the activities in this leg of the CAG are doing but I have also included the code for each of them below.

The CAG itself has a Declarative Rule Condition set for its until condition that is checking a private bool called _workflowCompleted. This bool is initiated to false and only set to true when the main workflow sequence is complete.

The first sequence that is not showing is my main workflow. This is where all my tasks are created and completed etc. This activity in the CAG has no when condition and is therefore only executed once. SharePoint is responsible for hydrating and dehydrating this workflow as SharePoint events occur.

The second sequence that is shown below is the sequence that checks for overdue tasks and sends emails as necessary. The when condition for this activity is also checking !_workflowCompleted using a Declarative Rule Condition. Adding the when condition causes this leg of the CAG to run continuously until the _workflowCompleted bool is set to true.

The DelayActivity is initialized with a time span defined in a settings file but could be hard coded. We are going to check for overdue tasks daily.

The GetWorkflowTasks activity gets current tasks for the workflow and assigns the collection of tasks to a local variable.

The while loop iterates the tasks collection.

The IfElse block checks to see if the task is not completed and has a (due date – some adjustment) later than today’s date.

If the task is overdue and not completed we send an email to the assignee to remind them they have a task due. The correlation toke for the SendEmail activity was set to the workflow correlation token.

The increment index simply increments a local variable we use to pull the correct value from the task collection. When the local index variable exceeds the task collection count we exit the loop.

Finally, the clearWorkflowCol CodeActivity sets the local variable that contains the workflow tasks collection to null because SPWorkflowTaskCollection is not a serializable object.

Code for all of the activities above

private SPWorkflowTaskCollection workflowTasks = null;
private int _currentIndex = 0;

private void EmailDelay_InitializeTimeoutDuration(object sender, EventArgs e)
    EmailDelay.TimeoutDuration = Settings.Default.EmailThreadDelay;

private void getWorkflowTasks_ExecuteCode(object sender, EventArgs e)
    workflowTasks = workflowProperties.Workflow.Tasks;
    _currentIndex = 0;


private void iterateEmails_Cond(object sender, ConditionalEventArgs e)
    if (_currentIndex < workflowTasks.Count)
        e.Result = true;

private void sendReminderEmail_MethodInvoking(object sender, EventArgs e)
    ((SendEmail)sender).Body = GetEmail(
                                workflowTasks[_currentIndex]["Due Date"].ToString());
    ((SendEmail)sender).To = workflowProperties.Web.AllUsers.GetByID(Convert.ToInt32(workflowTasks[_currentIndex]["Assigned To"].ToString().Split(";".ToCharArray())[0])).Email;
    ((SendEmail)sender).Subject = string.Format("Reminder - {0}", workflowTasks[_currentIndex][SPBuiltInFieldId.Title].ToString());

private void ifEmailOverdue_Cond(object sender, ConditionalEventArgs e)
    if (workflowTasks[_currentIndex]["Due Date"] != null
        && DateTime.Today >= Convert.ToDateTime(workflowTasks[_currentIndex]["Due Date"].ToString()) - Settings.Default.ReminderEmailStart
        && workflowTasks[_currentIndex][SPBuiltInFieldId.TaskStatus].ToString().ToLower() != "completed")
        e.Result = true;

private void incrementIndex_ExecuteCode(object sender, EventArgs e)

private void clearWorkflowColl_ExecuteCode(object sender, EventArgs e)
    workflowTasks = null;

No comments:

Post a Comment