E-mail To Case Using Workflow – Update

Voodoo Chile...

Having implemented the workflow described by Jagan here to take an incoming e-mail and convert it automatically to a case, it became clear that some custom code would be need to solve the following problems:

  1. Using the workflow designer you cannot map the "from" field of an "email" entity to the "customer" field of an "incident" entity. If you think about it this makes perfect sense since the "from" field type is a PartyList (which is an array of Lookup types), whereas the "customer" field type is a Customer (which is just a single Lookup type).
  2. There's no way to tell if the "from" field of an "email" entity is recognised as a known "account", "contact", "lead", "user" or "queue" (the five entity types that can send/receive e-mails in Microsoft CRM). This means there is no way for you to create a new customer record automatically (if no record exists) when the e-mail is tracked. This is important for organisations who handle e-mail from potentially unknown customers, where no account, contact or lead record exists and the only unique identifier is the customer e-mail address.

After a quick scan through the SDK, I realised I could build a single custom workflow activity which would help me solve both issues. This activity would work as follows:

  1. Take an email Lookup type as the input parameter.
  2. Call the CRM web service and retrieve the "from" attribute of the email.
  3. Although the "from" attribute field type is a PartyList and can, in theory, have multiple senders, in practice an email will have only a single sender. So we are just interested in the first party in the array of parties
  4. Determine if the party is an "account", "contact", "lead", "user" or "queue" entity.
  5. Return the appropriate entity Lookup type as an output property. Because CRM only supports a single entity type per lookup property, using the CrmReferenceTarget attribute, we have to implement five different output properties, one for each "account", "contact", "lead", "user" and "queue" entity type.

Following the SDK documentation for creating a custom workflow activity, my first job was to build the basic class, inherited from the workflow SequenceActivity base class, and override the execute method.

Imports System
Imports System.Workflow.ComponentModel
Imports System.Workflow.Activities
Imports System.Xml
Imports Microsoft.Crm.Sdk
Imports Microsoft.Crm.SdkTypeProxy
Imports Microsoft.Crm.Workflow
<CrmWorkflowActivity("Get Sender", "E-mail Utilities")> _
Public Class GetSender
    Inherits SequenceActivity
    Protected Overrides Function Execute(ByVal executionContext As ActivityExecutionContext) As ActivityExecutionStatus
        Return ActivityExecutionStatus.Closed
    End Function
End Class

Simply compiling this class and registering the workflow assembly with Microsoft CRM, enables this as a new activity in the CRM workflow editor.

Custom Workflow Activity

Next, I needed to pass an email as an input parameters to the activity. Again, following the SDK documentation for creating workflow dependency properties, this was pretty straightforward.

Public Shared EmailProperty As DependencyProperty = DependencyProperty.Register("Email", GetType(Lookup), GetType(GetSender))
<CrmInput("E-mail"), CrmReferenceTarget("email")> _
Public Property Email() As Lookup
        Return CType(Me.GetValue(EmailProperty), Lookup)
    End Get
    Set(ByVal value As Lookup)
        Me.SetValue(EmailProperty, value)
    End Set
End Property

Compiling and registering the assembly exposes the input parameters to the CRM workflow editor.

Custom Workflow Activity Input Properties

In order to determine if the e-mail sender is already a recognised entity within CRM, I had to create five different output parameters, one for each of the possible entity types that could be a sender.

Public Shared ContactProperty As DependencyProperty = DependencyProperty.Register("Contact", GetType(Lookup), GetType(GetSender))
<CrmOutput("Contact"), CrmReferenceTarget("contact")> _
Public Property Contact() As Lookup
        Return CType(Me.GetValue(ContactProperty), Lookup)
    End Get
    Set(ByVal value As Lookup)
        Me.SetValue(ContactProperty, value)
    End Set
End Property
Public Shared AccountProperty As DependencyProperty = DependencyProperty.Register("Account", GetType(Lookup), GetType(GetSender))
<CrmOutput("Account"), CrmReferenceTarget("account")> _
Public Property Account() As Lookup
        Return CType(Me.GetValue(AccountProperty), Lookup)
    End Get
    Set(ByVal value As Lookup)
        Me.SetValue(AccountProperty, value)
    End Set
End Property
Public Shared LeadProperty As DependencyProperty = DependencyProperty.Register("Lead", GetType(Lookup), GetType(GetSender))
<CrmOutput("Lead"), CrmReferenceTarget("lead")> _
Public Property Lead() As Lookup
        Return CType(Me.GetValue(LeadProperty), Lookup)
    End Get
    Set(ByVal value As Lookup)
        Me.SetValue(LeadProperty, value)
    End Set
End Property
Public Shared UserProperty As DependencyProperty = DependencyProperty.Register("User", GetType(Lookup), GetType(GetSender))
<CrmOutput("User"), CrmReferenceTarget("systemuser")> _
Public Property User() As Lookup
        Return CType(Me.GetValue(UserProperty), Lookup)
    End Get
    Set(ByVal value As Lookup)
        Me.SetValue(UserProperty, value)
    End Set
End Property
Public Shared QueueProperty As DependencyProperty = DependencyProperty.Register("Queue", GetType(Lookup), GetType(GetSender))
<CrmOutput("Queue"), CrmReferenceTarget("queue")> _
Public Property Queue() As Lookup
        Return CType(Me.GetValue(QueueProperty), Lookup)
    End Get
    Set(ByVal value As Lookup)
        Me.SetValue(QueueProperty, value)
    End Set
End Property

Again, compiling and registering the assembly exposes these output parameter to the CRM workflow editor, and in this case they are visible in the create case form.

