Wednesday, 12 January 2011

How to customize Sharepoint E-Mail-Enabled document library using SPEmailEventReceiver

I guess it is not new to everyone that Sharepoint can receive e-mails in a document library. This feature from its inception was meant to finally replace Exchange Public Folders. But Exchange 2010 still has Public Folders even though Sharepoint has been able to receive e-mails since 2003 version! One of the reasons that E-Mail-Enabled document libraries in Sharepoint are not still widely used must be that out-of-the-box feature is pretty much inflexible. You can do just the couple of things with it such as:
  • Save files that are attached to an e-mail in a document library
  • Save original e-mails as a separate eml item in a document library
  • Ability to choose weather you want to overwrite attached documents with the same name
And that's about is.

So, what if you are receiving e-mails all attached with the same file name but with different content? You would not be able to use this feature because you would have an option to overwrite or not to overwrite the file with the same name and you could not save all the attachments you receive. Sharepoint allows to group attachments in a separate folders based on e-mail sender address or e-mail subject. But what if you are receiving e-mail from the same sender with the same e-mail subject every time?

Consider using a fax machine (yes, faxes are still a very popular thing) that sends an e-mail with an pdf document attached that represents fax message. You specify an e-mail address where this e-mail messages will be sent to, let's say it is your Sharepoint E-Mail Enabled document library. Fax machine hardly has an option to change e-mail subject with each e-mail that is sent and the sender address is always the same.

Fortunately the fax machine I've encountered always attaches the pdf document with different name so we were able to store every pdf/fax in a document library that is received. But the message body also contains important information such as the number from which the fax was received, destination number and the receive date. If we choose the option only to save attachments to document library we would loose this information forever. If we choose the option to save original e-mails as a separate eml item in a document library we could only save one eml item because every e-mail has the same subject!. And of course it is not intuitive at all to open eml item just to see the message body.

The solution to this kind of problems is to use SPEmailEventReceiver event handler object in Sharepoint 2010 and with a little bit of effort we could catch every e-mail that is destined to this document library and change the default behavior to whatever suits or scenario.

By default, Sharepoint represents this GUI when we click on "Incoming e-mail settings" on the "Library Settings" page:


 

But once we register our own event handler we only get something like this:


This is because we can now only specify an e-mail address for the document library and we should do the rest of the customization with Visual Studio 2010.

To create custom SPEmailEventReceiver you need to install Sharepoint 2010 and Visual Studio 2010 on the same Window Server 2008 R2 machine. Otherwise if you try to create a new project using Sharepoint 2010 templates you will get an error message like this:



Now, let's say that our fax device is sending e-mails with always the same subject and an attachment with filename that is always different. The message body also contains "FROM=" line which specifies the sending fax number. Once the e-mail is picked up by Sharepoint from the local SMTP service drop folder and delivered to the document library our SPEmailEventReceiver will save the attachment in the document library and read a message body to extract the FROM field. The sending fax number will be saved in a custom field FaxNumber that we could later use to create views that group received faxes from a certain sender.

I will now cover the basic steps that will help to create such an event receiver:
  • Create a Sharepoint site
  • Create a Document Library and enable it for e-mails - type only e-mail address, the rest of the properties are not important to us
  • Open Visual Studio 2010 and create a new project from Sharepoint 2010 template and select Event Receiver template
  • Enter the URL which points to the Sharepoint site you have created and select "Deploy as farm solution" option
  • Select "List Email Events" for the event receiver type and "Document Library" for the event source. Of course you can select any other event source that you need such as "Custom List" for example
When you complete the steps you should be presented with the code template that you can now customize to your own need. Here is a copy/paste of the code that I have used:


using System;
using System.Security.Permissions;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Security;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.Workflow;
using System.Runtime.InteropServices;

namespace FaxReceiver.EventReceiver1
{
    /// <summary>
    /// List Email Events
    /// </summary>
    public class EventReceiver1 : SPEmailEventReceiver
    {
       /// <summary>
       /// The list received an e-mail message.
       /// </summary>
       public override void EmailReceived(SPList list, SPEmailMessage emailMessage, String receiverData)
       {
           //base.EmailReceived(list, emailMessage, receiverData);
           
           //list.RootFolder.Files.Add(list.RootFolder.ServerRelativeUrl=
           //oListItem["Name"] = emailMessage.Headers["Subject"];
           //oListItem["Body"] = emailMessage.HtmlBody;
           SPEmailAttachmentCollection attachColl = emailMessage.Attachments;
           foreach (SPEmailAttachment attach in attachColl)
           {
               //Exception ex =  Marshal.GetExceptionForHR(-2130575257);
               try
               {
                   AddFileToDocLib(list, attach, attach.FileName,emailMessage.PlainTextBody);
               }
               catch (SPException ex)
               {
                    if (ex.ErrorCode == -2130575257) //a file alredy exists
                    { 
                        //append random number to the beginning of the file name
                        AddFileToDocLib(list, attach, "123456" + attach.FileName, emailMessage.PlainTextBody);
                    }
               }
           }
       }

