Last year I published a post describing how to fake mail sending of your applications in test system, without physically delivering mails via SMTP to addressees. In that post I utilized a solution via the smtp deliveryMethod to store the outgoing mails into a specific pickupDirectoryLocation instead of sending them to an SMTP server.
That solution is useful in simple situation, but have 2 major and 2 minor issues you face to, especially in more advanced topologies:
- The users of your system might have no access to the file system of the server. Even if you shared the folder and gave read permission to the users, there may be firewalls or other network components that do not allow accessing the share, just because the ports necessary for file and printer sharing are not open (see SMB, ports 445 and 139). It’s typical in the case of an intranet (like a public facing WCM site) or extranet solution, but may happen is your test and production systems are separated into various physical networks. Even if you could technically open the ports, the security policy of the organization should not allow this configuration change.
- If you have multiple applications in your SharePoint web application, and a few of these ones should be able to send out mails while others should not, or you have various processes running in the context of the SharePoint Timer Service (owstimer.exe), like workflows or event receivers conflicting in this requirement, you can not solve the conflict via a simple setting in the configuration.
- If you click on the .eml file the first time, the value of the Date modified field is changed to the current time (I explain the reason of this behavior in another post later), so it is more convenient to use the Date created field, and order the files based on the value of this field (newest files on the top).
- Even if your users have access to the mails in the file system, it’s not very helpful to see a list of .eml files with GUIDs as file name to choose which file to open (see sample screenshot below).
You can check the Subject of the mail by opening the .eml file using Outlook or even a simple text editor, or by checking the file properties (Details tab). Neither of these options is very comfortable.
Instead of the configuration-based solution we can use a code-based approach that fulfills even more sophisticated requirements. In this sample I assume the helper classes are part of the application assembly. If you would like to place them into a separate helper assembly, you should consider to alter the internal access modifiers to public.
First we define an interface IMailSender with a single method, Send that has a single parameter of type MailMessage.
- interface IMailSender
- {
- void Send(MailMessage message);
- }
Then we implement this interface in two classes, SmtpMailSender and DummyMailSender. Both of this types have a single public constructor with a single SPSite parameter.
In the SmtpMailSender class we send the mail as usual, through the the SMTP server configured in the OutboundMailServiceInstance of the corresponding web application.
- internal class SmtpMailSender : IMailSender
- {
- private SPSite _site;
- public SmtpMailSender(SPSite site)
- {
- _site = site;
- }
- public void Send(MailMessage message)
- {
- Trace.TraceInformation("Mail sending started via SmtpMailSender");
- string smtpHost = _site.WebApplication.OutboundMailServiceInstance.Server.Address;
- SmtpClient smtpClient = new SmtpClient
- {
- Host = smtpHost
- };
- smtpClient.Send(message);
- Trace.TraceInformation("Mail sent to '{0}' via '{1}'", message.To, smtpHost);
- }
- }
In the DummyMailSender class we read the content of the mail as a byte array, and store it in a SharePoint list (name of the list is stored in the GlobalConst.DummyMailList) as an attachment of an item that has its title as the mail subject.
- internal class DummyMailSender : IMailSender
- {
- private SPSite _site;
- public DummyMailSender(SPSite site)
- {
- _site = site;
- }
- public void Send(MailMessage message)
- {
- Trace.TraceInformation("Mail sending started via DummyMailSender");
- Assembly assembly = typeof(SmtpClient).Assembly;
- Type mailWriterType = assembly.GetType("System.Net.Mail.MailWriter");
- // see http://stackoverflow.com/questions/9595440/getting-system-net-mail-mailmessage-as-a-memorystream-in-net-4-5-beta
- byte[] messageData = null;
- using (MemoryStream mailStream = new MemoryStream())
- {
- ConstructorInfo mailWriterContructor = mailWriterType.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(Stream) }, null);
- object mailWriter = mailWriterContructor.Invoke(new object[] { mailStream });
- MethodInfo sendMethod = typeof(MailMessage).GetMethod("Send", BindingFlags.Instance | BindingFlags.NonPublic);
- sendMethod.Invoke(message, BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { mailWriter, true }, null);
- messageData = mailStream.ToArray();
- }
- SPList mailList = _site.RootWeb.Lists[GlobalConst.DummyMailList];
- SPListItem mailItem = mailList.AddItem();
- mailItem[SPBuiltInFieldId.Title] = message.Subject;
- mailItem.Attachments.Add("Mail.eml", messageData);
- mailItem.Update();
- string mailUrl = string.Format("{0}{1}?ID={2}", _site.Url, mailList.DefaultDisplayFormUrl, mailItem.ID);
- Trace.TraceInformation("Dummy mail \"sent\" to '{0}', saved to '{1}'", message.To, mailUrl);
- }
- }
Furthermore, we have a factory class that returns the right implementation of the IMailSender based on a condition evaluated in an extension method called IsTestSystem.
- internal static class MailSenderFactory
- {
- public static IMailSender GetMailSender(SPSite site)
- {
- IMailSender mailSender = site.IsTestSystem() ? (IMailSender)new DummyMailSender(site) : (IMailSender)new SmtpMailSender(site);
- return mailSender;
- }
- }
In our case we check if the site URL contains the substring “test”, of course you can use more advanced conditions and configuration options as well, like values stored in the site property bag, etc.
- public static bool IsTestSite(this SPSite site)
- {
- string siteUrl = (site != null) ? site.Url.ToLower() : string.Empty;
- bool result = siteUrl.Contains("test");
- return result;
- }
In the code of your application you can utilize these classes like illustrated below:
- // prepare mail
- MailMessage mail = new MailMessage("sender@company.com", "addressee@company.com");
- mail.Subject = "Test message";
- mail.Body = "This is the body of a test message";
- //Send mail
- IMailSender mailSender = MailSenderFactory.GetMailSender(site);
- mailSender.Send(mail);
In the test system you have to create manually the list for the mails in the root web. I find it useful, to add the Creation date field to the default view, and order the items based on that field (latest on the top. If you have multiple applications to test, you can use various lists for each one, so it is possible to give permissions only to the users who test the specific application.
You can enable opening .eml files in the browser itself via a PowerShell script like below:
$mimetype = "message/rfc822"
$webapp = Get-SPWebApplication http://yourwebappurl
$webapp.AllowedInlineDownloadedMimeTypes.Add($mimetype)
$webapp.Update()
However, only the body of the message will be displayed in the browser. So if the users need to see other information (like sender, addressee, subject, etc.), they should download the attachment and open it using Outlook.
