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

在C#中使用Microsoft Graph API移除草稿邮件附件的问题排查

使用Microsoft Graph API转发邮件时移除草稿附件的问题及解决方案

问题背景

在C#应用中使用Microsoft Graph API转发邮件时,尝试移除转发生成的草稿邮件附件,遭遇tooManyRetries错误,删除操作长时间挂起后抛出异常。

原代码示例

private static async Task<Message> GetDraftMessageAsync(GraphServiceClient client, EmailCreateServiceModel model,
                                                       ComposeMode mode, CancellationToken cancellationToken)
{
    // 其他模式处理逻辑

    var message = await client.Users[model.OriginalEmailAddress]
        .Messages[model.MessageId]
        .CreateForward()
        .Request()
        .Header("Prefer", "IdType=\"ImmutableId\"")
        .PostAsync(cancellationToken)
        .ConfigureAwait(false);

    // 疑问1:能否无需二次调用直接获取带附件的草稿邮件?
    var messageWithAttachments = await client.Users[model.OriginalEmailAddress]
        .Messages[message.Id]
        .Request()
        .Expand(m => m.Attachments)
        .GetAsync(cancellationToken)
        .ConfigureAwait(false);

    return messageWithAttachments;
}

public static async Task<Message> SendEmailAsync(EmailCreateServiceModel model, ComposeMode mode, List<string> keepingAttachments,
                                                  CancellationToken cancellationToken)
{
    var client = await AuthService.GetGraphClientAsync();
    var draftMessage = await GetDraftMessageAsync(client, model, mode, cancellationToken)
                .ConfigureAwait(false);

    if (mode is ComposeMode.Forward && draftMessage.Attachments != null)
    {
        var attachmentsToRemove = draftMessage.Attachments
            .Where(attachment => !keepingAttachments.Contains(attachment.Name))
            .ToList();

        foreach (var attachment in attachmentsToRemove)
        {
            // 疑问2:此代码长时间挂起后抛出异常
            await client.Users[model.OriginalEmailAddress]
                .Messages[draftMessage.Id]
                .Attachments[attachment.Id]
                .Request()
                .DeleteAsync(cancellationToken)
                .ConfigureAwait(false);
        }
    }

    draftMessage.From = string.IsNullOrEmpty(model.From)
        ? null
        : new() { EmailAddress = new() { Address = model.From } };
    draftMessage.ToRecipients = GetRecipients(model.To);
    draftMessage.CcRecipients = GetRecipients(model.Cc);
    draftMessage.BccRecipients = GetRecipients(model.Bcc);
    draftMessage.Subject = model.Subject;
    draftMessage.Body = new ItemBody() { Content = model.Body, ContentType = BodyType.Html, };

    draftMessage = await client.Users[model.OriginalEmailAddress]
            .Messages[draftMessage.Id]
            .Request()
            .UpdateAsync(draftMessage, cancellationToken)
            .ConfigureAwait(false);
            
    return draftMessage;
}

错误详情

Microsoft.Graph.ServiceException
  HResult=0x80131500
  Message=Code: tooManyRetries
  Message: More than 3 retries encountered while sending the request.

  Source=Microsoft.Graph.Core
  StackTrace:
   at Microsoft.Graph.HttpProvider.<SendRequestAsync>d__19.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   ...
   at Microsoft.Graph.AttachmentRequest.<DeleteAsync>d__3.MoveNext() in AttachmentRequest.cs
   ...

核心疑问

  1. 删除草稿邮件附件是否需要特定权限?
  2. 获取和修改草稿邮件的方式是否存在问题?
  3. 如何正确处理tooManyRetries错误?

更新信息

使用Graph Explorer执行删除草稿附件操作时,返回以下错误:

Error code: RequestBroker-ParseUri
Message: Could not parse the URL.
Details: Unrecognized segment 'attachments'

解决方案

1. 无需二次调用获取带附件的草稿邮件

可以直接在CreateForward请求中添加$expand参数,一次性获取包含附件的草稿邮件,避免二次请求。修改后的代码片段:

var message = await client.Users[model.OriginalEmailAddress]
    .Messages[model.MessageId]
    .CreateForward()
    .Request()
    .Header("Prefer", "IdType=\"ImmutableId\"")
    .Expand(m => m.Attachments) // 添加此行,直接返回附件数据
    .PostAsync(cancellationToken)
    .ConfigureAwait(false);

2. 删除草稿附件的权限要求

删除草稿邮件附件需要Mail.ReadWrite权限,需确保应用已正确申请该权限(委托权限或应用权限均可,根据业务场景选择)。

3. 修正草稿邮件的获取与修改方式

  • 避免替换整个Message对象:原代码中直接更新完整的draftMessage对象,可能覆盖Graph API自动维护的系统字段(如LastModifiedDateTime),建议采用PATCH部分字段的方式更新,仅修改需要变更的属性。
  • 避免循环逐个删除附件:循环单次删除会增加请求次数,容易触发Graph API的速率限制,导致重试失败。推荐使用批量请求合并多个删除操作,减少请求频次。

4. 处理tooManyRetries错误

  • 使用批量请求删除附件:将多个删除附件的请求合并为一个批量请求发送,示例代码如下:
if (attachmentsToRemove.Any())
{
    var batchRequestContent = new BatchRequestContent();
    foreach (var attachment in attachmentsToRemove)
    {
        var deleteRequest = client.Users[model.OriginalEmailAddress]
            .Messages[draftMessage.Id]
            .Attachments[attachment.Id]
            .Request()
            .GetHttpRequestMessage(HttpMethod.Delete);
        
        batchRequestContent.AddBatchRequestStep(deleteRequest);
    }

    var batchResponse = await client.Batch.Request().PostAsync(batchRequestContent, cancellationToken)
        .ConfigureAwait(false);

    // 检查批量请求结果,处理失败的删除操作
    foreach (var stepResponse in batchResponse.BatchResponses)
    {
        if (!stepResponse.Response.IsSuccessStatusCode)
        {
            var errorContent = await stepResponse.Response.Content.ReadAsStringAsync(cancellationToken);
            // 记录日志或执行补偿逻辑
        }
    }

    // 删除后重新获取草稿最新状态,确保后续更新基于最新数据
    draftMessage = await client.Users[model.OriginalEmailAddress]
        .Messages[draftMessage.Id]
        .Request()
        .Expand(m => m.Attachments)
        .GetAsync(cancellationToken)
        .ConfigureAwait(false);
}
  • 自定义重试策略:初始化GraphServiceClient时,可自定义重试规则,针对速率限制(429)、服务器错误(5xx)调整重试次数与间隔:
var retryHandler = new RetryHandler(new RetryHandlerOptions
{
    MaxRetry = 5, // 增加重试次数
    RetryDelay = TimeSpan.FromSeconds(2), // 调整重试间隔
    ShouldRetry = (delay, attempt, message) =>
    {
        // 针对429和5xx错误进行重试
        return message.StatusCode == HttpStatusCode.TooManyRequests ||
               (int)message.StatusCode >= 500;
    }
});

var httpClient = HttpClientFactory.Create(retryHandler);
var client = new GraphServiceClient(httpClient, authProvider);

5. 修正Graph Explorer的URL错误

确保删除附件的请求路径格式正确:/users/{user-id}/messages/{draft-message-id}/attachments/{attachment-id},其中draft-message-id是转发生成的草稿邮件ID,而非原邮件ID。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 16:47:36