Geeks With Blogs
blog.davidbarrett.net ///<summary>Just a lot of random technical stuff, really.</summary>

I needed to get TFS 2008 (both the server alerts and TFS web access) to send emails over a secure SMTP relay.  "Easy", I thought.  I remembered setting up SMTP username/password during the web access setup, at least, so I'll verify settings, and then it should just work.

I couldn't be more wrong.  Through peeling back the covers and discovery, I found myself saddened at the SMTP implementation in TFS.  I can only hope that this is changed in a future service pack for TFS 2008, or at the very least, Rosario.  Let's review.

SMTP Configuration in TFS Server

I found the SMTP configuration for TFS alerts at C:\Program Files\Microsoft Visual Studio 2008 Team Foundation Server\Web Services\Services\Web.config.  It looks something like:

   1: <appSettings>
   2:   <add key="ConnectionString" value="Application Name=TeamFoundation;Persist Security Info=False;Initial Catalog=TfsIntegration;Data Source=TFS2008;Integrated Security=SSPI"/>    
   3:   <add key="eventingEnabled" value="true" />
   4:   <add key="DetailedExceptions" value="false" />
   5:   <add key="emailNotificationFromAddress" value="tfs2008@mydomain.com" />
   6:   <add key="smtpServer" value="mySmtpServer" />
   7:  
   8:   <!-- Optional Alert Settings
   9:        AlertUseReplyTo: True, False (default: True). Specifies that the ReplyTo property of the 
  10:                    email alert should be set. When false, the From property is set.
  11:   <add key="AlertUseReplyTo" value="True" />
  12:   -->
  13: </appSettings>

Incidentally, this information is set during the TFS install.  If you choose not to enable alerts during the TFS install, this is where you enable it later.  Anyway, notice there's only a from address and a server name.  Where's the configuration option for SSL or authentication?  Hmmm.  More on this later.

SMTP Configuration in TFS Web Access

I found the SMTP configuration for TFS Web Access at C:\Program Files\Microsoft Visual Studio 2008 Team System Web Access\Web\web.config.  It looks something like:

   1: <webAccessSettings>
   2:  
   3:   <!-- Some settings removed for brevity. -->
   4:  
   5:   <!-- 
   6:   Specifies whether sending query results and work items as email is enabled.
   7:   Make sure to enable/disable mailSettings section in <system.net> below for the sender username and password
   8:   -->
   9:   <emailSettings sendingEmailEnabled="true" />
  10:  
  11: </webAccessSettings>
  12:  
  13: <!-- ** Email Settings ** -->
  14: <system.net>
  15:   <mailSettings>
  16:     <smtp deliveryMethod="network" from="fromaddress@domain.com">
  17:  
  18:       <!-- Use default credentials -->
  19:       <!--<network host="mail.example.com" port="25" defaultCredentials="true" />-->
  20:       
  21:       <!-- To specify a username and password, comment out the <network> section 
  22:       above, and uncomment the one below -->
  23:       <network host="smtpHost" port="25" defaultCredentials="false" userName="smtpUsername" password="smtpPassword" />
  24:     
  25:     </smtp>
  26:   </mailSettings>
  27: </system.net>

You'll notice a few things.  First, there is a custom section and node called <webAccessSettings\emailSettings> that defines whether sending of email via the web interface is allowed.  The notes in that section also indicate to set the properties under <system.net\mailSettings> .  Perfect!  I recognize this!  <mailSettings> is a configuration section used by the SmtpClient class in the .net framework.  Excellent.  Good.  At least the web access piece is using SmtpClient.

Experience

I tried sending email using both TFS Web Access and project alerts.  I got email from neither.  Hmm.  I checked the Event Viewer on the TFS server and saw warnings about the alerts failing to send email.  In Web Access, I got an immediate error via a javascript popup.  I tried using an internal secure relay.  Nada.  I tried using GMail's SMTP server with my own credentials.  Nada.  I tried using one of GoDaddy's SMTP servers with authentication information I know works.  Nada.  In all cases, I got SMTP 5.7.1 errors with varying texts, but all essentially pointed to:  Not Authenticated.

What's going on here?  I knew the relays worked, as I was able to use SmtpClient directly to send email.

I started reflecting the code, and that's where my stomach turned.

SMTP Implementation in TFS Server

I found the section that sends email in the TFS code.  It contains the following lines:

   1: SmtpClient client = new SmtpClient(s_smtpServer);
   2: client.UseDefaultCredentials = true;

