Fine-Tuning Sitecore EXM for Better Performance and Reliability

Hello Everyone!

If you have ever worked with Sitecore Email Experience Manager, you know that sending emails is only half of the story, sometimes, initially you will face some scalability issues, That Email campaign starts very small, as a test batch like 5.000 emails, but eventually starts to grow, and on this growing process, issues with your xDB start to happen, issues with EXM (Sitecore Email Experience Manager) also start to show up

So, first, we can think about our infrastructure for that, so let’s first check a few things

  1. Do we have a separate server for sending emails? (the EXM Email Dispatcher Server)?
  2. Do you have a properly scaled Sitecore xDB?

Anyway, if you know those things, it might help, if you don’t know, what we can do is perform a “warm-up” strategy.

Warm-up strategy

This strategy is basically a plan to start slow and gradually add load. That means sending more emails over time and observing how the server handles the increased stress.

Usually, I go like this, but you can adjust the pace to slower or faster if you prefer, depending on how your team handles failed emails.

Week 1Daily volume
Day 14000 emails sentIt handled well
Day 28000 emails sentSo far , so good
Day 312000 emails sentHere we noticed a problem

So, in this case, we already started seeing some issues, so, when sending 12k emails per day , the logs started showing up some issues on EXM, xDB breaking sometimes. In terms of infrastructure, we had only one CM Server, and we still don’t have our own dedicated EXM Mail Dispatcher Server.

Below the first xDB issue,

And we can see many of these, which means there is a retry attempt being made, and after a few, it fails. This shows that our xDB is overloaded and cannot handle the calls

Exception: Sitecore.XConnect.XdbCollectionUnavailableException
Message: An error occurred while sending the request.
Source: Sitecore.Xdb.Common.Web
at Sitecore.Xdb.Common.Web.Synchronous.SynchronousExtensions.SuspendContextLock[TResult](Func`1 taskFactory)
at Sitecore.XConnect.Client.XConnectSynchronousExtensions.SuspendContextLock[TResult](Func`1 taskFactory)
at Sitecore.Modules.EmailCampaign.Core.Contacts.ContactService.<>c__DisplayClass10_0.<GetContactWithRetry>b__0(IXdbContext client)
at Sitecore.EmailCampaign.XConnect.Web.XConnectRetry.RequestWithRetry(Action`1 action, Func`3 isTransient, Double delay, Int32 retryCount)

Nested Exception

Another error is in “SavingInteractions” ,

8652 17:24:20 DEBUG [SaveInteractions] Submitting 50 interactions
8652 17:24:20 DEBUG [SaveInteractions] Number of: 'Pending,Ready,Created,Succeeded' operations: 50.
8652 17:24:20 WARN [SaveInteractions] Transient error. Retrying. (Message: An error occurred while sending the request.)
Exception: Sitecore.XConnect.XdbCollectionUnavailableException
Message: An error occurred while sending the request.
Source: Sitecore.Xdb.Common.Web
at Sitecore.Xdb.Common.Web.Synchronous.SynchronousExtensions.SuspendContextLock[TResult](Func`1 taskFactory)
at Sitecore.XConnect.Client.XConnectSynchronousExtensions.SuspendContextLock(Func`1 taskFactory)
at Sitecore.EmailCampaign.Cm.Pipelines.HandleSentMessage.SaveInteractions.Process(HandleSentMessagePipelineArgs args)

Both errors will confuse your xDB and cause issues with the numbers, and that is why you need to do some diligence on your xDB, and for that, I would recommend you to go to this article https://errorcotidianam.wordpress.com/2025/07/02/sitecore-10-4-xdb-performance-tips-and-tricks/

This will bring a lot of tips on how to perform xDB maintenance, it’s a very important step that will already reduce the number of issues

However, there is also another way, which is reducing the load created from EXM Itself

Taking some control

Before diving into the details, it’s important to note that these customizations are not applied everywhere.

In this case, we need to apply the customizations on the correct servers, and they are made only where the EXM is active, so, for a local developer, it will probably be on the “Standalone” instance, for a scaled environment, those changes can be applied on

  • Content Management (CM Server)
  • Dedicated Dispatch Server

