Search

Removing Incoming Email in MS Exchange, C# Example

1 views

Building an Exchange Store Event Sink with C#

When you need to process every incoming message in an Exchange mailbox without touching the UI, the most reliable path is to tap into the store itself. Exchange exposes a low‑level event sink API that lets managed code run inside the COM+ runtime whenever a store operation occurs. The core of that approach is a C# assembly that registers itself as an event sink against the store's IExStore interface. Once registered, your code receives notifications such as OnSyncSave whenever an item is written, and you can decide what to do next.

The reference material that guided the initial build was Logu Krishnan’s post “Developing Managed Event Sinks/Hooks for Exchange Server Store using C#” on CodeProject. That article walks through the COM+ registration steps, the interface implementation, and the wiring of the event sink to the store. Coupled with the Microsoft Exchange SDK samples, it gives a solid foundation. The steps below distill the key parts and add a few practical details that help avoid the most common pitfalls.

1. Install the Exchange SDK and the .NET Framework developer pack that includes the Interop assemblies for Microsoft.Exchange.Data.Storage. These assemblies expose the COM interfaces you need to reference from C#. Make sure the SDK version matches the Exchange version you are targeting, as interface signatures can drift between releases.

2. Create a new Class Library project in Visual Studio and add references to the following COM objects: Microsoft.Exchange.Data.Storage, ADODB (for the legacy database connectivity), and the .NET wrapper for Exoledb if you prefer a managed API. Once the references are in place, set the project’s target framework to .NET 4.8 or later; this ensures compatibility with the latest Windows Server editions.

3. Implement the IExStoreEventSink interface. The most important method is OnSyncSave, which the store calls whenever an item is written to a mailbox. In addition to this, you should provide stub implementations for the other event methods (e.g., OnSyncDelete, OnSyncCreate) to keep the compiler happy. Even if you don’t use them, returning (S_OK) is enough to keep the sink alive.

4. Register the assembly with COM+ so that the runtime knows to load it when a store event fires. You do this by opening the Component Services console, navigating to “COM+ Applications”, and creating a new application. Right‑click the application, choose “Add”, then “New Component”. Browse to the compiled DLL and finish the wizard. The key setting is the “Server‑side component” option, which tells Exchange to load your code on the same machine that hosts the mailbox database.

5. Configure the event sink’s priority and lifetime. In the COM+ console, open the component properties, go to the “Events” tab, and set the sink’s priority to OnSyncSave and OnSyncCommit. This ensures your code runs during the commit phase of the transaction, giving you access to the final state of the message. Set the lifecycle to “Single‑ton” so that only one instance serves all events; this keeps memory usage low.

6. Build the project, copy the DLL to the server, and restart the COM+ application. After a restart, the store will begin routing OnSyncSave events to your implementation. You can verify this by placing a breakpoint in the method or by logging a message to a file. If you see the log entries, the sink is active and ready for the next steps.

Once the sink is up, you can start filtering incoming messages, creating corresponding records in Microsoft CRM, and even deciding whether to keep or delete the message. The rest of this guide walks through the logic that makes that possible.

Implementing OnSyncSave for Targeted Mail Processing

The OnSyncSave event receives three parameters: the event info structure, a string representing the URL of the item being written, and a flag set that describes the operation. For a mail handler that acts on delivered messages, you care about the combination of the EVT_SYNC_COMMITTED and EVT_IS_DELIVERED flags. Those flags indicate that the message has passed the transport pipeline and is being committed to the mailbox store.

Inside the event handler you typically want to wrap the entire body in a try‑catch block to surface any unexpected failures back to the logging framework. A sample implementation follows, with a brief commentary on each step:

Prompt
public void OnSyncSave(IExStoreEventInfo pEventInfo, string bstrURLItem, int IFlags)</p> <p>{</p> <p> try</p> <p> {</p> <p> // Only process messages that have been delivered and are about to be committed.</p> <p> if (IFlags == ((int)EVT_SINK_FLAGS.EVT_SYNC_COMMITTED +</p> <p> (int)EVT_SINK_FLAGS.EVT_IS_DELIVERED))</p> <p> {</p> <p> ProcessMessage(pEventInfo, bstrURLItem, IFlags);</p> <p> }</p> <p> }</p> <p> catch (Exception ex)</p> <p> {</p> <p> log.Debug(ex.Message + " " + ex.StackTrace);</p> <p> }</p> <p> finally</p> <p> {</p> <p> LogManager.Shutdown();</p> <p> }</p> <p>}</p>

In the above code, ProcessMessage is where you perform your business logic: you might parse the message body, extract identifiers, create a lead record in CRM, or flag the message for follow‑up. By filtering on the flag set, you avoid processing drafts or internally generated items that have not yet hit the mailbox.

Because the event runs inside the store’s transaction, you can safely create external records before deleting the message. If anything fails during ProcessMessage, the exception will bubble back up, and the store can roll back the transaction if the sink signals failure. In practice, we prefer to let the event finish and handle errors through logging; the message will still be delivered to the user, and we can re‑run the logic later if needed.

Another nuance is that the event receives a URL that points to the message in the store’s native format. That URL is required for any subsequent operations, such as opening the item with ADODB or calling the Exchange Managed API. Storing the URL in a variable makes the code cleaner and ensures you pass the same reference through all helper methods.

When building for a production environment, be mindful of thread safety. The event sink can be called on multiple threads simultaneously if the server processes many concurrent deliveries. Guard any shared resources with locks or use thread‑safe collections. The sample below demonstrates a simple lock around a shared CRM client object to avoid race conditions when multiple messages are processed at once.

Finally, you should test the handler in a controlled lab environment first. Deploy the assembly to a test Exchange server, send a handful of emails, and monitor the logs. Confirm that ProcessMessage runs exactly once per message and that the database record appears as expected. Once satisfied, you can roll the changes to the production server.

Deleting the Email from the Exchange Store

After the message has been processed and you have the assurance that the data is safely persisted elsewhere, the next step is to remove the email from the mailbox. Exchange exposes a low‑level OleDB provider, exoledb.datasource, which lets you open a stream to the message and delete it directly. The following method shows a typical pattern, including connection handling and error logging.

Prompt
private void DeleteMessage(string bstrURLItem)</p> <p>{</p> <p> try</p> <p> {</p> <p> ADODB.Connection oCn = new ADODB.Connection();</p> <p> oCn.Provider = "exoledb.datasource";</p> <p> oCn.Open(bstrURLItem, "", "", -1);</p> <p> if (oCn.State == 1)</p> <p> {</p> <p> log.Debug("Good Connection");</p> <p> }</p> <p> else</p> <p> {</p> <p> log.Debug("Bad Connection");</p> <p> }</p> <p> ADODB.Record rec = new ADODB.Record();</p> <p> rec.Open(bstrURLItem,</p> <p> oCn,</p> <p> ADODB.ConnectModeEnum.adModeReadWrite,</p> <p> ADODB.RecordCreateOptionsEnum.adFailIfNotExists,</p> <p> ADODB.RecordOpenOptionsEnum.adOpenSource,</p> <p> "", "");</p> <p> rec.DeleteRecord(bstrURLItem, false);</p> <p> rec.Close();</p> <p> oCn.Close();</p> <p> rec = null;</p> <p> oCn = null;</p> <p> }</p> <p> {</p> <p> }</p> <p>}</p>

Several points deserve emphasis:

In your code, initialize log4net at startup:

Prompt
log4net.Config.XmlConfigurator.Configure();</p> <p>private static readonly log4net.ILog log = log4net.LogManager.GetLogger(typeof(YourEventSinkClass));</p>

Because the COM+ component runs under a service account, you must grant that account the right to write to the log file location. Add the account to the folder’s ACL with write permissions, or run the component under a privileged user if the logs are stored in a system folder.

Logging is only half the story; the second half is permission configuration for message deletion. Exchange uses mailbox rights to control who can modify items in a mailbox. In a Windows Server 2003 environment, you set these rights in the Active Directory Users and Computers console. Locate the computer or service account that hosts the COM+ application, open its properties, and navigate to the “Exchange Advanced” tab. From there, assign the Delete right on the relevant mailboxes. If you’re on a newer server, the rights are controlled via the Exchange Management Shell:

Prompt
Add-MailboxPermission -Identity "UserMailbox" -User "COM+AppAccount" -AccessRights DeleteItem</p>

When the account has the correct rights, the DeleteRecord call in DeleteMessage succeeds. If you encounter errors such as “Access is denied” or “Object is read‑only,” double‑check the ACL and the mailbox’s retention policy. In some cases, a mailbox’s transport rule or compliance settings may override the deletion request, so ensure the account also bypasses such rules.

Finally, a practical tip: before deleting a message in production, run the handler in “dry‑run” mode. Comment out the DeleteRecord line and log the URL instead. Once you confirm the message is processed correctly, enable the deletion. This step prevents accidental data loss during early deployments.

By combining a well‑structured event sink, robust logging, and correct permission settings, you can reliably remove incoming emails from Exchange while keeping the data in a downstream system. The sample code and configuration snippets above form a ready‑to‑copy template that can be adapted to any Exchange environment that needs mail handling automation.

Suggest a Correction

Found an error or have a suggestion? Let us know and we'll review it.

Share this article

Comments (0)

Please sign in to leave a comment.

No comments yet. Be the first to comment!

Related Articles