The variable s_smtpServer originally comes from the AppSettings section shown above.  But what's this on line #2?  Setting UseDefaultCredentials = "true" instructs SmtpClient to use the thread identity's credentials to attempt to authenticate against the Smtp server.  That's fine if I'm using an internal Smtp server on a domain, but what if I need the flexibility to send to a server requiring basic authentication using a non-domain Id/password?  Too bad.  The code prevents me from doing so.  Simply unacceptable.

SMTP Implementation in TFS Web Access

I found several sections of code that work together to send email on behalf of a user connected to TFS Web Access:

 
   1: private void ProcessSendMail(NameValueCollection commandArguments)
   2: {
   3:     string from = commandArguments["from"];
   4:     string to = commandArguments["to"];
   5:     string subject = commandArguments["subject"];
   6:     this.SendMail(this.m_emailSettings.SmtpHost, this.m_emailSettings.SmtpUsername, this.m_emailSettings.SmtpPassword, from, to, subject, "<html><body style=\"font-family:Tahoma, Arial; font-size:70%;\">" + commandArguments["body"] + "</body></html>");
   7: }
   8:  
   9: private void SendMail(string host, string username, string password, string from, string to, string subject, string htmlBody)
  10: {
  11:     SmtpClient client;
  12:     MailMessage message = new MailMessage();
  13:     if (!string.IsNullOrEmpty(from))
  14:     {
  15:         message.From = new MailAddress(from);
  16:         message.ReplyTo = new MailAddress(from);
  17:     }
  18:     message.To.Add(to);
  19:     message.Subject = subject;
  20:     message.IsBodyHtml = true;
  21:     message.Body = htmlBody;
  22:     message.BodyEncoding = Encoding.UTF8;
  23:     if (!string.IsNullOrEmpty(host))
  24:     {
  25:         client = new SmtpClient(host);
  26:     }
  27:     else
  28:     {
  29:         client = new SmtpClient();
  30:     }
  31:     if (!string.IsNullOrEmpty(username))
  32:     {
  33:         client.Credentials = new NetworkCredential(username, password);
  34:     }
  35:     else
  36:     {
  37:         client.UseDefaultCredentials = true;
  38:     }
  39:     client.EnableSsl = WebAccessApplication.Settings.EmailSettings.EnableSsl;
  40:     try
  41:     {
  42:         client.Send(message);
  43:     }
  44:     catch (SmtpException exception)
  45:     {
  46:         if (exception.InnerException == null)
  47:         {
  48:             throw;
  49:         }
  50:         throw new SmtpException(exception.Message + " " + exception.InnerException.Message, exception.InnerException);
  51:     }
  52: }
  53:  

As you can see from tracing the code, UseDefaultCredentials gets set to "true" again if our username value is not populated, which is what appears to be happening here.  However, from the configuration above, we clearly are providing one (well, within the <network> node anyhow).  Ok, let's dig into this.m_emailSettings.SmtpUsername to see where it comes from.  It must be null/empty.  Turns out m_emailSettings is an instance of a custom configuration section named EmailSettingsElement:

   1: public class EmailSettingsElement : ConfigurationElement
   2: {
   3:     // Fields
   4:     private string m_smtpFrom;
   5:  
   6:     // Methods
   7:     public EmailSettingsElement();
   8:  
   9:     // Properties
  10:     [ConfigurationProperty("enableSsl", DefaultValue=false, IsRequired=false)]
  11:     public bool EnableSsl { get; }
  12:     [ConfigurationProperty("fromEmail", DefaultValue="", IsRequired=false)]
  13:     public string FromEmail { get; }
  14:     [ConfigurationProperty("sendingEmailEnabled", DefaultValue=false, IsRequired=true)]
  15:     public bool SendingEmailEnabled { get; }
  16:     public string SmtpFrom { get; }
  17:     [ConfigurationProperty("smtpHost", DefaultValue="", IsRequired=false)]
  18:     public string SmtpHost { get; }
  19:     [ConfigurationProperty("smtpPassword", DefaultValue="", IsRequired=false)]
  20:     public string SmtpPassword { get; }
  21:     [ConfigurationProperty("smtpUsername", DefaultValue="", IsRequired=false)]
  22:     public string SmtpUsername { get; }
  23: }
  24:  