One of the main goals of this configuration is to make EXM less fragile when something goes wrong.
Network hiccups, temporary database latency, or processing spikes can occasionally cause event handling to fail. Instead of losing data, EXM is configured here to slow down and try again.

Handling Email Open Events

When a recipient opens an email, EXM records that action as an interaction. This is critical data for analytics, personalization, and reporting.

In this setup:

  • EXM waits 5 seconds before retrying a failed interaction save
  • It retries the operation up to 6 times

This small delay-and-retry approach significantly increases the chance that interaction data is eventually stored, even if the system is under temporary load. The result is more accurate engagement tracking and fewer “missing opens” in reports.

Improving Sent Message Interaction Tracking

similar strategy is applied when EXM processes sent messages.

When an email is successfully sent, EXM creates interaction records that tie the message to contacts and campaigns. These records are just as important as open or click events, especially for attribution and analytics.

Here, EXM:

  • Introduces a 5-second delay between retry attempts
  • Allows up to 6 retries if saving interactions fails

This adds an extra layer of safety, ensuring that sent message data isn’t lost due to transient issues during high-volume dispatches.

Thread Usage and Dispatch

Beyond pipeline reliability, this configuration also puts clear boundaries around how much work EXM does in parallel.
This is particularly useful in environments where EXM shares resources with other Sitecore workloads.

The following limits are applied:

  • NumberThreads: 2
    Controls the number of threads EXM uses for internal processing.
  • DispatchEnqueueThreadsNumber: 1
    Ensures that message enqueueing happens in a controlled, sequential manner.
  • DispatchEnqueueBatchSize: 100
    Defines how many messages are added to the dispatch queue at once.
  • EXM.DispatchBatchSize: 50
    Limits how many emails are processed in a single dispatch batch.

After many attempts, and seeing so many errors in logs, and slowly changing the configurations, to achieve the desired place(being able to send and overcome the limit of 20k emails per day), those were the configurations applied.

The idea behind those changes is to instead of overwhelming the system with a large parallel workloads, the EXM will process the messages in smaller batches. The idea here is to reduce a few pinpoints and have control over the following

  • Database Pressure: Reducing the number of calls to xDB
  • Thread contention: Here we try to hold and minimize the number of simultaneous threads executing at the same time. Of course, there is a trade-off, it will be slower
  • Avoid timeouts

Here, we see a classic trade-off: we are reducing the speed of sending emails, for the stability of the data captured by xDB

So, without further delays, below you can grab the configurations, create this file as Sitecore Patch.(xml) file ex: exm-customizations.xml, and apply it on your server

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:x="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/" xmlns:exmEnabled="http://www.sitecore.net/xmlconfig/exmEnabled/">
<sitecore exmEnabled:require="yes" role:require="Standalone or ContentManagement or DedicatedDispatch">
<pipelines>
<group groupName="exm.messageEvents">
<pipelines>
<emailOpened>
<processor type="Sitecore.EmailCampaign.Cm.Pipelines.EmailOpened.SaveInteraction, Sitecore.EmailCampaign.Cm" resolve="true">
<Delay>5000</Delay>
<RetryCount>6</RetryCount>
</processor>
</emailOpened>
</pipelines>
</group>
<handleSentMessage>
<processor type="Sitecore.EmailCampaign.Cm.Pipelines.HandleSentMessage.SaveInteractions, Sitecore.EmailCampaign.Cm" resolve="true" >
<Delay>5000</Delay>
<RetryCount>6</RetryCount>
</processor>
</handleSentMessage>
</pipelines>
<settings>
<setting name="NumberThreads" value="2" />
<setting name="DispatchEnqueueThreadsNumber" value="1" />
<setting name="DispatchEnqueueBatchSize" value="100" />
<setting name="EXM.DispatchBatchSize" value="50" />
</settings>
</sitecore>
</configuration>

Final thoughts

These EXM customizations don’t introduce new features , and that’s exactly the point. They focus on making what already exists more reliable, more predictable, and easier to operate at scale.

If you’re running frequent campaigns, handling large recipient lists, or simply want more confidence in your engagement data, adjustments like these can make a noticeable difference.

Happy emailing, and may your opens always be tracked.

References:

https://doc.sitecore.com/xp/en/developers/exm/latest/email-experience-manager/implementing-a-warm-up-strategy-for-exm-delivery-cloud-accounts.html