You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Dynamics CRM C#插件:联系人增改时给SystemCustomizer角色用户批量发邮件

Dynamics CRM C#插件:向SystemCustomizer角色用户发送通知邮件的实现方案

先理清核心思路

我之前做过类似的需求,核心其实就是两步:精准获取SystemCustomizer角色下的有效用户,然后选择合适的方式给这些用户发送通知邮件。至于用循环逐个发还是批量请求,各有优劣,我给你拆解清楚:

两种发送方式的对比

  • 循环逐个发送:代码逻辑简单直观,上手快,适合用户数量少的场景(比如10个以内),但用户多的话会产生多次API调用,性能稍差,而且单个发送失败可能需要单独处理。
  • 批量请求发送:用ExecuteMultipleRequest打包所有邮件请求,一次性执行,API调用次数大幅减少,效率更高,是官方推荐的批量操作方式,适合用户数量较多的场景,还能设置失败不中断其他请求。

具体实现步骤与代码示例

第一步:获取SystemCustomizer角色下的有效用户

首先要查询Dynamics CRM中的角色和关联用户,这里用QueryExpression构建查询,还会自动过滤掉禁用、没有邮箱的用户:

private List<Entity> GetSystemCustomizerUsers(IOrganizationService service)
{
    // 先找到System Customizer角色
    var roleQuery = new QueryExpression("role")
    {
        ColumnSet = new ColumnSet("roleid"),
        Criteria = new FilterExpression
        {
            Conditions = { new ConditionExpression("name", ConditionOperator.Equal, "System Customizer") }
        }
    };

    var roleResult = service.RetrieveMultiple(roleQuery);
    if (roleResult.Entities.Count == 0)
        return new List<Entity>();

    var roleId = roleResult.Entities[0].Id;

    // 查询该角色下的有效用户(排除禁用+无邮箱的)
    var userQuery = new QueryExpression("systemuser")
    {
        ColumnSet = new ColumnSet("fullname", "internalemailaddress"),
        Criteria = new FilterExpression
        {
            Conditions = 
            { 
                new ConditionExpression("isdisabled", ConditionOperator.Equal, false),
                new ConditionExpression("internalemailaddress", ConditionOperator.NotNull)
            }
        },
        LinkEntities =
        {
            new LinkEntity
            {
                LinkFromEntityName = "systemuser",
                LinkFromAttributeName = "systemuserid",
                LinkToEntityName = "systemuserroles",
                LinkToAttributeName = "systemuserid",
                LinkCriteria = new FilterExpression
                {
                    Conditions = { new ConditionExpression("roleid", ConditionOperator.Equal, roleId) }
                }
            }
        }
    };

    var userResult = service.RetrieveMultiple(userQuery);
    return userResult.Entities.ToList();
}

第二步:准备邮件内容与基础信息

从插件上下文获取当前创建/更新的联系人信息,构造邮件的主题和正文:

// 从插件上下文的PostImage获取最新联系人信息(注册插件时要配置PostImage)
var contact = context.PostEntityImages["ContactPostImage"] as Entity;
var contactName = contact.GetAttributeValue<string>("fullname");
var contactEmail = contact.GetAttributeValue<string>("emailaddress1");
var contactId = contact.Id.ToString();

// 邮件主题和正文(可以根据需求调整格式)
var subject = $"联系人更新通知:{contactName}已创建/修改";
var body = $@"<p>您好,</p>
<p>联系人 <strong>{contactName}</strong> 已完成创建或更新操作,相关信息如下:</p>
<ul>
<li>联系人姓名:{contactName}</li>
<li>联系人邮箱:{contactEmail}</li>
<li>联系人ID:{contactId}</li>
</ul>
<p>请留意相关业务跟进。</p>";

第三步:实现邮件发送逻辑

方式一:循环逐个发送邮件(适合小批量用户)

这种方式代码简单,容易调试,单个用户发送失败不会影响其他用户:

