Thursday, February 9, 2012

Send an Email Asynchronously in Microsoft Dynamics CRM 2011 using C# or Jscript

This illustration shows how to send an email asynchronously in Microsoft Dynamics CRM 2011 with BackgroundSendEmailRequest.  This example will be given in Jscript (SOAP) and in C# (.NET).

NOTE: Two days ago I covered creating the email activity in CRM in code (a prerequisite to today's tutorial).  Check it out if you are having trouble creating the entity: http://mileyja.blogspot.com/2012/02/create-email-activity-in-microsoft.html

Additionally, it's interesting to note that you can use a QueryExpression to filter on what non-sent email it will send.  It allows you to send more than one at once.

Ok, here is what the code looks like!
First in C#:
BackgroundSendEmailRequest req = new BackgroundSendEmailRequest();   
QueryExpression query = new QueryExpression()
{
    EntityName = "email",
    ColumnSet = new ColumnSet(true),
    Criteria = new FilterExpression
    {
        FilterOperator = LogicalOperator.And,
        Conditions = 
        {
            new ConditionExpression
            {
                AttributeName = "regardingobjectid",
                Operator = ConditionOperator.Equal,
                Values = { "AC946D36-3E41-E111-9D05-0050569838D6" }
            }
        }
    }
};

req.Query = query;
service.Execute(req);


If you need help instantiating a service object in .NET within a plugin check out this post:
http://mileyja.blogspot.com/2011/04/instantiating-service-object-within.html

Now here is the Jscript nicely formatted by the CRM 2011 SOAP formatter. Available at: http://crm2011soap.codeplex.com/

Now in Jscript


This example is asynchronous, if you want to learn how to make JScript SOAP calls synchronously please visit this posthttp://mileyja.blogspot.com/2011/07/using-jscript-to-access-soap-web.html


if (typeof (SDK) == "undefined")
   { SDK = { __namespace: true }; }
       //This will establish a more unique namespace for functions in this library. This will reduce the 
       // potential for functions to be overwritten due to a duplicate name when the library is loaded.
       SDK.SAMPLES = {
           _getServerUrl: function () {
               ///<summary>
               /// Returns the URL for the SOAP endpoint using the context information available in the form
               /// or HTML Web resource.
               ///</summary>
               var ServicePath = "/XRMServices/2011/Organization.svc/web";
               var serverUrl = "";
               if (typeof GetGlobalContext == "function") {
                   var context = GetGlobalContext();
                   serverUrl = context.getServerUrl();
               }
               else {
                   if (typeof Xrm.Page.context == "object") {
                         serverUrl = Xrm.Page.context.getServerUrl();
                   }
                   else
                   { throw new Error("Unable to access the server URL"); }
                   }
                  if (serverUrl.match(/\/$/)) {
                       serverUrl = serverUrl.substring(0, serverUrl.length - 1);
                   } 
                   return serverUrl + ServicePath;
               }, 
           BackgroundSendEmailRequest: function () {
               var requestMain = ""
               requestMain += "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">";
               requestMain += "  <s:Body>";
               requestMain += "    <Execute xmlns=\"http://schemas.microsoft.com/xrm/2011/Contracts/Services\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">";
               requestMain += "      <request i:type=\"b:BackgroundSendEmailRequest\" xmlns:a=\"http://schemas.microsoft.com/xrm/2011/Contracts\" xmlns:b=\"http://schemas.microsoft.com/crm/2011/Contracts\">";
               requestMain += "        <a:Parameters xmlns:c=\"http://schemas.datacontract.org/2004/07/System.Collections.Generic\">";
               requestMain += "          <a:KeyValuePairOfstringanyType>";
               requestMain += "            <c:key>Query</c:key>";
               requestMain += "            <c:value i:type=\"a:QueryExpression\">";
               requestMain += "              <a:ColumnSet>";
               requestMain += "                <a:AllColumns>true</a:AllColumns>";
               requestMain += "                <a:Columns xmlns:d=\"http://schemas.microsoft.com/2003/10/Serialization/Arrays\" />";
               requestMain += "              </a:ColumnSet>";
               requestMain += "              <a:Criteria>";
               requestMain += "                <a:Conditions>";
               requestMain += "                  <a:ConditionExpression>";
               requestMain += "                    <a:AttributeName>regardingobjectid</a:AttributeName>";
               requestMain += "                    <a:Operator>Equal</a:Operator>";
               requestMain += "                    <a:Values xmlns:d=\"http://schemas.microsoft.com/2003/10/Serialization/Arrays\">";
               requestMain += "                      <d:anyType i:type=\"e:string\" xmlns:e=\"http://www.w3.org/2001/XMLSchema\">AC946D36-3E41-E111-9D05-0050569838D6</d:anyType>";
               requestMain += "                    </a:Values>";
               requestMain += "                  </a:ConditionExpression>";
               requestMain += "                </a:Conditions>";
               requestMain += "                <a:FilterOperator>And</a:FilterOperator>";
               requestMain += "                <a:Filters />";
               requestMain += "              </a:Criteria>";
               requestMain += "              <a:Distinct>false</a:Distinct>";
               requestMain += "              <a:EntityName>email</a:EntityName>";
               requestMain += "              <a:LinkEntities />";
               requestMain += "              <a:Orders />";
               requestMain += "              <a:PageInfo>";
               requestMain += "                <a:Count>0</a:Count>";
               requestMain += "                <a:PageNumber>0</a:PageNumber>";
               requestMain += "                <a:PagingCookie i:nil=\"true\" />";
               requestMain += "                <a:ReturnTotalRecordCount>false</a:ReturnTotalRecordCount>";
               requestMain += "              </a:PageInfo>";
               requestMain += "              <a:NoLock>false</a:NoLock>";
               requestMain += "            </c:value>";
               requestMain += "          </a:KeyValuePairOfstringanyType>";
               requestMain += "        </a:Parameters>";
               requestMain += "        <a:RequestId i:nil=\"true\" />";
               requestMain += "        <a:RequestName>BackgroundSendEmail</a:RequestName>";
               requestMain += "      </request>";
               requestMain += "    </Execute>";
               requestMain += "  </s:Body>";
               requestMain += "</s:Envelope>";
               var req = new XMLHttpRequest();
               req.open("POST", SDK.SAMPLES._getServerUrl(), true)
               req.setRequestHeader("Accept", "application/xml, text/xml, */*");
               req.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
               req.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute");
               var successCallback = null;
               var errorCallback = null;
               req.onreadystatechange = function () { SDK.SAMPLES.BackgroundSendEmailResponse(req, successCallback, errorCallback); };
               req.send(requestMain);
           },
       BackgroundSendEmailResponse: function (req, successCallback, errorCallback) {
               ///<summary>
               /// Recieves the assign response
               ///</summary>
               ///<param name="req" Type="XMLHttpRequest">
               /// The XMLHttpRequest response
               ///</param>
               ///<param name="successCallback" Type="Function">
               /// The function to perform when an successfult response is returned.
               /// For this message no data is returned so a success callback is not really necessary.
               ///</param>
               ///<param name="errorCallback" Type="Function">
               /// The function to perform when an error is returned.
               /// This function accepts a JScript error returned by the _getError function
               ///</param>
               if (req.readyState == 4) {
               if (req.status == 200) {
               if (successCallback != null)
               { successCallback(); }
               }
               else {
                   errorCallback(SDK.SAMPLES._getError(req.responseXML));
               }
           }
       },
       _getError: function (faultXml) {
           ///<summary>
           /// Parses the WCF fault returned in the event of an error.
           ///</summary>
           ///<param name="faultXml" Type="XML">
           /// The responseXML property of the XMLHttpRequest response.
           ///</param>
           var errorMessage = "Unknown Error (Unable to parse the fault)";
           if (typeof faultXml == "object") {
               try {
                   var bodyNode = faultXml.firstChild.firstChild;
                   //Retrieve the fault node
                   for (var i = 0; i < bodyNode.childNodes.length; i++) {
                       var node = bodyNode.childNodes[i];
                       //NOTE: This comparison does not handle the case where the XML namespace changes
                       if ("s:Fault" == node.nodeName) {
                       for (var j = 0; j < node.childNodes.length; j++) {
                           var faultStringNode = node.childNodes[j];
                           if ("faultstring" == faultStringNode.nodeName) {
                               errorMessage = faultStringNode.text;
                               break;
                           }
                       }
                       break;
                   }
               }
           }
           catch (e) { };
        }
        return new Error(errorMessage);
     },
 __namespace: true
};




To understand how to parse the response please review my post on using the DOM parser.
Now you can call the SDK.SAMPLES.BackgroundSendEmailRequest function from your form jscript handler.

Thats all there is to it!
-

5 comments:

  1. Hello Jamie,

    thanks for sharing you code, but I have a question: Once I executed the BackgroundSendEmailRequest mail email recoreds are still in status "Draft" instead of "Completed". Do i need to exeucte some other request?

    Daniel

    ReplyDelete
    Replies
    1. Did you choose criteria that you are sure picked up actual messages. I would not expect them to be in draft. I would expect them to be sent, or if you do not have your email router configured properly I would expect them to be pending.

      Delete
    2. Hi,

      I create a new email, and set the filter so that only the just created email is "included" in the background-process:

      this is the code:

      var id = this.Create(subject, message, toCollection, from, regarding);
      var backgroundRequest = new BackgroundSendEmailRequest();
      var condition = new ConditionExpression { AttributeName = "activityid", Operator = ConditionOperator.Equal, Values = { id.ToString() } };

      backgroundRequest.Query = new QueryExpression()
      {
      EntityName = Email.EntityLogicalName,
      ColumnSet = new ColumnSet(true),
      Criteria = new FilterExpression
      {
      FilterOperator = LogicalOperator.And,
      Conditions = { condition }
      }
      };
      this.Execute(backgroundRequest);



      Thanks for you help

      Daniel

      Delete
    3. Is your Async service running properly? can you see the state of the system jobs for this to make sure they ran properly?

      Delete
    4. Hi,

      I am currenlty using the outlook-plugin to the email-send operation and not the eamil-router, but this should work with the outlook-client, right?

      The BackgroundSendEmailRequest is not listed in the "System Job" view....

      Daniel

      Delete