Hold up a minute!   This looks somewhat familiar.  I have a portion of this element already in my config.  Check out the <webAccessSettings\emailSettings> element in our configuration above.  Looks like the sendingEmailEnabled attribute maps to the SendingEmailEnabled property here.  So, let me see.  You're telling me that there are other properties on this node that aren't even populated?  Keep in mind this config file was generated from the install.  There are no comments in the config that there are other options available for this node.  And you'll remember that the instructions accompanying this node POINTED US to the <mailSettings> node for authentication information.  Yet the code checks non-existent attributes for authentication information.  Fantastic!

So maybe there's some obscure documentation somewhere on this EmailSettingsElement.  I check Google.  It's not often that a search comes up with absolutely nothing.

Well, that's OK, I'll populate the correct attributes and see what happens.  Here's my updated config:

   1: <webAccessSettings>
   2:  
   3:   <!-- Some settings removed for brevity. -->
   4:  
   5:   <!-- 
   6:   Specifies whether sending query results and work items as email is enabled.
   7:   Make sure to enable/disable mailSettings section in <system.net> below for the sender username and password <<-- WRONG!
   8:   -->
   9:   <emailSettings sendingEmailEnabled="true" enableSsl="false" fromEmail="fromAddress" smtpHost="smtpServer" smtpUsername="smtpUser" smtpPassword="password" />
  10:  
  11: </webAccessSettings>
  12:  
  13: <!-- ** Email Settings ** -->
  14: <system.net>
  15:   <mailSettings>
  16:     <smtp deliveryMethod="network">
  17:       <network port="25" />
  18:     </smtp>
  19:   </mailSettings>
  20: </system.net>

A couple items to note:  I've moved the attributes for secure/not secure, username, password, from address, and host to the emailSettings node.  You'll note that the port remains with the SmtpClient configuration as there isn't support for a non-standard port within the emailSettings node.  If you're using a non-standard port, you'll have to use the SmtpClient configuration section.

After making these configuration changes, TFS Web Access email works with a SMTP server requiring authentication.

Solution and Conclusion

Unfortunately, if you need to use basic non-domain authentication to an SMTP server from TFS Server, you're out of luck doing it directly.  You'll have to use an open relay instead to send directly or use an open relay in between TFS and the secure SMTP server.  Not a great solution, but it works.

For TFS Web Access, while the configuration is 100% undocumented, at least it's there and you can use it to get things working.  For some folks, TFS Web Access is installed on the same server as TFS itself, so you might as well just configure Web Access to use the same open relay as TFS as save yourself the configuration headache.

The vast majority of SMTP scenarios can be handled by SmtpClient using a configuration file.  Why the code was written to assume a particular scenario (domain authentication) is beyond me.  Also, why configuration for the SmtpClient is taken (mostly -- remember the port remains behind!) to another undocumented location baffles me.  SmtpClient works already -- why didn't the TFS devs simply use it as-is?  Frankly, it's unacceptable.  TFS 2008 is such an awesome platform; it's really a shame this important piece was so poorly implemented.

Agree or disagree?  Let me hear from you.

Posted on Friday, June 13, 2008 1:37 PM TFS , VSTS | Back to top


Comments on this post: Team Foundation Server (TFS) 2008 and Authenticated SMTP

# re: Team Foundation Server (TFS) 2008 and Authenticated SMTP - A Travesty
Requesting Gravatar...
A few workarounds, for what it's worth:

http://blogs.msdn.com/buckh/archive/2006/07/21/smtp-username-password.aspx

http://geekswithblogs.net/thibbard/archive/2006/12/13/UsingAuthenticatedSMTPServerForTFSEmailAlerts.aspx
Left by Richard Berg on Aug 20, 2008 5:35 PM

# re: Team Foundation Server (TFS) 2008 and Authenticated SMTP - A Travesty
Requesting Gravatar...
Great Help!!! Thanks.
Left by Pablo on Sep 02, 2008 9:04 AM

# re: Team Foundation Server (TFS) 2008 and Authenticated SMTP - A Travesty
Requesting Gravatar...
You rule.
Left by LoRez on Oct 16, 2008 1:52 PM

# re: Team Foundation Server (TFS) 2008 and Authenticated SMTP - A Travesty
Requesting Gravatar...
Yes, annoying beyond belief! Thanks
Left by Justin on Jun 13, 2009 7:41 AM

# re: Team Foundation Server (TFS) 2008 and Authenticated SMTP - A Travesty
Requesting Gravatar...
Thanks - worked perfect first time through - your right very annoying.
Left by Mark on Nov 03, 2009 11:38 AM

Your comment:
 (will show your gravatar)


Copyright © David Barrett | Powered by: GeeksWithBlogs.net