       private void AddFileToDocLib(SPList list, SPEmailAttachment attach, string filename, string messagePlainText)
       {
           SPFile file = list.RootFolder.Files.Add(list.RootFolder.ServerRelativeUrl + "/" + filename, attach.ContentStream);
           ExtractMessageProperties(messagePlainText, file);
           file.Update();
       }

       private void ExtractMessageProperties(string messagePlainTextBody, SPFile file)
       {

           file.Properties["MessageBody"] = messagePlainTextBody;
           string[] messageBody = messagePlainTextBody.Split("\n\r".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);

           foreach(string str in messageBody)
           {
               if (str.IndexOf("FROM", 0) != -1)
               {
                   string strFaxNumber = str.Replace("FROM=","");
                   file.Properties["FaxNumber"] = strFaxNumber;
               }
           }
       }


    }
}

For this code to work on your document library you need to create these custom fields:
  • FaxNumber (Single line of text) - used for storing sending fax numbers
  • MessageBody (Multiple lines of text) - used for storing the complete message body for later reference
Now you should build and deploy your solution using the Build menu in Visual Studio. Your feature should be automatically activated on the site collection. You can check that by going to the root site of the site collection and selecting Site Settings > Manage site features. You should see something like this:



Now you can send an e-mail with attachment and make sure the message body contains the "FROM=" line with the fax number next to it such as "FROM=23456789".

Debugging SPEmailEventReceiver

Of course, you will ran into problems sooner or later when playing with event receivers so it is useful to know how to debug them using Visual Studio. MSDN documentation here describes how you can debug event receivers. This procedure works well for perhaps all event receivers except SPEmailEventReceiver so you will need to use other technique. The problem with the SPEmailEventReceiver is that it does not run in w3svc.exe process like Visual Studio assumes. In fact, SPEmailEventReceiver is run in owstimer.exe process which is actually "Sharepoint 2010 Timer" service. Unlike the rest of the event handlers which are triggered when the item is added, updated or deleted in a document library, SPEmailEventReceiver is triggered by Sharepoint Timer service at the time when an e-mail is picked up from the SMTP drop folder on a local Sharepoint server. This means that if we want to use breakpoints in our SPEmailEventReceiver we need to attach the debugger to OWSTIMER.EXE process and only then will the breakpoints be hit. You are not required to set "Active Deployment Configuration" to "No Activation" as stated in MSDN article but instead each time you deploy a solution you need to restart "Sharepoint 2010 Timer" service for the new dll to be loaded.

One additional peace of information that is available when debugging SPEmailEventReceiver is by checking the following Windows Event Logs - Applications and Services Log > Microsoft > Sharepoint Products > Shared > Operational. Here you will see that Sharepoint 2010 Timer service processes e-mails from the SMTP drop folder every minute. If if fails to process e-mails for some reason the error message will be logged here.


I will not describe here how to set up SMTP service on a Sharepoint server to receive e-mails because it is already well described on many blogs. But to make things easier for you I will tell you that I use Windows Live Mail client that is installed on my development server and I've set up the profile that points to the local SMTP server. When I send an e-mail I send it to something like shared.documents@vssp2010.local where "shared.documents" is what I typed in the document library incoming e-mail settings and vssp2010 is the name of the development server. Configure the SMTP service to accept e-emails for vssp2010.local domain and you can now easily generate e-mails for your Sharepoint.


I wish you happy customizing!

6 comments:

  1. You can use SharePoint email enalbed list such as Announcements list to track email as one item with attachments. It is better than using document library or you can use http://www.anurasoftware.com/ 's solution to enabled incoming email for any list.

    ReplyDelete
  2. Hello,
    I've follow the steps provided, but nothing appears to be happening with my event - two emails are displayed in the SharePoint list, one for the email, one for the attachment (PDF).
    However, nothing is displayed in the custom fields: FaxNumber and MessageBody.
    Any idea?
    thanx

    ReplyDelete
  3. Hi. I was wondering how you wrote the elements.xml file. I have deployed the email handler, but the event does not fire. I checked the SP logs, and there is one error "Event manager error: Unable to cast object of type 'CorporatePoliciesWorkflow.CheckWorkflowMails' to type 'Microsoft.SharePoint.SPItemEventReceiver'."

    This is my elements.xml



    CheckWorkflowMails
    EmailReceived
    20000
    CorporatePoliciesWorkflow, Version=1.1.1.2, Culture=neutral, PublicKeyToken=85fe4fb5ee7674d0
    CorporatePoliciesWorkflow.CheckWorkflowMails

    ReplyDelete
  4. I have used SPEmailEventReceiver one of my project. It was working fine. Now a days the event is not firing automatically. I have to restart share point timer regularly. Then it picks the .eml files from inetpub/../drop folders. and its start works.
    Any Idea ?

    ReplyDelete
  5. Hi,

    What about other timer events in Sharepoint, do they fire or is it just related to e-mail events?

    ReplyDelete
  6. You don't explain how to attach it to the library. I deploy my solution by the SPEmailEventReciever is not attached to the doc lib

    ReplyDelete