Wouldn’t it be great to implement your own custom logic to distribute mails to targeted addresses (for example, based on sender or subject of the mail) using SharePoint lists and event receivers? In this post I show you the fundamental technical issues and their solution to achieve that goal. My “custom logic” is quite simple, I send the same mail back to the sender, however you can build more sophisticated logic using the same technique, but more on that later.
Before starting Visual Studio, I’ve created a simple custom list called MailDistributor on my SharePoint site.
In Visual Studio I chose the Empty SharePoint Project template, and added a new List Email Event event receiver item.
Having these artifacts, I altered the default Elements.xml, to register the event receiver to the list I created earlier:
- <?xml version="1.0" encoding="utf-8"?>
- <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
- <Receivers ListUrl="Lists/MailDistributor">
- <Receiver>
- <Name>IncomingMailHandlerEmailReceived</Name>
- <Type>EmailReceived</Type>
- <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>
- <Class>MailDistributor.IncomingMailHandler</Class>
- <SequenceNumber>10000</SequenceNumber>
- </Receiver>
- </Receivers>
- </Elements>
Regarding the code, the first step was to create the extension method GetMailMessage for the SPEmailMessage type to convert it to a MailMessage object (System.Net.Mail namespace). Fortunately, both of these object types have the same stream format in the background. This stream is directly accessible from the SPEmailMessage, however, MailMessage is not creatable from the stream (or ByteArray / String) format. To solve this issue, I utilized the RxMailMessage type (copyright by Peter Huber, Singapore), that is a derived class of MailMessage with Stream and File support.
- public static MailMessage GetMailMessage(this SPEmailMessage spEmailMessage)
- {
- MailMessage result = null;
- if (spEmailMessage != null)
- {
- result = RxMailMessage.CreateFromStream(spEmailMessage.GetMessageStream());
- }
- return result;
- }
Below is the structure of the solution, highlighted the classes borrowed from Peter Huber.
The next challenge was to set the addressee (To property) of the MailMessage instance. Since there is no way to change this read-only property using the public methods of the type (in practice, you should set it already in the constructor), I had to apply my experience with Reflection, and set the private field to of the private field message of the MailMessage instance. Although I set only the To property in the example using the SetTo extension method, you could (and should!) set the Cc and Bcc fields as well. For example, clear these values to avoid perpetual sending / receiving the same message in the case the one of the Cc / Bcc fields contain the incoming mail address of the SharePoint list. To do that, you should implement the SetCc and SetBcc methods and from these methods call the SetMailAddressCollection method with the parameters “cc” and “bcc” accordingly.
- public static void SetTo(this MailMessage mailMessage, MailAddressCollection mailAddressCollection)
- {
- if ((mailMessage != null) && (mailAddressCollection != null))
- {
- SetMailAddressCollection(mailMessage, mailAddressCollection, "to");
- }
- }
- private static void SetMailAddressCollection(MailMessage mailMessage, MailAddressCollection mailAddressCollection, string fieldName)
- {
- if ((mailMessage != null) && (mailAddressCollection != null) && (fieldName != null))
- {
- Type typeMailMessage = typeof(MailMessage);
- FieldInfo fi = typeMailMessage.GetField("message", BindingFlags.NonPublic | BindingFlags.Instance);
- if (fi != null)
- {
- object message = fi.GetValue(mailMessage);
- if (message != null)
- {
- Type typeMessage = message.GetType(); // it is internal class System.Net.Mail.Message
- FieldInfo fi2 = typeMessage.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
- if (fi2 != null)
- {
- fi2.SetValue(message, mailAddressCollection);
- }
- }
- }
- }
- }
In the event receiver, we convert the SPEmailMessage into a MailMessage instance, set its To property to the e-mail address of the original poster (From property of the MailMessage) and send the mail using an SmtpClient object. The Host property of the SmtpClient instance can be set using the Address of the configured SMTP server of the OutboundMailServiceInstance in the current web application.
As you can see, most of the code below is just tracing to help us to follow the process using DebugView. You are free to remove these lines without affecting the functionality, of course.
- using System;
- using System.Diagnostics;
- using System.Net.Mail;
- using Microsoft.SharePoint;
- using Microsoft.SharePoint.Utilities;
- namespace MailDistributor
- {
- public class IncomingMailHandler : SPEmailEventReceiver
- {
- public override void EmailReceived(SPList list, SPEmailMessage emailMessage, string receiverData)
- {
- try
- {
- Trace.TraceInformation("IncomingMailHandler starting…");
- foreach (SPEmailHeader header in emailMessage.Headers)
- {
- Trace.TraceInformation("EmailReceived emailMessage header {0}, {1}", header.Name, header.Value);
- }
- SmtpClient smtpClient = new SmtpClient();
- smtpClient.Host = list.ParentWeb.Site.WebApplication.OutboundMailServiceInstance.Server.Address;
- Trace.TraceInformation("IncomingMailHandler: getting mail message from stream");
- MailMessage mailMessage = emailMessage.GetMailMessage();
- Trace.TraceInformation("IncomingMailHandler: setting mail message To field");
- mailMessage.SetTo(new MailAddressCollection { mailMessage.From });
- Trace.TraceInformation("IncomingMailHandler: sending mail");
- smtpClient.Send(mailMessage);
- Trace.TraceInformation("IncomingMailHandler: finished");
- }
- catch (Exception ex)
- {
- Trace.TraceInformation("IncomingMailHandler exception: {0}", ex.Message);
- }
- }
- }
- }
After deploying the solution and activating the feature, we should enable the incoming mail for the MailDistributor list, set the mail address alias, and send a test message to this address. If there is no error, within about a minute we should receive the same mail, including formatting and attachments to the mailbox of the sender.
Using the Category settings of the mail to route the message
The rule we implemented is really a trivial one and has not much sense, but one can implement more complicated and more useful routing rules as well. My plan is to build a routing “engine” based on the Category settings of the mail.
As part of the Options / Tracking properties in Outlook, we can set not only Blue or Green categories, but our own custom categories (like SharePoint and Silverlight below) as well. As long as these categories are transferred within the mail, we can process them in our event receiver, look up SharePoint user profiles having the same values set in the Ask me about property, and route the message exactly to those users, implementing thus a simple but efficient knowledge management solution.
It would be even better if the user could choose those category values from a SharePoint Managed Metadata keyword list (a candidate for an Office 2013 mail-app?).
However there are some issues with the Category property that you should be aware of.
Based on this information, Outlook removes category settings from outgoing mails due to privacy concerns. One can alter this settings via registry (HKEY_CURRENT_USER\Software\Policies\Microsoft\Office\xx.0\Outlook\Preferences\SendPersonalCategories, where xx is the version number of Outlook, like 14 for Outlook 2010).
Exchange 2010 also removes the categories from the outgoing messages by default, as I’ve learned here. This behavior can be changes using the following PowerShell command:
Set-TransportConfig -ClearCategories:$False
A workaround for these issues might be an Outlook add-in, or a simply VBA code like this one, that illustrates, how to copy the mail categories to a custom mail header called X-Categories when sending the mail, thus avoiding losing of the categories:
Private Sub Application_ItemSend(ByVal item As Object, Cancel As Boolean)
Dim mi As MailItem
Set mi = item
If Not mi Is Nothing Then
item.PropertyAccessor.SetProperty "http://schemas.microsoft.com/mapi/string/{00020386-0000-0000-C000-000000000046}/X-Categories", item.Categories
End If
End Sub
Of course, we should filter the mails affected by this behavior based on the To e-mail address, limiting it to the mails sent to our MailDistributor list.
