Home > CRM > [Code Snippet] Send Email To Bulk and Track

[Code Snippet] Send Email To Bulk and Track

In this article, lets see how we can send email to bulk records by forming a query and Email template.

To understanding this better, lets send an email to all the ‘Active Contacts’ in the system.

Below are the required components to send the email to ‘Active Contacts’:

  • Query Expression of ‘Active Contacts’
    • You need to form your query as per your business requirement.
  • Email Template
    • As we are targeting ‘Active Contacts’, Email Template Type must be of ‘Contact’.


  • Email From – Entity Reference of the ‘Sender’
  • Email Regarding – Record of which all the created Emails will get associated with.

Lets see the code.

Below is the main method which sends out emails.

public static Guid SendBulkEmail(EntityReference sender, EntityReference mailRegarding, Guid emailTemplateId, QueryExpression querySenders, IOrganizationService crmService){
// Set trackingId for bulk mail request.
var trackingId = Guid.NewGuid();
var bulkMailRequest = new SendBulkMailRequest() {
Query = querySenders,
Sender = sender,
RegardingId = mailRegarding.Id,
RegardingType = mailRegarding.LogicalName,
TemplateId = emailTemplateId,
RequestId = trackingId

// Execute the async bulk email request
var resp = (SendBulkMailResponse)crmService.Execute(bulkMailRequest);
catch (Exception ex)

return trackingId;

Below are the helper methods which pass required inputs for the SendBulkEmail() method.

Get ‘Email Template ID’:

public static Guid GetEmailTemplateId(string emailTemplateName, IOrganizationService crmService)
Guid emailTemplateId = Guid.Empty;
var queryBuildInTemplates = new QueryExpression{
EntityName = “template”,
ColumnSet = new ColumnSet(“templateid”, “templatetypecode”),
Criteria = new FilterExpression(LogicalOperator.And)

queryBuildInTemplates.Criteria.AddCondition(“templatetypecode”, ConditionOperator.Equal, “contact”);
queryBuildInTemplates.Criteria.AddCondition(“title”, ConditionOperator.Equal, emailTemplateName);

var templateEntityCollection = crmService.RetrieveMultiple(queryBuildInTemplates);

if (templateEntityCollection.Entities.Count > 0){
emailTemplateId = (Guid)templateEntityCollection.Entities[0].Attributes[“templateid”];
throw new ArgumentException(“Standard Email Templates are missing”);

return emailTemplateId;

Get the ‘From’ and ‘Regarding’:

  • Note: For simplicity, I am using current user as ‘From’ and ‘Email Regarding’

public static EntityReference GetCurrentUserId(IOrganizationService crmService){
var systemUserRequest = new WhoAmIRequest();
var systemUserResponse = (WhoAmIResponse)crmService.Execute(systemUserRequest);
return new EntityReference(“systemuser”, systemUserResponse.UserId);

Get the Recipients (i.e., Active Contacts) ‘Query Expression’:

public static QueryExpression GetEmailRecipientsQuery(){
var queryContacts = new QueryExpression
EntityName = “contact”,
ColumnSet = new ColumnSet(“contactid”),
Criteria = new FilterExpression(LogicalOperator.And)

queryContacts.Criteria.AddCondition(“statecode”, ConditionOperator.Equal, 0);
//queryContacts.Criteria.AddCondition(“createdby”, ConditionOperator.EqualUserId);
return queryContacts;

That’s it, lets see how we do we call the main method

var currentUser = GetCurrentUserId(_service);

var trackingID = SendBulkEmail(currentUser, currentUser, GetEmailTemplateId(“Contact Welcome”, _service), GetEmailRecipientsQuery(), _service);

Execute above lines of code in console and you would get the response as below.


What happens on the execution of the code?:

  • A new ‘Bulk Email’ system job gets created. Go to Settings -> System Jobs


  • Once the ‘Status Reason’ turns in to ‘Succeeded’, Go to ‘Advance Find’ and query ‘Email Messages’.
  • You should see a new ‘Email’ records for each ‘Active Contact’, as the Query Expression we formed is for ‘Active Contacts’


  • Open one of the Emails and you should see the email body copied from ‘Email Template’.


  • We can also track the ‘Bulk Email’ job from the code, with below code snippet by passing the ‘Tracking ID’ (i.e., GUID) returned by SendBulkEmail() method.

private const int ARBITRARYMAXPOLLINGTIME = 60;

public static void TrackMailDelivery(Guid trackingId, IOrganizationService crmService){
var bulkQuery = new QueryByAttribute(){
EntityName = “asyncoperation”,
ColumnSet = new ColumnSet(new string[] { “requestid”, “statecode” }),
Attributes = { “requestid” },
Values = { trackingId }

// Retrieve the bulk email async operation.
var emailResponse = crmService.RetrieveMultiple(bulkQuery);Console.WriteLine(” Retrieved Bulk Email Async Operation.”);

// Monitor the async operation via polling.

Entity createdBulkMailOperation = null;

Console.WriteLine(“Checking operation’s state for ” + ARBITRARYMAXPOLLINGTIME + ” seconds.”);

while (secondsTicker > 0){
// Make sure the async operation was retrieved.
if (emailResponse.Entities.Count > 0){
// Grab the one bulk operation that has been created.
createdBulkMailOperation = emailResponse.Entities[0];

// Check the operation’s state.
if (((OptionSetValue)createdBulkMailOperation[“statecode”]).Value != 3){
// The operation has not yet completed.
// Wait a second for the status to change.

// Retrieve a fresh version the bulk delete operation.
emailResponse = crmService.RetrieveMultiple(bulkQuery);
// Stop polling because the operation’s state is now complete.
secondsTicker = 0;
// Wait a second for the async operation to activate.

// Retrieve the entity again
emailResponse = crmService.RetrieveMultiple(bulkQuery);

// Validate async operation succeeded
if (((OptionSetValue)createdBulkMailOperation[“statecode”]).Value == 3){
Console.WriteLine(“Operation Completed.”);
Console.WriteLine(“Operation not completed yet.”);


Categories: CRM
  1. Subhashree
    August 18, 2021 at 12:58 PM

    Hi Sir,

    Question is not relevant to this blog. But I do have an query related to Bulk record creation within CRM platform.
    Lets assume, we have a requirement to bulk create (may be in lakhs) record in CRM. Earlier we used to do using ExecuteMultiplerequest and it used to process 1000 records in a single batch.

    But we should avoid batch request execution in WF/Plugin as suggested by Microsoft.


    Could you please suggest how can we achieve it?

    • August 18, 2021 at 8:38 PM

      Hi Subhashree,

      ExecuteMultiple and ExecuteTransactionRequest are not recommended considering 2 MIN time limit with Plug-ins but you can still use them outside of the platform execution pipeline, such as integration scenarios.

      I am not clear why would you bulk insert 100K records in Plug-in which I am sure would not fit with in the 2 min window. If you have Azure subscription, trigger Azure function as web hook from Plug-in and use TPL with WebAPI (

      Don’t use TPL in plug-ins. Develop your plug-ins knowing that the calls will be performed sequentially and may need to be rolled back.

      • Subhashree
        August 26, 2021 at 9:33 AM

        Thank You. This helped us.

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: