This blog post follows a session that I delivered at the MEC 2012 conference in Orlando. If you attended the conference the slides are available on http://mymec.mecisback.com for the rest of 2012.
Part of the transport agents session was writing a new transport agent, and the example agent was to do add a form of catch-all functionality. The example code below (which purposefully needs editing to work, as its designed to be a learning tool) takes an email in the form of firstname.lastname@example.org where the final recipient is email@example.com and the subject line is changed to read [tag] Original Subject. Therefore email addresses can be given out that are an adjusted form of your real address, and on receipt of the email the correct recipient address is determined in code (the value before the _ appended to the domain name) so that you do not need to add lots of aliases to your user account in Active Directory. The subject is then changed to indicate the email came from this alternative original address. For example, an email to firstname.lastname@example.org would go to email@example.com and have [linkedin] added to the start of the subject.
As this partial catch all transport agent needs access to the recipient information and the subject, the agent needs to be bound to OnEndOfHeaders or OnEndOfData in the SMTP stack. It could also be bound to OnSubmittedMessage as a routing agent (rather than an SMTP agent) but could not be bound to OnResolvedMessage as recipient resolution has already happened by this point.
Writing the Transport Agent
To create the transport agent you need Visual Studio – the basic version of Visual Studio are sufficient and you need to copy the two DLL’s from Program Files\Microsoft\Exchange Server\V14\Public (or the V15 folder for Exchange 2013) to a folder on your development machine. You need a copy of these DLL’s for every version/service pack/update rollup of Exchange that you will run your agent on, as the DLL’s might change between these versions, and if you have the wrong version you will not be able to install your agent on a new server or if the server is updated, the transport service will fail as it will not be able to load the agent.
In Visual Studio, create a .NET 2 or 3.5 Class Library (for Visual Basic if using the below code) for Exchange 2010 or .NET 4.0 Class Library for Exchange 2013. For the project name enter something descriptive for what you are going to create (rather then ClassLibrary1). For example MECDemoPartialCatchAll.
Copy and paste the following code into the class.vb file, replacing the template text.
Imports System Imports System.Collections.Generic Imports System.Text Imports Microsoft.Exchange.Data.Transport Imports Microsoft.Exchange.Data.Transport.Smtp Namespace XXXXX REM Change This NotInheritable Class YYYYYY REM Change This Inherits SmtpReceiveAgentFactory Public Overrides Function CreateAgent(ByVal server As SmtpServer) As SmtpReceiveAgent Return New ZZZZZ REM Change This End Function End Class Public Class ZZZZZ REM Change This Inherits SmtpReceiveAgent Private Sub MyEndOfDataHandler(ByVal source As ReceiveMessageEventSource, ByVal e As EndOfDataEventArgs) Handles Me REM Change This ' Get and change the recipient from alias_tag to alias (only doing for 1 recipient for simplicity) If e.MailItem.Recipients.Count = 1 And InStr(e.MailItem.Recipients.Item(0).Address, "_") > 1 Then Dim Recipient() As String = Split(e.MailItem.Recipients.Item(0).Address, "@", 2) Dim EmailAlias() As String = Split(Recipient(0), "_", 2) 'EmailAlias(0) = alias to send email to 'EmailAlias(1) = tag for subject line 'Recipient(1) = domain ' The following line prepends [tag] to the subject of the message. REM Change This "[" + EmailAlias(1).ToString + "] " + e.MailItem.Message.Subject 'the following drops the current recipient e.MailItem.Recipients.Remove(e.MailItem.Recipients.Item(0).Address) 'the following adds the recipient back again, this time using the alias without the tag e.MailItem.Recipients.Add(New RoutingAddress(EmailAlias(0).ToString + "@" + Recipient(1).ToString)) End If End Sub End Class End Namespace
The remaining steps on creating the agent will be to modify the lines above that are marked with REM statements and then to add the reference DLL’s you copied from your Exchange Server and finally build your DLL.
Visual Studio can tell you when your code contains errors before you build your code, but to do so you need to reference the DLL’s that you obtained from your Exchange Server earlier in this blog. To do this you need to have copied the two DLL’s to a unique folder on your computer. I use c:\temp\Agent Authoring\Dll\E14-SP2-RU4 to store the DLL’s from Exchange 2010, SP2, RU4. Then when RU5 is released, if the DLL’s have changed I will place them in an E14-SP2-RU5 folder and update the references in my project. If I keep using the same folder then Visual Studio does not refresh the DLL but uses an existing cached copy.
To add the DLL’s right-click the project name to the right of the Visual Studio window and choose Properties (or press Alt+Enter):
Change to the Reference’s tab and click Add > Browse to find and add these two DLL’s.
You should see the two DLL’s listed as well as the default references for DLL’s included in the class library template that you used in Visual Studio.
Back on the code, its time to make the changes. Each of the changes and why it is needed are detailed based on the line numbers above
Line 7 – Namespace
This value of the namespace for the transport agent. Standard conventions indicate that this should be your company name. Therefore in your code change XXXXX to C7Solutions
Line 9 – NotInheritable Class
This value, YYYYYY, is the class that you are creating. This class inherits all the functionality of the Microsoft.Exchange.Data.Transport.Smtp.SmtpReceiveAgentFactory class. For this partial catch-all agent, a good name would be PartialCatchAllFactory.
Line 13 and 18 – Public Class
The Public Class contains the code to execute when the agent is called. This class has a name (ZZZZZ in the sample code above) and should be given a name that represents what the code does. This name in this example can be PartialCatchAll and it needs to be used in Line 13 (inside the PartialCatchAllFactory code to indicate the code to call instead of SmtpReceiveAgentFactory. And of course it is needed on line 18 to name the actual block of code. The name to use for this example will be PartialCatchAll
Line 21 – Handler
This line is missing the end of the code. Its a line that says run the code in this subroutine if the OnEndOfData event is called. Change the end of the line to read Me.OnEndOfData. This value should be one of the suggestions in the drop down list if you have done everything correct so far:
This subroutine also returns e as a pointer to the email message (i.e. you can modify e.MailItem.DeliveryPriority and many other properties) and source as a reference to the SMTP connection (i.e. with source.Disconnect you would close the SMTP session).
In this code lines 34 and 37 change the recipient. Line 34 removes the first recipient and then line 37 adds a new recipient that matches the alias + the domain.
Line 31 – Modify Subject
Finally, this code is missing the start of line 31. You need to enter e.MailItem.Message.Subject = at the start of the line so that they subject becomes [tag] + original subject.
Building the Transport Agent DLL
The Errors List at the bottom of the Visual Studio screen should be empty by now. So time to build the project. If you have not saved this project in Visual Studio, the DLL will be created in the %temp% directory, so best recommendation is to save the project before building the DLL.
To save the project click File > Save All and enter a suitable name for the project.
The project name becomes the solution name by default and then click Save.
The final thing to check before you build the DLL is the Root namespace value. This should be set to the name of the class library and can be set during creation of the library. If you have written your library in VB.Net then you will need this value to register the DLL. If you used C# then you will not need this value. As we have used VB.Net above, we need to check the root namespace value. This can be found on the project properties page.
If your DLL is ready to release (i.e. you have build and tested it already) then choose Build > Configuration Manager menu. Change the Active solution configuration to Release and click Close. If you do not make this change then the DLL will be created in the project_name\bin\debug folder and its possible that Visual Studio does not compile all the optimizations to the code that it can. For production items that perform at as good a speed as you can write optimal code, you should change this to Release for the Configuration value. The DLL will be created in project_folder\bin\release.
To build your DLL (debug or release) select the Build > Build RootNamespaceValue menu. This will create a DLL in project_name\bin\debug or project_name\bin\release. A working version for Exchange 2010 SP2, RU4 can be downloaded from here.
Installing the Transport Agent
Copy the DLL to all your Exchange Servers that have the hub transport role installed on them (or if this should only apply to emails inbound from the internet, then just the servers that are listed on your MX records or the first servers connected to for inbound emails). Place the file in a directory (say C:\C7Agents) and run the following five lines from Exchange Management Shell:
Install-TransportAgent -Name “C7PartialCatchAll” -AssemblyPath “C:\C7Agents\MECDemoPartialCatchAll.dll”
# Recommend closing EMS window now
Note that this restarts the transport service (required) and IIS (which will effect OWA and other CAS roles on the same machine). Remote Powershell (an IIS resource) will lock the DLL open, and so if you need to delete the DLL after uninstalling it, you need to have reset IIS and closed the Powershell window.
Finally, send an email to firstname.lastname@example.org where name is the part of your email address before the @ in Exchange and yourdomain.com is your domain. The value in “value” will be added to the subject as [value]. You get get an email in this form you know your agent has worked.
Transport Agents and Exchange 2013
I’ve also written an additional blog post on the changes for Exchange Server 2013 and transport agents. This covers the things to consider that are different with regards to Exchange 2007/2010 and the new Exchange.