Exchange Web Services发送邮件报错:item is out of date问题排查
Great question—this is a super common gotcha with EWS when dealing with message items that have concurrent state changes, even if you think you're reloading them properly. Let's break down why this happens and how to fix it.
为什么会出现这个错误?
This error stems from optimistic concurrency control in Exchange: every item has an ETag that acts as a version identifier. When you perform an operation on an item, Exchange checks if your local ETag matches the server's current ETag. If they don't, it throws this error because the item has been modified elsewhere since you last loaded it. Here are the most likely culprits:
- Concurrent modifications: The message (e.g., a draft you're sending) might be altered by another process before your code runs—think Outlook syncing in the background, an Exchange rule triggering, or another thread in your app modifying the same item. Even if you call
Bind(), if the modification happens between your Bind and send operation, you'll hit the error. - Timing issues with Bind(): If you call
Bind()immediately after saving the item to Sent Items, Exchange might not have finished updating the item's state on the server yet. Your Bind call could still retrieve the old version with a stale ETag. - Incomplete PropertySet in Bind(): If you're using a limited PropertySet when binding, you might not be fetching the latest ETag. Without the correct ETag, Exchange can't verify the item's current state.
- Cross-operation state contamination: Since you're also reading incoming emails and moving items, if you're reusing the same
ExchangeServiceinstance for both send and move operations, you might be inadvertently sharing cached state that causes conflicts.
解决方法:规避或恢复策略
1. 优化Bind()调用以获取最新状态
Make sure you're binding with a full PropertySet that includes the ETag, and avoid relying on cached data. Use this pattern:
// 强制获取最新的完整属性和ETag var freshMessage = EmailMessage.Bind( exchangeService, message.Id, new PropertySet(BasePropertySet.FirstClassProperties, ItemSchema.Etag) );
This ensures you're pulling the absolute latest version of the item from the server, not a cached copy.
2. 实现带指数退避的重试机制
Since this error is often transient (caused by temporary server sync delays), adding a retry loop can resolve most cases. Catch the specific error code and retry after a short wait:
int retryAttempts = 0; const int maxRetries = 3; bool operationSucceeded = false; while (!operationSucceeded && retryAttempts < maxRetries) { try { var freshMessage = EmailMessage.Bind(exchangeService, message.Id, new PropertySet(BasePropertySet.FirstClassProperties)); freshMessage.SendAndSaveCopy(WellKnownFolderName.SentItems); operationSucceeded = true; } catch (ServiceResponseException ex) { // 检查是否是目标错误 if (ex.Response.ErrorCode == ServiceError.ErrorItemIsOutOfDate) { retryAttempts++; // 指数退避:1s, 2s, 4s System.Threading.Thread.Sleep((int)Math.Pow(2, retryAttempts) * 1000); } else { // 其他错误直接抛出,不要重试 throw; } } }
3. 分离操作上下文
If you're handling both send operations and email moving, use separate ExchangeService instances for each task. This prevents cached state from one operation interfering with the other. Alternatively, add a short delay between operations to give Exchange time to sync state.
4. 创建全新的发送消息(绕过原有项目状态)
Instead of modifying and sending an existing draft/item, create a brand new EmailMessage using the content from the original item. This eliminates any chance of conflicting with existing state on the server:
// 基于原消息内容创建新邮件 var newOutgoingMessage = new EmailMessage(exchangeService); newOutgoingMessage.Subject = originalMessage.Subject; newOutgoingMessage.Body = originalMessage.Body; newOutgoingMessage.ToRecipients.AddRange(originalMessage.ToRecipients); // 复制其他必要属性(抄送、附件等) newOutgoingMessage.Attachments.AddRange(originalMessage.Attachments); // 发送新邮件 newOutgoingMessage.SendAndSaveCopy(WellKnownFolderName.SentItems);
5. 强制覆盖冲突(最后手段)
If you're sure overwriting the server's latest version is safe, you can bypass concurrency checks by specifying ConflictResolutionMode.AlwaysOverwrite:
freshMessage.SendAndSaveCopy(WellKnownFolderName.SentItems, ConflictResolutionMode.AlwaysOverwrite);
⚠️ Warning: This will overwrite any changes made to the item by other processes, so only use this if you're confident it won't cause data loss.
内容的提问来源于stack exchange,提问作者Mike Shepard

