The Microsoft Dynamics CRM Blog
News and views from the Microsoft Dynamics CRM Team

Custom Workflow Activity for matching email addresses to customers

Custom Workflow Activity for matching email addresses to customers

  • Comments 24

In an earlier blog (E-mail to Case/Lead Using CRM 4 Workflow ), I described a sample workflow that could be used to automatically create cases based on emails sent to a queue.

One of the questions that was frequently asked for the above blog was that the email activity’s regarding field set to blank. In this blog, I’ll show how this can be done using a custom CRM workflow activity.

Let’s summarize the problem we are trying to solve. Joe (joe@mycustomer.com) is a customer in your system and say you have an account that is capturing this. Joe sends an email to your support alias to report an issue. We would like the email activity that is created to be regarding Joe’s account.

For those of you new to writing custom workflow activities, I highly recommend that you take a look at the SDK documentation at: http://msdn.microsoft.com/en-us/library/cc151142.aspx --It contains all the information needed for you to start using custom workflow activities.

I’m going to split my solution into 2 parts:

1) A custom workflow activity that takes an email address as input and returns a matching account as output

2) A workflow rule that uses the above custom activity. This workflow rule is triggered when an email is created and updates the email activity to have the regarding field set to the output returned by the custom workflow activity.

Custom Workflow Activity

Let’s walk thru’ the code for the custom workflow activity.

1) Define the class skeleton and the using statements for the various namespaces that we will need. Please ensure you have read and understood the SDK documentation at: http://msdn.microsoft.com/en-us/library/cc151142.aspx to get an overview of how to develop CRM custom workflow activities.

using System.Workflow.Activities;

using System.Workflow.ComponentModel;

using Microsoft.Crm.Sdk;

using Microsoft.Crm.SdkTypeProxy;

using Microsoft.Crm.Workflow;

using Microsoft.Crm.Sdk.Query;

namespace CrmCustomWFActivity

{

   [CrmWorkflowActivity("Find Customer with specified email address")]

   public class MatchSenderWithExistingCustomerActivity:  

   System.Workflow.Activities.SequenceActivity

   {

       // Activity code goes here.

   }

}

2) Now, add properties that represent the inputs and outputs. As discussed earlier, the input is an email address (of type string) and the output will be the matching account (of type Lookup).

DependencyProperty is a Windows Workflow concept and you can read more about it here: http://msdn.microsoft.com/en-us/library/system.workflow.componentmodel.dependencyproperty.aspx

   1: // Input property
   2: public static DependencyProperty senderProperty = DependencyProperty.Register("sender", typeof(string), typeof(MatchSenderWithExistingCustomerActivity));
   3:  
   4:         [CrmInput("Sender")]
   5:         public string sender
   6:         {
   7:             get
   8:             {
   9:                 return (string)base.GetValue(senderProperty);
  10:             }
  11:             set
  12:             {
  13:                 base.SetValue(senderProperty, value);
  14:             }
  15:  
  16:         }
  17:  
  18: // Output property
  19: public static DependencyProperty accountIdProperty = DependencyProperty.Register("accountId", typeof(Lookup), typeof(MatchSenderWithExistingCustomerActivity));
  20:  
  21:         [CrmOutput("AccountId")]
  22:         [CrmReferenceTarget("account")]
  23:         public Lookup accountId
  24:         {
  25:             get
  26:             {
  27:                 return (Lookup)base.GetValue(accountIdProperty);
  28:             }
  29:             set
  30:             {
  31:                 base.SetValue(accountIdProperty, value);
  32:             }
  33:  
  34:         }

3) This custom workflow activity needs to be able to retrieve accounts whose email address matches the sender property value. To do this, it needs to be able to call into CRM SDK methods and can do this thru’ IcrmService interface that is provided to all crm custom workflow activities. The following is code for a helper method that uses CRM query functionality to return account that matches the email address.

   1: private Guid MatchSenderWithExistingAccount(ICrmService crmService, string fromAddress)
   2: {
   3:     // Retrieve accounts with this email address.
   4:     QueryByAttribute query = new QueryByAttribute();
   5:     query.EntityName = EntityName.account.ToString();
   6:     query.Attributes = new string[] { "emailaddress1" };
   7:     query.Values = new string[] { fromAddress };
   8:  
   9:     RetrieveMultipleRequest retrieveMultipleRequest = new RetrieveMultipleRequest();
  10:     retrieveMultipleRequest.Query = query;
  11:     retrieveMultipleRequest.ReturnDynamicEntities = true;
  12:  
  13:     RetrieveMultipleResponse retrieveMultipleResponse = (RetrieveMultipleResponse)crmService.Execute(retrieveMultipleRequest);
  14:  
  15:  
  16:  
  17:     Guid accountId = Guid.Empty;
  18:  
  19:     foreach (BusinessEntity busEntity in retrieveMultipleResponse.BusinessEntityCollection.BusinessEntities)
  20:     {
  21:         // Pick the first accountid.
  22:         accountId = ((Key)(((DynamicEntity)busEntity)["accountid"])).Value;
  23:         break;
  24:     }
  25:  
  26:     return accountId;
  27: }

