Search code examples
wcfmsmq

Setting up MSMQ in a WCF application to ensure reliable mail delivery


So I have been tasked with setting up MSMQ so that if our mail server goes down (which is seems to often) the messages just end up in the Queue and will be delivered when they come back up. With that said I have to say I don't know much about this except what I have learned in the past 24 hours however I believe I know enough to take the right approach but I wanted to ask someone in the community because there is some confusion amongst my colleagues given some existing setup in our WCF application.

Currently we have some services that use msmq as the protocol for the endpoint. the endpoint looks like this

<endpoint address="net.msmq://localhost/private/Publisher"
behaviorConfiguration="BatchBehaviour" 
binding="netMsmqBinding"
bindingConfiguration="MSMQNoSecurity"
contract="HumanArc.Compass.Shared.Publisher.Interfaces.Service.IPublisherSubscriber"
name="PublishSubscriber"/>

This of course lets the client make a service call and if for some reason the service wasn't up it will ensure that when the service comes back up the call will be processed. What I don't think that it will do is if you have something like the following in you service method.

try
{
    smtp.Send(mail);
    return true;
}
catch (System.Net.Mail.SmtpFailedRecipientException ex)
{
     throw new Exception("User Credentials for sending the Email are Invalid",ex);
}
catch (System.Net.Mail.SmtpException smtpEx)
{
   throw new Exception(string.Format("Application encountered a problem send a mail message to {0} ", smtpHostName),smtpEx);
}

WCF isn't going to retry and send the message again somehow, am I correct about this assumption?

What I think we should have is something that looks like the following in place of the call to smtp.send() above. (from http://www.bowu.org/it/microsoft/net/email-asp-net-mvc-msmq-2.html)

  string queuePath = @".\private$\WebsiteEmails";
  MessageQueue msgQ;
  //if this queue doesn't exist we will create it
  if(!MessageQueue.Exists(queuePath))
       MessageQueue.Create(queuePath);
  msgQ = new MessageQueue(queuePath);
  msgQ.Formatter = new BinaryMessageFormatter();
  msgQ.Send(msg);

Then somewhere in the startup of the service (I am not sure where yet) we set up an event handler that will actually call send() on the SmtpClient object. Something like this

 msgQ.ReceiveCompleted += new ReceiveCompletedEventHandler(msgQ_ReceiveCompleted)

So to sum it all up my first question is which way is better? Create a service that uses net:msmq as the protocol or just change the email method to put messages in the queue and set up a handler for it? The next question, if my assumption about changing the method that calls SmtpClient.Send() is correct then where in the program should I wire up ReceiveCompleted? Out WCF service is hosted in a windows service, meaning there is actually a call to ServiceBase.Run(servicesToRun). Is there a place I could wire it up there? My experience with WCF is with much simpler IIS hosted services so I am not 100% sure.

Thanks - I realize this is a long question but I have been trying to research it and there is a lot of information and I can't seem to find a clear explanation of the benefits of doing things one way vs another.


Solution

  • Your approach to using msmq to address availability in a downstream dependency (in this case your smtp server) is valid. However, there are a couple of things you should understand about msmq first.

    If you create a queue in msmq then by default it is non-transactional. In this mode the queue will not provide the kind of guaranteed delivery semantic you require. So create your queues as transactional.

    Then you can tell WCF that your service operation will enlist in the transaction when it receives a message for processing. You do this by defining a behavior on your service operation implementation:

    [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
    public void SendEmail(Something mail)
    {
        ....
        smtp.Send(mail);
    }
    

    TransactionScopeRequired tells WCF that the service operation should enlist in the same transaction used to transmit the message from sender to receiver. TransactionAutoComplete states that the service method should commit the transaction once the operation has successfully completed. So in answer to your query above, a failure in the service operation will cause the transaction to rollback.

    What happens at this point depends on your service bindings configuration.

    <netMsmqBinding>
      <binding name="netMsmqBinding_IMyServiceInterface" 
               exactlyOnce="true" 
               maxRetryCycles="3" 
               retryCycleDelay="00:01:00" 
               receiveErrorHandling="Move"> <-- this defines behavior after failure
        ...
      </binding>
    </netMsmqBinding> 
    

    When, for whatever reason the transaction is not committed (for example, an unhandled exception occurs), WCF will roll the message back onto the queue and retry processing once per minute up to 3 times (defined by maxRetryCycles and retryCycleDelay).

    If the message still fails processing after this time then the receiveErrorHandling attribute tells WCF what to do next (The above binding specifies that the message be moved to the system poison message queue).

    Note: exactlyOnce tells WCF that we require transactions, that each message will be delivered exactly once and in the order they were sent.

    So your original approach is in fact correct and you just need to configure your service correctly to implement the behavior you want.