Custom Workflow Activity Output Parameters

Finally, I had to write the code to call the CRM web service, retrieve the "from" attribute of the "e-mail" entity we are interested in and obtain the lookup type of the sender (if one exists)

Protected Overrides Function Execute(ByVal executionContext As ActivityExecutionContext) As ActivityExecutionStatus
    ' Set default NULL values for output properties
    Me.Contact.IsNull = True
    Me.Contact.IsNullSpecified = True
    Me.Account.IsNull = True
    Me.Account.IsNullSpecified = True
    Me.Lead.IsNull = True
    Me.Lead.IsNullSpecified = True
    Me.User.IsNull = True
    Me.User.IsNullSpecified = True
    Me.Queue.IsNull = True
    Me.Queue.IsNullSpecified = True
    ' Get the context service
    Dim contextService As IContextService = CType(executionContext.GetService(GetType(IContextService)), IContextService)
    ' Get the context
    Dim context As IWorkflowContext = contextService.Context
    ' Create an instance of CrmService
    Dim service As ICrmService = context.CreateCrmService
    ' Make sure that the input lookup field has been specified
    If Not (Email Is Nothing) Then
        ' Make sure that the input lookup field is of type "email"
        If Email.type = "email" Then
            ' Initalise the TargetRetrieveEmail class
            Dim target As New Microsoft.Crm.SdkTypeProxy.TargetRetrieveEmail
            ' Set the EntityId property equal to the E-mail Id
            target.EntityId = Email.Value
            ' Only retrieve the "from" attribute of the "email" entity
            Dim columnSet As New Microsoft.Crm.Sdk.Query.ColumnSet
            columnSet.AddColumns(New String() {"from"})
            ' Initialise the RetrieveRequest class
            Dim request As New Microsoft.Crm.SdkTypeProxy.RetrieveRequest
            request.Target = target
            request.ColumnSet = columnSet
            ' Call the CRM web service to retrieve the email entity
            Dim response As RetrieveResponse = service.Execute(request)
            Dim emailEntity As Microsoft.Crm.SdkTypeProxy.email = CType(response.BusinessEntity, Microsoft.Crm.SdkTypeProxy.email)
            ' email.from is a array of type "microsoft.crm.sdktypeproxy.activityparty"
            Dim fromAttribute() As Microsoft.Crm.SdkTypeProxy.activityparty = emailEntity.from
            ' Determine the entity type of the first activityparty
            ' Return the correct entity type
            If Not (fromAttribute Is Nothing) Then
                If Not (fromAttribute(0) Is Nothing) Then
                    If Not (fromAttribute(0).partyid Is Nothing) Then
                        Select Case fromAttribute(0).partyid.type
                            Case "contact"
                                Me.Contact = fromAttribute(0).partyid
                            Case "account"
                                Me.Account = fromAttribute(0).partyid
                            Case "lead"
                                Me.Lead = fromAttribute(0).partyid
                            Case "systemuser"
                                Me.User = fromAttribute(0).partyid
                            Case "queue"
                                Me.Queue = fromAttribute(0).partyid
                        End Select
                    End If
                End If
            End If
        End If
    End If
    ' Set up return parameter
    Return ActivityExecutionStatus.Closed
End Function

Now, I'm not going to delve into the code itself, but I stripped out any error handling, logging etc to leave just the key functionality, so hopefully it is pretty self explanatory.

The final piece of the puzzle is to build the workflow itself. In this example I have constructed a very simple example which works as follows:

  1. Call the "Get Sender" activity and pass the e-mail as the input parameter
  2. Test the output parameters for Null data (i.e the sender is not recognised). This can be tested for by using the "Does Not Contain Data" property of each output parameter.
  3. If one of the output properties does contain data, then create a new case and set the customer field to be the sender of the e-mail.

This workflow can be seen in the series of screenshots below.

Basic Workflow Steps

Here we are testing each of the output parameters of the custom workflow activity for Null data.

Is sender recognised?

Here we are mapping the outputs of the custom workflow activity to the "Customer" field of a new case.

Map customer field to sender

As you can see, just a little bit of code solves this issue. In addition, we can now add a couple of additional workflow steps to create an "Unknown Contact" record if the sender isn't recognised.

In order to make it easier for you to implement your own custom workflow activity, you can download the source code here. In addition, if you wish to use the functionality as-is, I've also included the test workflow which you can import as a customisation, as well as the compiled assembly file - all you have to do is register it with your own CRM application.

This posting is provided "AS IS" with no warranties, and confers no rights.

Laughing Boy


Comments (6)

  1. Manos says:

    Great! Thanks, I was trying to create (!) something similar…

    Now, I have a similar(?) case senario. I have an xml attachement with some various data like customer name or serial number etc; do you think is possible to upload these data to specific table (like case for expample)?

    Thanks in advance,


  2. Tim says:

    Just tried this… doesn't work. Pity.

  3. Achim Ramesohl says:

    Hi Simon,

    Is your solution available for CRM 2011?

    I am having a situation which is very similar to your case.



  4. Rudi Groenewald says:

    Hi Simon,

    This seems like it would solve my problem.  My questions is, does this support CRM 2011 online?  Also, it seems I cant get this solution added… solution.xml missing…

  5. Simon Hutson says:

    CRM Online only supports Custom Workflow Activities written in .NET 4.0 (with platform update 3) or .NET 4.5. Unfortunately this CRM 4.0 solution will need to be re-written for CRM 2011 before you can upload it to CRM Online – msdn.microsoft.com/…/gg334459.aspx

  6. Rudi Groenewald says:

    Allright, thanks a lot simon.

Skip to main content