How to control routing from your own routing agent

With the release of Microsoft Exchange Server 2007 Service Pack 1 you can programmatically override the default routing for message recipients on a per-recipient basis. This can be done by using the SetRoutingOverride method on the recipient for which you want to override the default routing.

Let's assume that you have an Exchange 2007 server with just one send connector "Internet Connector"" using DNS for the address space *. With this configuration all messages leaving the organization will use this connector. Let's further assume that you want to route messages from certain senders to a smarthost instead of using DNS. This can be done as follows:

- Create an additional send connector "Smarthost Connector" pointing to the smarthost
- Specify a non-existing domain (e.g. nexthopdomain.com) as address space of the new connector
- Write, install and enable a routing agent which registers for the OnResolvedMessage event and overwrites the default routing for the recipient.

The sample agent of this article shows you how to route messages from administrator@contoso.com over the new connector, the routing for all other sender's won't be changed. 

1.) Create a C# project (dll/library type) 

2.) Copy the following DLLs from the C:\Program Files\Microsoft\Exchange Server\Public directory of an Exchange 2007 server to the debug directory of your new C# project:
     a. Microsoft.Exchange.Data.Common.dll
     b. Microsoft.Exchange.Data.Transport.dll

3.) Add references to the two DLLs to the C# project using the Visual Studio solution explorer

4.) Add the following code to your project:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Exchange.Data.Transport;
using Microsoft.Exchange.Data.Transport.Email;
using Microsoft.Exchange.Data.Transport.Smtp;
using Microsoft.Exchange.Data.Transport.Routing;
using Microsoft.Exchange.Data.Common;

namespace RoutingAgentOverride
{
    public class SampleRoutingAgentFactory : RoutingAgentFactory
    {
        public override RoutingAgent CreateAgent(SmtpServer server)
        {
            RoutingAgent myAgent = new ownRoutingAgent();

            return myAgent;
        }
    }
}
public class ownRoutingAgent : RoutingAgent
{
    public ownRoutingAgent()
    {
        //subscribe to different events
        base.OnResolvedMessage += new ResolvedMessageEventHandler(ownRoutingAgent_OnResolvedMessage);
    }

    void ownRoutingAgent_OnResolvedMessage(ResolvedMessageEventSource source, QueuedMessageEventArgs e)
    {
        try
        {
            // For testing purposes we do not only check the sender address but the subject line as well
            // If the subject contains the substring "REDIR" then the default routing is overwritten.
            //
            // Instead of hard-coding the sender you could also perform an LDAP-query, read the information
            // from a text file, etc.
            //
            if (e.MailItem.FromAddress.ToString() == "administrator@contoso.com"
                && e.MailItem.Message.Subject.Contains("REDIR"))
            {

                // Here we set the address space we want to use for the next hop. Note that this doesn't change the recipient address.
                // Setting the routing domain to "nexthopdomain.com" only means that the routing engine chooses a suitable connector
                // for nexthopdomain.com instead of using the recpient's domain.

                RoutingDomain myRoutingOverride = new RoutingDomain("nexthopdomain.com"); 
                                                                                         
                foreach (EnvelopeRecipient recp in e.MailItem.Recipients)
                {
                    recp.SetRoutingOverride(myRoutingOverride);

                }

            }
        }

        catch // (Exception except)
        {

        }
    }

}

5.) Compile the DLL

6.) Copy the DLL to the HT server

7.) Install the transport agent using the Exchange Management Shell:
     Install-TransportAgent "OwnTestAgent" -TransportAgentFactory "RoutingAgentOverride.SampleRoutingAgentFactory" -AssemblyPath "Path to DLL"

8.) Enable the transport agent using the Exchange Management Shell:
     Enable-TransportAgent "OwnTestAgent"

9.) IMPORTANT: Exit Powershell
10. IMPORTANT: Restart the MSExchangeTransport service

11.) Verify that the agent was successfully enabled / registered by running Get-Transportagent

Tips:

  • To live debug the dll you need to attach to edgetransport.exe.
  • To recompile after a change
    • detach from edgetransport
    • stop the transport service (otherwise Visual studio won't be able to overwrite the file)
      (only if you registered the dll from the \debug folder)
  • Sometimes messages remain in the outbox until the mail submission service is restarted

Comments

  • Anonymous
    January 01, 2003
    Sorry to ask again, but why should internal mail go to a smarthost? If you create a connector pointing to a smarthost and specify an address space like nexthopdomain.com then ONLY messages sent directly to this domain and messages for which the agent sets nexthopdomain.com as 'RoutingOverride' will go through this connector. If this is still not clear then you need to describe your problem in more details: Topology (servers, roles, connectors, address spaces, ...)

  • Anonymous
    January 01, 2003
    You could add a header field (e.g. X-already-processed).

  • Anonymous
    January 01, 2003
    The comment has been removed

  • Anonymous
    January 01, 2003
    It does'nt work, if I compile the sourcecode. I think the problem is my .net framework. Can anyone compile the source-code from above and then load the .dll file up, so I can test my exceptation? Thanks.

  • Anonymous
    January 01, 2003
    Hello, I am trying to write a custom transport agent for the first time using the steps above, however when i pasted the supplied code into my Visual Studio project, and then try to save and build the project to compile the DLL, the build fails. I see several errors within the code, but don't have the programming background to troubleshoot and resolve them. Is there anything additional besides the code above that I would need? Right now, I only have "Public Class Class1" at the very top, and "End Class" at the very bottom, with the code from above in between. Thanks.

  • Anonymous
    January 01, 2003
    What happens if you move the DLL to another location and try to register it from there? Do you get the same error?

  • Anonymous
    January 01, 2003
    I don't understand the question. If you specify a certain address space like nexthopdomain.com - why should internal messages be routed over this connector? The address space should be a dummy-address-space which is NOT used as proxy address and which does NOT match any accepted domain.

  • Anonymous
    November 03, 2009
    Hi there, I have written a RoutingAgent(installed on HUB) to override the default outbound routing to another send connector for messages that have secure in the subject. THe send connector then sends the mail back to the HUB server, here the RoutingAgent should ignore this message , otherwise it goes in a loop. How do we achieve the same. Can i use e.MailItem.OriginatingDomain to do the same. Thanks!!

  • Anonymous
    December 09, 2009
    The comment has been removed

  • Anonymous
    December 14, 2009
    Nevermind, cracked it using: if (e.MailItem.FromAddress.DomainPart.Contains("secure.contoso.com"))

  • Anonymous
    December 31, 2009
    We have put the dll we created in the same folder as the other dlls and tried to install it.  We are getting an error that the Transport Agent assembly file does not exist.  Here is the command we used and the output: [PS] C:Windowssystem32>Install-TransportAgent "OwnTestAgent" -TransportAgentFactory "RoutingAgentOverride.SampleRoutin gAgentFactory" -AssemblyPath "C:Program FilesMicrosoftExchange ServerV14PublicExchangeRouter.dll" The Transport Agent assembly file "C:Program FilesMicrosoftExchange ServerV14PublicExchangeRouter.dll" doesn't ex ist. Parameter name: AssemblyPath    + CategoryInfo          : InvalidArgument: (:) [Install-TransportAgent], ArgumentException    + FullyQualifiedErrorId : 77AF4613,Microsoft.Exchange.Management.AgentTasks.InstallTransportAgent

  • Anonymous
    January 04, 2010
    I moved the DLL to a new location, but still get the error.  I should also mention that this is Exchange 2010. [PS] C:Windowssystem32>Install-TransportAgent "OwnTestAgent" -TransportAgentFactory "RoutingAgentOverride.SampleRoutin gAgentFactory" -AssemblyPath "C:Exchange_DLL/ExchangeRouter.dll" The Transport Agent assembly file "C:Exchange_DLL/ExchangeRouter.dll" doesn't exist. Parameter name: AssemblyPath    + CategoryInfo          : InvalidArgument: (:) [Install-TransportAgent], ArgumentException    + FullyQualifiedErrorId : 77AF4613,Microsoft.Exchange.Management.AgentTasks.InstallTransportAgent

  • Anonymous
    January 04, 2010
    Actually, I have found there is an issue connecting the ems to that server.  Likely this is why. Thank you for the response.

  • Anonymous
    February 03, 2010
    hi, Thank you very much for your solution which worked for me.. But my internal mails are also getting routed through the samrt host.Kindly help me to overcome this behaviour as i don't have much knowledge in programming..

  • Anonymous
    March 08, 2010
    We also are having all mail (including internal) going to the smart host. Example: UserA@usa.com sends an email to UserB@usa.com The mail is sent to the smart host and then tries to route back to Exchange.  Exchange also rejects it. Is there a way to make it check internally before seeing if it should use the smart host?

  • Anonymous
    March 29, 2010
    We are still having the same behavior when using the routing agent.  We had to go back to sending all mail out the smart host until we can resolve this.   We had thought of putting a check in there that would not use the smarthost connector if the mail sending domain was the same as the recipient.  Not sure how that would handle mail that had some internal and some external address though. Any thoughts on another way to get the routing agent to keep the local mail local?

  • Anonymous
    April 06, 2010
    I too have no idea why internal mail would be going out this way instead of just being delivered locally.  I should mention that this is Exchange 2010.  We have one hub, one cas, and two mb servers in a dag.  We have two send connectors.  One has the nexthopdomain address space, and one has *.  Anything that gets marked as being from a domain that should use the nexthop connector sends out that way, including internal mail.  The smarthost also then passes the back, but Exchange just sends it back to the smarthost, in an endless loop.  Anything not marked for nexthop goes to the other connector, sends internal and external mail properly. If there is an email address I could send you more info to as well, that would be great.

  • Anonymous
    April 08, 2010
    We fixed the issue by adding some code.

  • Anonymous
    April 14, 2010
    Eric, would you be able to post the additional code you needed? I would like to try this but looking at the example it seems that the behaviour you describe is unavoidable as it stands. The example says that any email from administrator@contoso.com with REDIR in the subject will go over the connector so it looks like internal email would do the same.

  • Anonymous
    July 26, 2010
    Does anyone know how to intercept Meeting Types?  Great article by the way. Cheers John

  • Anonymous
    July 27, 2010
    Does anyone know how to intercept Meeting Requests?  We need something similar but we need to check for a Meeting Request, look at the receipients and if there are flagged email on the request, trash the request.  We've search for days and posted on various blogs but it seems nobody has an answer.  Surely you can do with by writing you own Agent, as above. Can anyone help out there!?   Thanks in advance John

  • Anonymous
    August 09, 2010
    The comment has been removed

  • Anonymous
    August 09, 2010
    The comment has been removed

  • Anonymous
    August 11, 2010
    The comment has been removed

  • Anonymous
    October 10, 2010
    I know this code has problems with trying to route internal mail through the dummy 'nexthopdomain' connector however I would like to understand how to insert an exception in this code to ignore messages intended for internal use only.  A little help on this would be much appreciated.

  • Anonymous
    October 17, 2010
    Would something like this resolve this issue of forwarding internal mail through the send connector?? RoutingDomain myRoutingOverride = new RoutingDomain("nexthopdomain.com");                foreach (EnvelopeRecipient recp in e.MailItem.Recipients)                {                    if (!recp.Address.DomainPart.Contains("internal-domain-name-here"))                    {                        recp.SetRoutingOverride(myRoutingOverride);                    }                }            }        }        catch // (Exception except)        {        }    } }

  • Anonymous
    October 17, 2010
    I've tested this addition to the code which finally works!  Basically this custom transport rule is ignored if the message recipient address contains the internal domain name (or contains part of), therefore all internal messages are routed normally.

  • Anonymous
    October 18, 2010
    After installing SP1 to 2010 on the HUB we had issues with this.  Taking out the custom routing agent resolved the issue, but the mail is not routing the way we would like of course. When We try to reinstall the agent, the error is: The TransportAgentFactory type "RoutingAgentOverride.SampleRoutingAgentFactory" doesn't exist. The TransportAgentFactory type must be the Microsoft .NET class type of the transport agent factory. Parameter name: TransportAgentFactory    + CategoryInfo          : InvalidArgument: (:) [Install-TransportAgent], ArgumentException    + FullyQualifiedErrorId : 779E4613,Microsoft.Exchange.Management.AgentTasks.InstallTransportAgent Any ideas on this?

  • Anonymous
    October 20, 2010
    The comment has been removed

  • Anonymous
    October 21, 2010
    Graham, Yes, we also did have to use the new DLLs.  Also found that the method we were using for internal mail to work had to be altered.

  • Anonymous
    November 06, 2010
    The code must be complied with the correct common and transport DLL's for your system. If you say update your Exch2010 system to SP1 and/or Rollup 1 these DLL's will change. You then need to remove from your C# project and add the new DLL's (resources) in. I've uploaded my transport agent however this is complied for Exch2010 SP1 with Rollup1. rapidshare.com/.../agent.dll Use installer command: Install-TransportAgent "Myagent" -TransportAgentFactory "RoutingAgentOverride.gcRoutingAgentFactory" -AssemblyPath "Path to DLL"

  • Anonymous
    January 25, 2011
    Hi I am getting problem installing 2007 routing agent; social.technet.microsoft.com/.../da9fc6bc-8449-42b0-a23f-0c8744b688c0 Regards

  • Anonymous
    December 06, 2011
    Hi, I used this routing agent for a while and it works very good. But after installing service pack 2 I cannot send any mails. After deactivating the routing agent everything works fine again. anyone have the same problem and know a solution? Regards

  • Anonymous
    December 06, 2011
    Sorry, I fixed the problem. 2 new dlls were installed with the sp2. I recompiled the routing agent and everything works fine.

  • Anonymous
    February 05, 2012
    Thank you thank you thank you! That really saved me from spending money for a 3rd-Party application to route e-mails to different smarthosts by sender domain. Works like charm :-)

  • Anonymous
    October 22, 2012
    Hi All I enhanced Grahams code so that it is externally configurable: www.tuescher.net/.../index.html Regards, Martin

  • Anonymous
    December 27, 2012
    I've tried this for Exchange 2007 and a test account.  Mail is not being rerouted for the test account.  What the bet way to troubleshoot it? Thanks Matt

  • Anonymous
    April 09, 2013
    Here is my thought.

  1. Create a Transport Rule with condition “If from address matches “specific sender” redirect to “contact(with different domain name)”
  2. Create a Send connecter to forward email (having domain name of the contact) to specific gateway