4) Now, we can implement the Execute method of the custom workflow activity.

   1: protected override System.Workflow.ComponentModel.ActivityExecutionStatus Execute(System.Workflow.ComponentModel.ActivityExecutionContext executionContext)
   2:         {
   3:             IContextService contextService = (IContextService)executionContext.GetService(typeof(IContextService));
   4:             IWorkflowContext context = contextService.Context;
   5:  
   6:         // Obtain IcrmService so we can call into CRM SDK to retrieve
   7:         // accounts
   8:             ICrmService crmService = context.CreateCrmService(); 
   9:  
  10:             // this.sender property will have the email address that needs to be matched.
  11:             Guid accountId = MatchSenderWithExistingAccount(crmService, this.sender);
  12:  
  13:             // Set the accountId output property to return this data
  14:         // back to the calling workflow
  15:             this.accountId = new Lookup("account", accountId);
  16:  
  17:             return ActivityExecutionStatus.Closed;
  18:         }
  19:       
  20:     return accountId;

Using The Custom Workflow Activity

In order to use MatchSenderWithExistingCustomerActivity in workflow rules, you need to register the activity. Follow the steps at: http://msdn.microsoft.com/en-us/library/cc151144.aspx to accomplish this.

We will create a workflow rule that does the following:

1) When an email is created, it checks if the email’s regarding field is set.

2) If not set, it invokes MatchSenderWithExistingCustomerActivity

3) It updates email with the accountid returned by MatchSenderWithExistingCustomerActivity.

Let’s walk through these in more detail.

1) Create the workflow rule, call it SetEmailRegarding and set it to trigger on email create

clip_image002

2) Insert a check condition step and configure it to check if Email:regarding field does not contain data

clip_image004

3) If Email:regarding is not set, we want to invoke the MatchSenderWithExistingCustomerActivity. Under the add step drop-down, you should find the custom workflow activity as shown below:

clip_image006

4) After you select MatchSenderWithExistingCustomerActivity, you need to ensure that it is being invoked with the correct input. Click ‘Set Properties’ and set the value of the input parameter Sender to that of Email:Sender

clip_image008

5) The next step would be to update the email’s regarding to the value returned by the custom workflow activity in the previous step. To do this, add a Update Email step, select ‘Set Properties’ and tab to the ‘Regarding’ field. In the dynamic values form assistant, you will now find that it shows ‘Match email address:accountid’ to indicate the return value from the previous step. Select this to be the value for regarding field as shown below.

clip_image010

6) Publish the workflow and verify that it is working.

Since the functionality to match email address to an account has been captured as a custom workflow activity, you will be able to use that activity in all your workflow rules. You can also enhance the custom workflow activity by making it return either a matching account or contact if needed. If you need to create an account/contact if a match wasn’t found, you can do this by adding a Create step to the workflow rule.

