Understanding the Gap Between the CRM SDK and Real‑World Requirements
When building extensions for Microsoft Dynamics CRM, developers often rely on the SDK to perform most data operations. The SDK is comprehensive, but it doesn't always cover every scenario, especially when you need to intervene earlier in the message flow or perform bulk updates that would be cumbersome through the API. One common situation is handling incoming email that arrives in Exchange before the CRM Exchange Connector has had a chance to associate it with an entity. In that case, you need to examine the email’s subject, check for a GUID, or otherwise determine if the sender’s address matches an existing lead, account, or contact. Once you know where the email belongs, you must create a closed activity email in CRM and place it in the general queue. The SDK provides a way to create an activity, but there is no direct method for marking it as closed in a single call. This gap forces developers to use lower‑level techniques such as direct SQL updates.
Direct SQL programming in Dynamics CRM is a topic that sparks debate. Official Microsoft documentation warns against it because bypassing the SDK can lead to data integrity issues, and Microsoft’s support policy excludes this scenario. However, for highly specialized tasks that the SDK can’t perform, many experienced developers turn to direct updates. The key is to understand which fields govern an activity’s state and how to set them correctly. When done responsibly, a direct SQL update can be both safe and efficient.
In this article, we walk through a practical example that illustrates both approaches: first, we use the SDK to create an email activity with the appropriate parties and owner, then we switch to a direct SQL update to mark the activity as closed. The code snippets are written in C# and use the legacy 1.2 SDK, which is still common in many on‑premise deployments. While newer SDK versions provide additional abstractions, the concepts remain the same.
To start, let’s describe the workflow you’ll need to implement. When an email arrives, you must:
- Inspect its subject for a CRM GUID.
- If no GUID is present, look up the sender’s email address in the CRM database.
- Determine the matching entity type (lead, account, or contact).
- Create an email activity that is attached to the entity.
- Close the activity immediately after creation.
Implementing this logic requires both SDK calls and direct database manipulation. The following sections provide the detailed code and explain the rationale behind each step.
Before you start, make sure you have the following prerequisites:
- Access to the Dynamics CRM server’s database and the ability to run SQL commands against it.
- A reference to the CRM 1.2 SDK assemblies in your project.
- Proper error handling and logging, as the code interacts with external services.
With those items in place, you can begin writing the functions that will perform the tasks described above.
Creating a Closed Email Activity with the SDK and SQL
The first step is to construct a helper method that will create the activity using the SDK. The method takes several parameters that define who owns the activity, the target entity, and the email details. Below is the complete implementation, followed by a breakdown of each part.
public Guid CreateEmailActivity(</p> <p> Guid userId,</p> <p> int objectType,</p> <p> Guid objectId,</p> <p> string mailFrom,</p> <p> CRMUser crmUser,</p> <p> string subject,</p> <p> string body)</p> <p>{</p> <p> try</p> <p> {</p> <p> // Set up a proxy to call the WhoAmI operation and get the user context</p> <p> var bizUser = new Microsoft.Crm.Platform.Proxy.BizUser();</p> <p> var credentials = new NetworkCredential(sysUserId, sysPassword, sysDomain);</p> <p> bizUser.Url = crmDir + "BizUser.srf";</p> <p> bizUser.Credentials = credentials;</p> <p> var userAuth = bizUser.WhoAmI();</p> <p> // Prepare the email proxy</p> <p> var email = new Microsoft.Crm.Platform.Proxy.CRMEmail</p> <p> {</p> <p> Credentials = credentials,</p> <p> Url = crmDir + "CRMEmail.srf"</p> <p> };</p> <p> // Build the XML payload for the email activity</p> <p> string strActivityXml =</p> <p> <emailactivity></p> <p> <messagesubject><![CDATA[" + subject + "]]></messagesubject></p> <p> <messagebody><![CDATA[" + body.Replace(" ", "<br>") + "]]></messagebody></p> <p> <ownerid type="" + Microsoft.Crm.Platform.Types.ObjectType.otSystemUser.ToString() + ""></p> <p> " + userId.ToString("B") + @"</p> <p> </ownerid></p> <p> </emailactivity>";</p> <p> // Build the XML for the activity parties</p> <p> string strPartiesXml =</p> <p> <activityparties></p> <p> <activityparty></p> <p> <addressused>" + crmUser.GetEmailAddress() + @</addressused></p> <p> <partyobjecttypecode>" + Microsoft.Crm.Platform.Types.ObjectType.otSystemUser.ToString() + @</partyobjecttypecode></p> <p> <partyid>" + crmUser.GetId().ToString("B") + @</partyid></p> <p> <participationtypemask>" + Microsoft.Crm.Platform.Types.ACTIVITY_PARTY_TYPE.ACTIVITY_PARTY_TO_RECIPIENT.ToString() + @</participationtypemask></p> <p> </activityparty></p> <p> <addressused>" + mailFrom + @</addressused></p> <p> <partyobjecttypecode>" + GetObjectTypeCode(objectType) + @</partyobjecttypecode></p> <p> <partyid>" + objectId.ToString("B") + @</partyid></p> <p> <participationtypemask>" + Microsoft.Crm.Platform.Types.ACTIVITY_PARTY_TYPE.ACTIVITY_PARTY_SENDER.ToString() + @</participationtypemask></p> <p> </activityparties>";</p> <p> // Create the email activity</p> <p> Guid emailId = new Guid(email.Create(userAuth, strActivityXml, strPartiesXml));</p> <p> return emailId;</p> <p> }</p> <p> catch (SoapException e)</p> <p> {</p> <p> log.Debug("ErrorMessage: " + e.Message + " " + e.Detail.OuterXml + " Source: " + e.Source);</p> <p> }</p> <p> catch (Exception e)</p> <p> {</p> <p> log.Debug(e.Message + " " + e.StackTrace);</p> <p> }</p> <p> return Guid.Empty;</p> <p>}</p> <p>private string GetObjectTypeCode(int objectType)</p> <p>{</p> <p> switch (objectType)</p> <p> {</p> <p> case (int)Microsoft.Crm.Platform.Types.ObjectType.otAccount:</p> <p> return Microsoft.Crm.Platform.Types.ObjectType.otAccount.ToString();</p> <p> case (int)Microsoft.Crm.Platform.Types.ObjectType.otContact:</p> <p> return Microsoft.Crm.Platform.Types.ObjectType.otContact.ToString();</p> <p> case (int)Microsoft.Crm.Platform.Types.ObjectType.otLead:</p> <p> return Microsoft.Crm.Platform.Types.ObjectType.otLead.ToString();</p> <p> default:</p> <p> throw new ArgumentException("Unsupported object type");</p> <p> }</p> <p>}</p>That method does the heavy lifting of creating an email activity in CRM. The XML strings are carefully crafted to include the subject, body, owner, and parties. The body replaces line breaks with HTML
<br>tags so that the email content renders correctly in CRM. TheGetObjectTypeCodehelper translates an integer code into the string representation that the SDK expects.Once the activity is created, it is in the “Active” state. To make it “Closed” you would normally call the
Closemethod on the activity proxy. However, that method is missing in SDK 1.2, and calling it would raise an error. The workaround is to update the relevant status fields directly in the database. TheUpdateActivityCodesfunction below shows how to do that safely.public void UpdateActivityCodes(Guid emailId)</p> <p>{</p> <p> try</p> <p> {</p> <p> using (var command = conn.CreateCommand())</p> <p> {</p> <p> command.CommandText = @"</p> <p> UPDATE ActivityBase</p> <p> SET DirectionCode = @Direction,</p> <p> StateCode = @State,</p> <p> PriorityCode = @Priority</p> <p> WHERE ActivityId = @ActivityId";</p> <p> command.Prepare();</p> <p> command.Parameters.Add(new OleDbParameter("@Direction",</p> <p> Microsoft.Crm.Platform.Types.EVENT_DIRECTION.ED_INCOMING));</p> <p> command.Parameters.Add(new OleDbParameter("@State",</p> <p> Microsoft.Crm.Platform.Types.ACTIVITY_STATE.ACTS_CLOSED));</p> <p> command.Parameters.Add(new OleDbParameter("@Priority",</p> <p> Microsoft.Crm.Platform.Types.PRIORITY_CODE.PC_MEDIUM));</p> <p> command.Parameters.Add(new OleDbParameter("@ActivityId", emailId));</p> <p> log.Debug($"Updating activity {emailId} to closed state.");</p> <p> command.ExecuteNonQuery();</p> <p> }</p> <p> }</p> <p> {</p> <p> }</p> <p>}</p>The update touches three columns:
DirectionCode– indicates whether the activity was inbound or outbound. For incoming mail you useED_INCOMINGStateCode– the primary state field. Setting it toACTS_CLOSEDtransitions the activity from “Active” to “Closed.”PriorityCode– optional but useful for sorting in CRM views;PC_MEDIUMis a typical choice.Because the SDK does not expose a method to close an activity in 1.2, this direct SQL approach ensures that the activity appears in CRM as a closed item, without the need for manual intervention or an additional UI workflow.
When integrating these two functions, your main code path will look like this:
// Assume we have determined the following variables</p> <p>Guid userId = ...;</p> <p>int objectType = ...; // e.g., otLead</p> <p>Guid objectId = ...;</p> <p>string mailFrom = ...;</p> <p>CRMUser crmUser = ...;</p> <p>string subject = ...;</p> <p>string body = ...;</p> <p>// Create the activity</p> <p>Guid emailId = CreateEmailActivity(userId, objectType, objectId, mailFrom, crmUser, subject, body);</p> <p>if (emailId != Guid.Empty)</p> <p>{</p> <p> // Close the newly created activity</p> <p> UpdateActivityCodes(emailId);</p> <p>}</p>This pattern keeps your code clear and separated: the SDK handles creation and party assignment, while the SQL block handles the state transition. You avoid the pitfalls of trying to force the SDK to do something it isn’t designed to do.
While direct database updates can be risky, the approach outlined here is safe because it only modifies the few fields that control an activity’s state. It does not alter relationships or delete data. Nevertheless, always test the code in a sandbox environment before deploying it to production, and keep backups of the database just in case.
In a typical deployment, you might run this logic as part of a server‑side plug‑in triggered by the email arrival event, or as a background job that processes queued messages. The key advantage is that you can create and close the activity in a single pass, saving time and reducing complexity for downstream processes.
For more details on setting up the Exchange handler that intercepts emails before they hit Exchange, refer to this external resource: ExchangeHandlerExample.
Andrew Karasev, Chief Technology Officer at Alba Spectrum Technologies, has led many such customizations across the United States and internationally. He is a recognized expert in Dexterity, SQL, C#.Net, Crystal Reports, and Microsoft CRM SDK development. For additional insights, visit
Tags





No comments yet. Be the first to comment!