Workflow E-mail Utilities

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

The new workflow functionality of Microsoft Dynamics CRM 4.0 opens new and exciting opportunities for developers and end users to easily create sophisticated business logic. However, sending alert-type e-mails is still one of the most common uses I see used for workflow. As you begin to work with CRM’s workflow e-mail capabilities, I hear two common requests frequently:

1. The ability to add a hyperlink URL to the body of a workflow generated e-mail message

2. Text in ntext fields do not display properly in an HTML email.

Unfortunately, neither of these requests works natively with CRM. In the case of #1, CRM does not surface the record id as a dynamic value, so you are unable to construct the URL properly in an e-mail.

With request #2, CRM doesn’t translate the ntext field into HTML. Take for example the following lead record. The text in the Description field is on 3 lines.


When a workflow e-mail is sent, the text in the Description field gets concatenated as seen in this example:


In this post, I demonstrate how you can solve both of the earlier requests to enhance the native Send E-mail workflow action using a very simple workflow assembly.

Creating the Workflow Utility Solution
As many of you know by now, Microsoft based the Dynamics CRM 4.0 workflow on Windows Workflow Foundation (WF). This choice allows developers to easily create additional workflow logic to use within your CRM workflow rules.

Our workflow utilities solution will have two classes UrlBuilder and FormatLineBreaks. The UrlBuilder class simply creates an instance of the IContextService and then retrieves the current context. The context’s PrimaryEntityId property contains the record id that triggered the workflow. The code then simply appends the entity id to the inputted URL and formats it as a hyperlink. The code for FormatLineBreaks is even simpler…I simply replace any ASCII line breaks with an HTML break node.

Start by creating a basic workflow project which will contain your workflow activity classes. The CRM SDK, various books, and numerous blog posts describe this in better detail, so I will just walk you through the process quickly.

  1. Using Visual Studio 2008, create a new Workflow Activity Library project targeting version 3.0 of the .NET Framework, and then do the following
    1. Digitally sign it
    2. Add references to the Microsoft Dynamics CRM SDK assembly files (located as part of the SDK download).
  2. Create a new class, and name it UrlBuilder.
  3. Replace the default code in the UrlBuilder class with the following code:
   1: using System;
   2: using System.Workflow.ComponentModel;
   3: using System.Workflow.Activities;
   4: using Microsoft.Crm.Workflow;
   6: namespace SonomaPartners.Crm.Workflow.Utilities
   7: {
   8:   [CrmWorkflowActivity("Url Builder", "Utilities")]
   9:   public partial class UrlBuilder : SequenceActivity
  10:   {
  11:     // Override this method with our custom logic
  12:     protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
  13:     {
  14:       //Get context
  15:       IContextService contextService = (IContextService)executionContext.GetService(typeof(IContextService));
  16:       IWorkflowContext ctx = contextService.Context;
  18:       // Get the record id from the context
  19:       Guid id = ctx.PrimaryEntityId;
  21:       // Configure the Url and pass back to the output parameter
  22:       string fullUrl = string.Format(this.Url,id);
  23:       this.RecordUrl = string.Format(@"<a href=""{0}"">{0}</a>", fullUrl);
  25:       return base.Execute(executionContext);
  26:     }
  28:     // Allow the user to set the Url with this input parameter
  29:     public static DependencyProperty UrlProperty = DependencyProperty.Register("Url", typeof(string), typeof(UrlBuilder));
  30:     [CrmInput("Url")]
  31:     public string Url
  32:     {
  33:       get { return (string)base.GetValue(UrlProperty); }
  34:       set { base.SetValue(UrlProperty, value); }
  35:     }
  37:     // Returns the formatted record Url to the workflow rule for use
  38:     public static DependencyProperty RecordUrlProperty = DependencyProperty.Register("RecordUrl", typeof(string), typeof(UrlBuilder));
  39:     [CrmOutput("RecordUrl")]
  40:     public string RecordUrl
  41:     {
  42:       get { return (string)base.GetValue(RecordUrlProperty); }
  43:       set { base.SetValue(RecordUrlProperty, value); }
  44:     }
  45:   }
  46: }

  1. Create another new class, and name it FormatLineBreaks.
  2. Replace the default code in the FormatLineBreaks class with the following code:
   1: using System;
   2: using System.Workflow.ComponentModel;
   3: using System.Workflow.Activities;
   4: using Microsoft.Crm.Workflow;
   6: namespace SonomaPartners.Crm.Workflow.Utilities
   7: {
   8:   [CrmWorkflowActivity("Format Line Breaks", "Utilities")]
   9:   public partial class FormatLineBreaks : SequenceActivity
  10:   {
  11:     protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
  12:     {
  13:             this.FormattedValue = (this.InputValue != null) ? this.InputValue.Replace("\n", "<br>") : string.Empty;
  14:             return base.Execute(executionContext);
  15:     }
  17:     // Input value
  18:     public static DependencyProperty InputValueProperty = DependencyProperty.Register("InputValue", typeof(string), typeof(FormatLineBreaks));
  19:     [CrmInput("Input Value")]
  20:     public string InputValue
  21:     {
  22:       get { return (string)base.GetValue(InputValueProperty); }
  23:       set { base.SetValue(InputValueProperty, value); }
  24:     }
  26:     // Returns the updated value
  27:     public static DependencyProperty FormattedValueProperty =
  28:  DependencyProperty.Register("FormattedValue", typeof(string), typeof(FormatLineBreaks));
  29:     [CrmOutput("Formatted Value")]
  30:     public string FormattedValue
  31:     {
  32:       get { return (string)base.GetValue(FormattedValueProperty); }
  33:       set { base.SetValue(FormattedValueProperty, value); }
  34:     }
  35:   }
  36: }

  1. Build the solution.

