Monday, January 26, 2009

From Address Rewriting in a Transport Agent for a Exchange 2007 Hub Server

The ability to rewrite the from address of emails can be exceedingly useful if you are working with consolidating or migrating email systems especially if you have two (or more) companies merging or you're trying to consolidate disparate email system across orgs. Exchange 2007 has a great address rewriting agent that runs on a Edge server which is explained in http://technet.microsoft.com/en-us/library/aa996806.aspx. If you aren't implementing an Edge server as part of your Exchange org then unfortunately you cant use this agent on a Hub server. One solution to this is that you write your own agent to do this which is what this post is about. Now that link I just pointed to has some very important information about the fields you should and shouldn't rewrite in an address rewriting Agent and the reason you shouldn't rewrite particular fields. The Return-Path is probably the one of most note and maybe the one people will tend to want to rewrite because of the visibility of the original address in the header and possible SPAM detection issues where the return-path will be different to the From address etc. But it is probably best to leave it as someone has probably done a lot more testing then you at this and it best not to rewrite it the longer story I'm sure is a lot more complicated.

Envelope From (MAIL FROM)


This field can be known more commonly as one of the P1 headers and is available in a Transport event as the mailitem.FromAddress property. To rewrite this address you need to use the RoutingAddress type e.g

e.MailItem.FromAddress = new RoutingAddress("localPart", "newFromDomain");

Body From and Sender

These fields are part of the P2 headers and are exposed via the EmailMessageClass accessable via the MailItem.Message property. Eg to rewrite these properties

e.MailItem.Message.From.SmtpAddress = localPart + "@" + newFromDomain;
e.MailItem.Message.Sender.SmtpAddress = localPart + "@" + newFromDomain;


Body Reply-To

Because there can be more then one address within the Reply-To property you need some code that will loop through the recipients collection and re-map address's as nessasary. e.g

foreach (EmailRecipient rpReplyTo in e.MailItem.Message.ReplyTo) {
if (rpReplyTo.SmtpAddress != null)
{
String rtSourceAddress = rpReplyTo.SmtpAddress.ToString();
String[] rtSourceArray = rtSourceAddress.Split('@');
if (rtSourceArray[1].ToString().ToLower() == oldFromDomain) {
rpReplyTo.SmtpAddress = rtSourceArray[0].ToString() + "@" + newFromDomain;
}
}

Disposition-Notification-To and Return-Receipt-To

These two properties represent Read Recipient address's the reason there are two is a little complicated and has a little to do with history but the one that relates to the RFC and is the standard property is Disposition-Notification-To which is what is exposed by the EmailMessageClass and can be rewritten with something like.


EmailRecipient dnDispNotice = e.MailItem.Message.DispositionNotificationTo;
String dnSourceAddress = dnDispNotice.SmtpAddress.ToString();
String[] dnSourceArray = dnSourceAddress.Split('@');
if (dnSourceArray[1].ToString().ToLower() == oldFromDomain)
{
dnDispNotice.SmtpAddress = dnSourceArray[0].ToString() + "@" + newFromDomain;
}

The second header Return-Receipt-To which is non standard is a little trickier this isn't exposed as a property by the EmailMessageClass but can be accessed as an AddressHeader when looping through the MIME headers. Rewriting this field is difficult in a Transport agent and the only method I've found that works is to use the MIME writer. To simplify my transport agent i took what might prove to be the wrong approach of just deleting this header. I took the view point that its a redundant header anyway and any compliant client should support the standard DispositionNotificationTo header. To delete the header you can use code like.


HeaderList hlHeaderlist = e.MailItem.Message.RootPart.Headers;
AddressHeader mhRrecp = (AddressHeader)hlHeaderlist.FindFirst("Return-Receipt-To");
hlHeaderlist.RemoveChild(mhRrecp);

If you are looking for some code to read an AddressHeader Email value which you wont be able to do by just looping through the headerlist like normal Text headers you need to use some code that looks like.

Stream messageStream = e.MailItem.GetMimeReadStream();
MimeReader mrMimeReader = new MimeReader(e.MailItem.GetMimeReadStream());
mrMimeReader.ReadFirstChildPart();
mrMimeReader.EnableReadingUnparsedHeaders();
while (mrMimeReader.HeaderReader.ReadNextHeader()) {
if (mrMimeReader.HeaderReader.Name.ToString() == "Return-Receipt-To") {
MimeAddressReader reRecp = mrMimeReader.HeaderReader.AddressReader;

};
}

Im nore sure if this is the best method but the documentation for the MIMEReader class is very limited.

Thats it while its not 100% hopefully it will help if your trying to write your own address rewriting Transport Agent.

4 comments:

Florian said...

"Im nore sure if this is the best method but the documentation for the MIMEReader class is very limited."

As it is for the whole TransportAgent/RoutingAgent thingy...
So, thanks for sharing your knowledge about this.

Kriss said...

Hello Glen,
Reading your post, I tried to add in my TransPort agent the proper Header to "activate" the "Return-Receipt-To" feature.
Unfortunately i have an error saying

[MyRoutingAgent_OnSubmittedMessage - Error] : 5 , Nom d'en-tête non valide « Return-Receipt-To: » au niveau du caractère #17

The 17th char is just after the "Return-Receipt-To"

So it seems that i can't add 'Return-Receipt-To' as an addressHeader.
Do i have to use an other Header Class like, ComplexHeader,AsciiTextHeader,ContentDispositionHeader or something else

Here is the code used inside MyRoutingAgent_OnSubmittedMessage:

Dim MyAddressHeader As AddressHeader = New AddressHeader("Return-Receipt-To: <" & e.MailItem.FromAddress.ToString & ">")
Dim lhLasterHeader As MimeNode = e.MailItem.Message.RootPart.Headers.LastChild
e.MailItem.Message.RootPart.Headers.InsertBefore(MyAddressHeader, lhLasterHeader)

Just for information, i also implement e.MailItem.Message.DispositionNotificationTo and it works well.

Kind regards
Kriss

Patrick said...

Just a tip to anyone else trying to do this:
In Exchange 2010, I had to use the OnCategorizedMessage handler before the From address would actually change as expected.

Thanks for this great post, though, Glen! It definitely pointed me in the right direction.

Anonymous said...

hi Glen.
MIMEWriter class is poor documented !!
your example is near the only available
there is info on usage of this class?
thanks on advance

giordano contigiani
Spaziosoft srl