var targetUsers = GetSystemCustomizerUsers(service);
foreach (var user in targetUsers)
{
    try
    {
        // 构造邮件实体
        var email = new Entity("email")
        {
            ["subject"] = subject,
            ["description"] = body,
            // 发件人设为当前插件运行用户
            ["from"] = new EntityCollection(new List<Entity> 
            { 
                new Entity("activityparty") { ["partyid"] = new EntityReference("systemuser", service.GetCurrentUserId()) }
            }),
            // 收件人设为当前循环的用户
            ["to"] = new EntityCollection(new List<Entity> 
            { 
                new Entity("activityparty") { ["partyid"] = new EntityReference("systemuser", user.Id) }
            })
        };

        // 先保存邮件到系统
        var emailId = service.Create(email);

        // 执行发送请求
        var sendRequest = new SendEmailRequest
        {
            EmailId = emailId,
            TrackingToken = string.Empty,
            IssueSend = true
        };

        service.Execute(sendRequest);
    }
    catch (Exception ex)
    {
        // 记录单个用户发送失败的日志,不中断循环
        Trace.TraceError($"给用户【{user.GetAttributeValue<string>("fullname")}】发送邮件失败:{ex.Message}");
    }
}

方式二:批量发送邮件(推荐,适合大批量用户)

用ExecuteMultipleRequest打包所有请求,减少API调用次数,提升效率:

var targetUsers = GetSystemCustomizerUsers(service);
if (!targetUsers.Any()) return;

// 第一步:批量创建邮件
var createEmailRequests = new ExecuteMultipleRequest
{
    Settings = new ExecuteMultipleSettings
    {
        ContinueOnError = true, // 单个创建失败不影响其他
        ReturnResponses = true // 需要返回结果获取邮件ID
    },
    Requests = new OrganizationRequestCollection()
};

foreach (var user in targetUsers)
{
    var email = new Entity("email")
    {
        ["subject"] = subject,
        ["description"] = body,
        ["from"] = new EntityCollection(new List<Entity> 
        { 
            new Entity("activityparty") { ["partyid"] = new EntityReference("systemuser", service.GetCurrentUserId()) }
        }),
        ["to"] = new EntityCollection(new List<Entity> 
        { 
            new Entity("activityparty") { ["partyid"] = new EntityReference("systemuser", user.Id) }
        })
    };

    createEmailRequests.Requests.Add(new CreateRequest { Target = email });
}

// 执行批量创建
var createResponses = (ExecuteMultipleResponse)service.Execute(createEmailRequests);

// 第二步:批量发送成功创建的邮件
var sendEmailRequests = new ExecuteMultipleRequest
{
    Settings = new ExecuteMultipleSettings { ContinueOnError = true, ReturnResponses = false },
    Requests = new OrganizationRequestCollection()
};

for (int i = 0; i < createResponses.Responses.Count; i++)
{
    if (createResponses.Responses[i].Fault == null)
    {
        var emailId = ((CreateResponse)createResponses.Responses[i].Response).id;
        sendEmailRequests.Requests.Add(new SendEmailRequest
        {
            EmailId = emailId,
            IssueSend = true
        });
    }
    else
    {
        var userName = targetUsers[i].GetAttributeValue<string>("fullname");
        Trace.TraceError($"给用户【{userName}】创建邮件失败:{createResponses.Responses[i].Fault.Message}");
    }
}

// 执行批量发送
if (sendEmailRequests.Requests.Count > 0)
{
    service.Execute(sendEmailRequests);
}

插件注册关键注意事项

  1. 事件注册:选择Contact实体的Create和Update消息,建议设置为异步执行,避免阻塞联系人的创建/更新操作,提升用户体验。
  2. PostImage配置:必须配置PostImage,包含fullname、emailaddress1等你需要用到的联系人字段,这样插件才能获取到最新的联系人数据。
  3. 权限配置:插件运行的服务账号需要拥有读取角色/用户、创建邮件、发送邮件的权限,建议用专门的系统服务账号来注册插件,避免权限问题。

内容的提问来源于stack exchange,提问作者NaveenGNK

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 08:56:43