Registering the Utilities Assembly

Your workflow assembly is now ready for deployment. We use Ajith's free Plug-in Registration Tool to add the workflow assembly to our Dynamics CRM application. Don't worry that the name says works for workflow assemblies as well!

  1. Download the tool if you don't already have it.
  2. Open the tool and create a connection to your CRM Web server and then connect to it.
  3. Select your organization and click Connect.
  4. Click Register, and then select Register New Assembly.
  5. In the Register New Plugin window, choose your compiled workflow utilities assembly.
  6. Leave the database option selected and click Register Selected Plugins.

  7. The tool will then register the assembly and provide a pop-up message if it was successful.

Using the E-mail Utilities within Workflow User Interface

Now that the custom solution is complete and registered with Microsoft Dynamics CRM, the next step is to use it in a workflow rule.

  1. Create a new workflow rule for the Lead entity called New Lead Notification.
  2. Add a step, selecting the new Url Builder option step located in the Utilities group.

  3. Enter Url Builder as the step's description.
  4. Click Set Properties, and enter the correct URL to the Lead's edit page as the Value of the Url property. For example:

    http://<crmserver>/<organization name>/sfa/leads/edit.aspx?id={0}


  5. Add a step, selecting the new Format Line Breaks option step located in the Utilities group.
  6. Enter Format Description as the step's description.
  7. Click Set Properties, and set the lead's description attribute.

  8. Add a new Send E-mail step and enter Send Alert E-mail for the description.
  9. Leave the Create New Message option selected, and click Set Properties.
  10. In the Send E-mail dialog box, configure the e-mail message, and in the body, add the new Url Builder and Format Line Breaks dynamic value.

  11. Save and publish your workflow rule.


When a new lead is created, I will receive an alert e-mail that looks like this:


Additional Comments

Here are some additional thoughts and tips to consider.

  • I used Visual Studio 2008 to create my workflow assembly. You can also use Visual Studio 2005 to create a custom workflow assembly, but be sure to add the Windows Workflow Foundation framework first.
  • Also check Nirav’s post about compiling the workflow activity with anycpu flag to provide the best compatibility between 32 and 64 bit environments.
  • You can easily add additional utility classes to build out a useful library for your users.
  • If you modify the assembly and register it again with your deployment, you may need to restart IIS and the AsyncService for your changes to take effect.
  • A complete list of record URL’s are listed in the SDK, although you could just open a record in Internet Explorer to determine the correct link.
  • For custom entities, your input URL would be something similar to: http://<crmServer>/<organizationname>/userdefined/edit.aspx?etn=<entity name>&id={0}. Consider using the etn query string parameter instead of the etc (object type code), since the object type code on custom entities can vary between deployments.

  • My example assumes that the URL you enter will be accessible to your users. If you use IFD with your deployment, you can either create proper domain mappings within your DNS to properly open the hyperlink. Or even simpler (although maybe a bit less elegant), you could create two instances of the UrlBuilder with two different URLs and set up two links in your e-mail for internal users and one for external users.
  • You could consider wrapping the Send e-mail and the workflow assembly steps into a child workflow, which can then be reused within other workflow rules. Just remember to check the As a child workflow option before publishing.
  • Finally, don't confuse the Create Record step (and selecting E-mail activity) with the Send E-mail step. If you use the Create Record step to create an E-mail activity, Dynamics CRM will create the e-mail activity but not actually send it. This approach is useful if you want a user to manually review or make alterations to the e-mail prior to sending. If you want the workflow rule to automatically send the e-mail, then be sure to choose the Send E-mail step.

Hopefully, this simple example demonstrates the ease by which a developer can enhance the native functionality of workflow.

Happy coding!

Jim Steger

Comments (14)

  1. maruf_d says:

    Thanks for sharing this information Jim. This is very helpful. I was wondering if we could attach files to these emails. Any pointers on this would be helpful.



  2. AJ says:

    I am looking for a similar function, by attaching files, but I think the workaround, will be to:

    1. Use SharePoint as a document repository

    2. Link the file in the email to the SharePoint site

    That would reduce the email overhead, and take advantage of the SharePoint versioning, and access rights if needed.

  3. Hari Krishnan says:

    Hi All,

    I have been using CRM 4 for my SaaS application. I need to send email using the from and to address from the custom entity i have created via workflow rule.

    But currently it doesnt show the email attribute i have created.

    Also is there a way to mention more than one email address for the "to" field.

  4. Viktor says:

    I’m getting an error trying to register the assembly. The Plugin registration tool reports 1 assembly registered, 2 plugins encountered errors.

    Here are the details:

    Unhandled Exception: System.Web.Services.Protocols.SoapException: Server was unable to process request.

    Detail: <detail><error>


     <description>Assembly can not be loaded from C:Program FilesMicrosoft Dynamics CRM ServerserverbinassemblyTestWorkflowActivityLibrary2.dll.</description>



      at System.Web.Services.Protocols.SoapHttpClientProtocol.ReadResponse(SoapClientMessage message, WebResponse response, Stream responseStream, Boolean asyncCall)

      at System.Web.Services.Protocols.SoapHttpClientProtocol.Invoke(String methodName, Object[] parameters)

      at PluginRegistrationTool.CrmSdk.CrmService.Create(BusinessEntity entity) in D:CRM 4.0PluginRegistrationWeb ReferencesCrmSdkReference.cs:line 212

      at PluginRegistrationTool.RegistrationHelper.RegisterPluginType(CrmOrganization org, CrmPlugin plugin) in D:CRM 4.0PluginRegistrationRegistrationHelper.cs:line 314

      at PluginRegistrationTool.PluginRegistrationForm.btnRegister_Click(Object sender, EventArgs e) in D:CRM 4.0PluginRegistrationPluginRegistrationForm.cs:line 490

    Can anyone help?

    Thanks in advance!


  5. Let’s say there is an nvarchar Attribute on an Entity, and that field is populated with a URL.

    Also there is a fixed string that needs to be appended to this URL and then passed as a clickable URL on an e-mail.

    Can this be accomplished using the method specified here? Or is there perhaps a better way?

    Any help would be greatly appreciated.

  6. Sam says:

    The Above Example helps me a lot, but i have one query.

    In the 4th step, where in you said,enter the correct URL to the Lead’s edit page as the Value of the Url property. For example:

    http://<crmserver>/<organization name>/sfa/leads/edit.aspx?id={0}

    How is the id being used here. is it used as static or the id is being generated dynamically

  7. Robert says:


    In the 4th step, where you are adding the lead page url, i see you are adding the url directly , is there any possiblity to add the GUID to it dynamically

  8. Dave says:

    How would you modify the above so that it could take another input parameter for the URL… so that rather than displaying the full hyper link it displays a text of your choice.

    For Example,

    Rather than

    you get

    Click Here (Dependant on input)

  9. Anthony Kylitis says:

    This comment is related to ntext fields in emails.  The line feeds work if the email does not use an email template.  Don’t know if this was fixed through a rollup after this blog posted.  In any case, it’s nice to have this customer workflow step when you do in fact want to use an Email Template.

  10. Mark Meehan says:

    This is great. A huge problem that I can’t get around is sending out from a CampaignResponse Email instead of Customer. In many instances, a campaign response may not be a lead and may just be a new reqistration from a web page hooked into the Campaign Response. I would like to send an email to the CampaignResponse email attribute rather than creating a Lead and adding the lead to the customer attribute and then use the customer to send from. Any suggestions? This would support the Event Accelerators.

    Thank you.


  11. Amazing. Just Amazing. Great article and Great Solution.

    I actually have another way for doing this using Java-Scripting but this way is better. i will post mine soon.

  12. Mark Guinan says:

    Excellent article.  

    I actually have a question that pertains to this utility.  How could one go about using the UrlBuilder for different environments (Dev, Test, and Production)?  Right now, we are using the UrlBuilder in both Dev and Test, but since they are different environments, the Guid in the URL must be changed manually in the utility and saved for the URL to build correctly.  Is there any way to create a workflow (or some other method) which will allow the UrlBuilder to generate different URLs, based on each environment?  Thanks

  13. Steve J. says:

    I'm looking for a way to include a URL to a lead record in a workflow-generated email that is created when the lead is assigned to a user. The above looks ideal.


    WIll it work with Dynamics CRM Online? It looks to me like the potential roadblock would be the step where I must register the assembly with the server.

    I admit this procedure intimidates me, but I think I could manage it OK. I don't want to get halfway through though, and then discover I can't register the assembly because I'm using CRM online, rather than on-premise.



Skip to main content