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 (38)

  1. thorstd says:

    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, …)

  2. thorstd says:

    You could add a header field (e.g. X-already-processed).

  3. to matty-uk

    Attach a debugger and check if the agent fires. If yes then step through the code and see verify that SetRoutingOverride is called for the recipient(s).

  4. Anonymous says:

    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.

  5. am12375 says:

    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.

  6. thorstd says:

    What happens if you move the DLL to another location and try to register it from there? Do you get the same error?

  7. thorstd says:

    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.

  8. Subha says:

    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!!

  9. CBCTechy says:

    Hello there

    First up, thanks for this, works like a charm… However..

    In the section:

    if (e.MailItem.FromAddress.ToString() == "administrator@contoso.com"

    Is there a way to select multiple users or a group from AD? I’m guessing LDAP wouldn’t work by itself…

    Oh and sorry for my lack of C# knowledge incase it’s a really simple answer. I just cant find anywhere that explains what I would need to do here.

    Thanks in advance

  10. CBCTechy says:

    Nevermind, cracked it using:

    if (e.MailItem.FromAddress.DomainPart.Contains("secure.contoso.com"))

  11. Eric H says:

    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

  12. Eric says:

    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

  13. Eric says:

    Actually, I have found there is an issue connecting the ems to that server.  Likely this is why.

    Thank you for the response.

  14. Sarath Kumar M says:

    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..

  15. Eric says:

    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?

  16. Eric says:

    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?

  17. Eric says:

    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.

  18. Eric says:

    We fixed the issue by adding some code.

  19. Damien says:

    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.

  20. John says:

    Does anyone know how to intercept Meeting Types?  Great article by the way.

    Cheers

    John

  21. John says:

    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

  22. Graham Hosking says:

    After hours of playing with different ways of recreating the agentfactory and class name, and knowing that it does exist!  –  I found that if you've copied the code from this webpage it doesn't paste correctly. Try copied text into notepad first and perform an iisreset to clear down Powershell Connections.

    Let me know if this resolves your issues.

    ERROR : Install-TransportAgent : The TransportAgentFactory type "MyAgent.MyAgents.MyAge

    ntFactory" does not exist. The TransportAgentFactory type must be the Microsoft

    .NET class type of the transport agent factory.

    Parameter name: TransportAgentFactory

    At line:1 char:23

    + Install-TransportAgent  <<<< -Name "MyCustomAgent" -TransportAgentFactory "My

    Agent.MyAgents.MyAgentFactory" -AssemblyPath "C:myagentsMyAgent.dll"

  23. Graham says:

    After hours of playing with different ways of recreating the agentfactory and class name, and knowing that it does exist!  –  I found that if you've copied the code from this webpage it doesn't paste correctly. Try copied text into notepad first and perform an iisreset to clear down Powershell Connections.

    Let me know if this resolves your issues.

    ERROR : Install-TransportAgent : The TransportAgentFactory type "MyAgent.MyAgents.MyAge

    ntFactory" does not exist. The TransportAgentFactory type must be the Microsoft

    .NET class type of the transport agent factory.

    Parameter name: TransportAgentFactory

    At line:1 char:23

    + Install-TransportAgent  <<<< -Name "MyCustomAgent" -TransportAgentFactory "My

    Agent.MyAgents.MyAgentFactory" -AssemblyPath "C:myagentsMyAgent.dll"

  24. Ivcha says:

    I do not see that this was explicitly mentioned anywhere, but it looks like that dummy adrress space such as "nexthopdomain.com" cannot be used with send connectors which use DNS to route mail.

    For example, if I sent a message to anyuser@gmail.com and my transport agent set routing override with the domain nexthopdomain.com, NDR is generated with the error message '554 5.4.4 SMTPSEND.DNS.NonExistentDomain; nonexistent domain'. This did not look obvious to me, but after testing I realized that the Exchange server was trying to find the MX record for nexthopdomain.com in order to deliver the message for anyuser@gmail.com.

  25. Graham Hosking says:

    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.

  26. Graham Hosking says:

    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)

           {

           }

       }

    }

  27. Graham Hosking says:

    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.

  28. jimmy says:

    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?

  29. Graham Hosking says:

    Hi Jimmy,

    Yes, I too have experiencing the same issues with "Transport Agent Factory Type" – still not sure of the root cause of this problem, however try this…..  copy the code into notepad.

    Create a new project in VS and then build solution and build the agent.  Try to do this on the local domain and copy the agent directly to the Exch server via UNC.  May sure you use the Exchange DLL from SP1 also (however I believe they haven't change during the SP1 update).  

    Let me know how you get on.  – Graham

    graham.hosking@fdean.gov.uk

  30. jimmy says:

    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.

  31. Graham Hosking says:

    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"

  32. Yahya says:

    Hi

    I am getting problem installing 2007 routing agent; social.technet.microsoft.com/…/da9fc6bc-8449-42b0-a23f-0c8744b688c0

    Regards

  33. gad says:

    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

  34. gad says:

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

  35. alex says:

    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 🙂

  36. MTuescher says:

    Hi All

    I enhanced Grahams code so that it is externally configurable:

    http://www.tuescher.net/…/index.html

    Regards,

    Martin

  37. Matty-uk says:

    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

  38. Kalanithi says:

    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

Skip to main content