Jagan Peri

  • PingBack from http://www.travel-hilarity.com/airline_travel/?p=5382

  • I know that I am asking in the wrong spot but I don't know where else to look. I have CRM 4.0 and I have an unassigned que that my cases go into. I want to have the case auto-assign to a specific user that opens it. So if I am user Bob and I open a case in the unassigned que it will automatically assign the case to me. Is this possible?

  • Hi,

    It's great, i was trying to do the same.

    But I still has an error when trying to register the workflow with The Plugin Registration Tools.

    <description>The type Lookup of the property contactId is not supported. </description>

    It work find with a string but not a Lookup.

    Any Idea ?

  • Hi Gabriel,

      How are you declaring this lookup? Can you include your definition?

    Lookups should work if you follow my code sample posted above...

    thanks

    Jagan Peri

  • Hi,

    First off, thanks for writing this post as it provides some much-requested functionality and another sample to work from.

    However, I cannot seem to get it to work.  The sample code above seems to have a return statement in the wrong place:

    3rd box, line 20:

    return accountId;

    With that line removed the code compiles and registers fine, though it stalls any workflows it is part of.  With that line in, it refuses to compile.  Any ideas?

  • Hi;

    Do you have your code example?

    I can't get the project to compile, it keeps asking for a reference for Guid.  I have added only the references you have specified above apart from Microsoft.Crm.Workflow as I can't seem to find that so I assume it is included somewhere else?

    Thanks

    MM

  • Can you please check your code?

    I only got this to build by adding "using System;" for reference and also deleting line number 20 on your last snippet "return accountId;"

    If I am correct then you at least owe it to others to correct your mistake(s).

    MM

  • Hi all;

    As previously mentioned the initial code kindly posted by Jagan requires two changes to work.

    Also, a lot of CRM customers want to associate emails with contacts rather than just accounts as there are usually lots of contacts per account.  And the above code will only work if the the value in the "from" field matches the email address on the account, this is not ideal in the real world as one can get emails from multiple people from within a company (account). The code below will  enable you to create a plug-in that will set the regarding field in an email to that of the contact who sent it - assuming it exists in the first place

    As with everything like this, usual caveats apply, not supported, back up first, test in development first etc etc etc:

    using System;

    using System.Workflow.Activities;

    using System.Workflow.ComponentModel;

    using Microsoft.Crm.Sdk;

    using Microsoft.Crm.SdkTypeProxy;

    using Microsoft.Crm.Workflow;

    using Microsoft.Crm.Sdk.Query;

    using System.Workflow.Runtime;

    using System.Data;

    using System.Xml;

    using System.Xml.Linq;

    namespace MatchContactSenderEmail

    {

       [CrmWorkflowActivity("Find Contact with specified email address")]

       public class MatchSenderWithExistingContactActivity :

       System.Workflow.Activities.SequenceActivity

       {

           // Input property

           public static DependencyProperty senderProperty = DependencyProperty.Register("sender", typeof(string), typeof(MatchSenderWithExistingContactActivity));

           [CrmInput("Sender")]

           public string sender

           {

               get

               {

                   return (string)base.GetValue(senderProperty);

               }

               set

               {

                   base.SetValue(senderProperty, value);

               }

           }

           // Output property

           public static DependencyProperty contactIdProperty = DependencyProperty.Register("contactId", typeof(Lookup), typeof(MatchSenderWithExistingContactActivity));

           [CrmOutput("ContactId")]

           [CrmReferenceTarget("contact")]

           public Lookup contactId

           {

               get

               {

                   return (Lookup)base.GetValue(contactIdProperty);

               }

               set

               {

                   base.SetValue(contactIdProperty, value);

               }

           }

           private Guid MatchSenderWithExistingContact(ICrmService crmService, string fromAddress)

           {

               // Retrieve accounts with this email address.

               QueryByAttribute query = new QueryByAttribute();

               query.EntityName = EntityName.contact.ToString();

               query.Attributes = new string[] { "emailaddress1" };

               query.Values = new string[] { fromAddress };

               RetrieveMultipleRequest retrieveMultipleRequest = new RetrieveMultipleRequest();

               retrieveMultipleRequest.Query = query;

               retrieveMultipleRequest.ReturnDynamicEntities = true;

               RetrieveMultipleResponse retrieveMultipleResponse = (RetrieveMultipleResponse)crmService.Execute(retrieveMultipleRequest);

               Guid contactId = Guid.Empty;

               foreach (BusinessEntity busEntity in retrieveMultipleResponse.BusinessEntityCollection.BusinessEntities)

               {

                   // Pick the first contactid.

                   contactId = ((Key)(((DynamicEntity)busEntity)["contactid"])).Value;

                   break;

               }

               return contactId;

           }

           protected override System.Workflow.ComponentModel.ActivityExecutionStatus Execute(System.Workflow.ComponentModel.ActivityExecutionContext executionContext)

           {

               IContextService contextService = (IContextService)executionContext.GetService(typeof(IContextService));

               IWorkflowContext context = contextService.Context;

               // Obtain IcrmService so we can call into CRM SDK to retrieve

               // accounts

               ICrmService crmService = context.CreateCrmService();

               // this.sender property will have the email address that needs to be matched.

               Guid contactId = MatchSenderWithExistingContact(crmService, this.sender);

               // Set the accountId output property to return this data

               // back to the calling workflow

               this.contactId = new Lookup("contact", contactId);

               return ActivityExecutionStatus.Closed;

           }

       }

    }

  • Hello,

    Thanks to MindenMan & Jagan Peri for the code. I use the code post by MindenMan and it works. Curiously the dynamic value set for the sender field appear twice after being save and reopen. I'm I the only one getting this behaviour in the Workflow sender field?

  • Hello.

    I use the workflow earlier described to make a case from email activity and get the same problem: the cases are not connected to a customer.

    If I use the manual command: "convert email to case" directly on the email-activity, the case then get the correct customer.

    Isn't there a way to to this without creating theese custom workflows?

    James

  • I have a question. I have created customized workflow using visual studio and have attached it to the SharePoint list. The problem I am getting is when I modify my workflow code and reinstall/reattach it to the list; the workflow items which were already in progress stops progressing further i.e. becomes dead, so each time when I modify workflow I remove the old workflow attach the new one and had to delete the entire old workflow items.

    So, is there a way to retain the workflow item in progress to use the new version of workflow when we reinstall the workflow in between. Please advice

  • I have a question. I have created customized workflow using visual studio and have attached it to the SharePoint list. The problem I am getting is when I modify my workflow code and reinstall/reattach it to the list; the workflow items which were already in progress stops progressing further i.e. becomes dead, so each time when I modify workflow I remove the old workflow attach the new one and had to delete the entire old workflow items.

    So, is there a way to retain the workflow item in progress to use the new version of workflow when we reinstall the workflow in between. Please advice

  • Today we welcome guest blogger Jim Steger , developer, blogger, and writer for Sonoma Partners with this

  • Today we welcome guest blogger Jim Steger , developer, blogger, and writer for Sonoma Partners with this

  • Today we welcome guest blogger Jim Steger , developer, blogger, and writer for Sonoma Partners with this

Page 1 of 2 (24 items) 12
Leave a Comment
  • Please add 3 and 1 and type the answer here:
  • Post