Navigation

Search

Categories

On this page

Archive

Blogroll

Disclaimer
None - these are my opinions and they're also my employer's!

RSS 2.0 | Atom 1.0 | CDF

Send mail to the author(s) E-mail

Total Posts: 14
This Year: 0
This Month: 0
This Week: 0
Comments: 15

Sign In

 Saturday, November 25, 2006
Saturday, November 25, 2006 11:33:20 PM (Mountain Standard Time, UTC-07:00) ( )

I needed to get to SMTP messages in the most efficient way possible without the overhead of POP3, etc.  When I searched around I found a great Microsoft article on how to create managed event sinks using some pre-fabbed event interop and wrapper code.  For the most part the instructions are self-explanatory but I did run into some issues with the samples.

The first sample describes building a sink that basically does nothing but access the parameters passed through the wireup interface.  The article walks you through setting the project build property Register for COM interop which basically runs RegAsm behind the scenes.  Well, if you forget to put the ComVisible attribute on your class you won't actually register any types which of course means you can't register with the SMTP service to be called via COM.  Running RegAsm directly will alert you to this fact, so I recommend not setting the property until you know your COM goo is in place correctly.

   [ComVisible(true), Guid("B3AEF136-3064-429c-8D61-4D8AA8EE8B29")]
   public class EventSink : ISmtpInCommandSink, IEventIsCacheable
   {

A key piece to making all this work is the registration with the SMTP service of your new COM type.  This is accomplished using the handy smtpreg.vbs script.  The /enum option is great for displaying all currently registered sinks.  Your assembly must be RegAsm'd first which, of course, requires strong naming.

The second sample has a more practical use in that it will actually terminate any SMTP session based on the EHLO request being received.  There is a code typo, though:

   if (null != pCcontext OOPS!)
   {
       Marshal.ReleaseComObject(pContext);
   }

In the ShieldsUp sample they also implement the IEventIsCacheable interface method IsCacheable().  This implementation allows the SMTP Service Extension Objects (SEO) process to avoid tearing down and recreating your component thus avoiding the overhead of CoCreateInstance() on each call.

Once I worked through the samples and got my feet wet creating and registering the SEO components, I was able to build what I needed pretty quickly.  In essence, my sink needed to intercept the messages that were inbound to my SMTP server and send them to a transactional MSMQ queue.  I found some help related to handling incoming messages using IMailTransportSubmission here

I also found some System.Messaging MSMQ optimization tips here that I followed since this process will need to be able to handle thousands of messages.  The event sink wrappers exposed a CopyContentToStream() method on the Message object which worked well with the serialization optimization strategies outlined in the document.  I basically took the message and after verifying that it isn't too long (over 1,000,000 bytes) sent the entire contents to a queue as you can see below: 

using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.IO;
using MSMQ = System.Messaging;
using Mail = System.Net.Mail;
using Microsoft.Exchange.Transport.EventInterop;
using Microsoft.Exchange.Transport.EventWrappers;
 
namespace SMTPEventSink
{
   [ComVisible(true), Guid("AD1F3BB9-22E9-4207-BBAC-F767DB32FA0D")]
   public class QueueMailMessage : IMailTransportSubmission, IEventIsCacheable
   {
      #region IMailTransportSubmission Members
      void IMailTransportSubmission.OnMessageSubmission(
         MailMsg pIMailMsg, 
         IMailTransportNotify pINotify, 
         IntPtr pvNotifyContext)
      {
         MSMQ.MessageQueueTransaction msmqTran = new MSMQ.MessageQueueTransaction();
         try
         {
            Message msg = new Message(pIMailMsg);
            if (msg.GetContentSize() < 1000000)
            {
               MemoryStream contentStream = new MemoryStream(Convert.ToInt32(msg.GetContentSize()));
               msg.CopyContentToStream(contentStream);
               MSMQ.MessageQueue msmqQueue = 
                  new MSMQ.MessageQueue(
                     @".\private$\SMTPInBoundMessages", 
                     System.Messaging.QueueAccessMode.Send);
               msmqTran.Begin();
               using (MSMQ.Message msmqMsg = new MSMQ.Message())
               {
                  msmqMsg.BodyStream = contentStream;
                  msmqMsg.Label = msg.Rfc822MsgId;
                  msmqMsg.Recoverable = false;
                  msmqQueue.Send(msmqMsg, msmqTran);
               }
               msmqTran.Commit();
 
               // abort message processing so message is intercepted
               msg.MessageStatus = Message.MessageStatusEnum.AbortDelivery;
               throw new COMException("Abort delivery");
            }
            else
            {
               LogEvent(string.Format(
                  "QueueMailMessage - message too long ({0} bytes)", 
                  msg.GetContentSize()));
            }
         }
         catch (COMException)
         {
            // ignore - we want this to bubble up
         }
         catch (Exception ex)
         {
            msmqTran.Abort();
            LogEvent(ex.ToString());
         }
         finally
         {
            if (null != pIMailMsg)
            {
               Marshal.ReleaseComObject(pIMailMsg);
            }
            if (null != pINotify)
            {
               Marshal.ReleaseComObject(pINotify);
            }
            if (null != pvNotifyContext)
            {
               Marshal.ReleaseComObject(pvNotifyContext);
            }
         }
      }
      #endregion
 
      #region IEventIsCacheable Members
      void IEventIsCacheable.IsCacheable()
      {
         // returns S_OK by default
      }
      #endregion
 
      private void LogEvent(string message)
      {
         if (!EventLog.SourceExists("SMTPEventSink"))
         {
            EventLog.CreateEventSource("SMTPEventSink", "Application");
         }
         EventLog.WriteEntry("SMTPEventSink", message);
      }
   }
}

Another gotcha that I experienced while debugging and building this managed SMTP event sink is that once the SMTP service has accessed your assembly file you will have to do IISReset to get it freed again.  I tried removing the binding using smtpreg.vbs and stopping the SMTP service in the IIS management MMC but to no avail.  Guess that's a small price to pay for such close to the edge control.

I used the following command lines to add and remove the binding for this new event sink with SMTP SEO:

cscript smtpreg.vbs /add 1 OnTransportSubmission SMTP2MSMQ SMTPEventSink.QueueMailMessage "mail from=*"

cscript smtpreg.vbs /remove 1 OnTransportSubmission SMTP2MSMQ

Overall, I was impressed with how easy it was to build this event sink and found the documentation pretty straightforward and reliable.  Now all I have to do is move everything to the appropriate environment and harden it a bit.

Comments [1] | | # 
Tracked by:
"SMTP Event Sink Registration Problem" (Jason Foster's Blog - wonderings) [Trackback]
http://blog.jasonfoster.com/PermaLink,guid,e1f53d2c-a6f6-46f3-9f15-7fe4924c4729.... [Pingback]
http://9nj-information.info/57026552/generic-to-ebay.html [Pingback]
http://9ni-information.info/23776761/student-book-corporation-vancouver-wa.html [Pingback]
http://9nl-information.info/02886119/dog-rescue-nc.html [Pingback]
http://9ns-information.info/29933837/index.html [Pingback]
http://9nm-information.info/95021500/index.html [Pingback]
http://9nd-information.info/77029181/index.html [Pingback]
http://9me-free-porn.info/32157269/index.html [Pingback]
http://9na-information.info/81042407/index.html [Pingback]
http://9na-information.info/22194758/index.html [Pingback]
http://9nl-information.info/15492575/index.html [Pingback]
http://9nc-information.info/62084231/official-tupelo-city-site.html [Pingback]
http://9nh-information.info/27199474/index.html [Pingback]
http://9mb-free-porn.info/95313582/index.html [Pingback]
http://9mi-free-porn.info/51258069/index.html [Pingback]
http://9mc-free-porn.info/38812418/index.html [Pingback]
http://9nl-information.info/55605763/learn-thriller-dance-routine.html [Pingback]
http://9me-free-porn.info/15799979/index.html [Pingback]
http://9ni-information.info/78320375/index.html [Pingback]
http://9ng-information.info/57088082/food-guidelines-for-party.html [Pingback]
http://9nu-information.info/83349499/satellite-saver.html [Pingback]
http://9nm-information.info/14470040/index.html [Pingback]
Friday, January 25, 2008 4:04:21 PM (Mountain Standard Time, UTC-07:00)
Really useful bit of code and a good example - enjoyed following it through to make some stuff I was doing on conjunction with Biztalk Server 2006 work intercepting emails
Comments are closed.