Share via


C#: Working with SMTP email (Part 1)

Introduction

This article, part one of a series will begin by showing how to setup the foundation for sending email messages with System.Net.SmtpClient and System.Net.MailMessage classes.  The first part which is covered is setting up an object capable of sending an email message. This object is created from the class SmtpClient.

Setting up a SmtpClient object is simple, create a new instance of SmtpClient, create a MailMessage object, provide a "From" and "To" address, a title and body for the message and invoked the Send method of the SmtpClient object. Unfortunately more is required the majority of the time.

Part 2 of the series
https://social.technet.microsoft.com/wiki/contents/articles/51936.c-working-with-smtp-email-part-2.aspx

SmtpClient issues

Back to top

If the Host property is not setup properly an email cannot be sent. If the Host is correct but the Port is incorrect, an email may be sent but the server (the host) may refuse the request to accept the email operation.

If the Credentials are not setup properly an unauthorized runtime exception will be thrown. A Host may require a username and password but you want to send an email without user intervention. This can be a tough one to handle but this topic will be covered with a working solution.

Some Host like Gmail will have security measures in place that must be setup by signing into your account and setting up permissions.

The Send method for sending email messages is synchronous which means your application is on hold until the intended message has been sent. This could be a second or two or perhaps a minute. 

These topics will be covered within a custom mail class that is easy to use in your solution along with other useful features which work in desktop and web solutions alike with unit testing.

Preparation for client setup

Back to top

Setting the foundation for working with SmtpClient which allows multiple configurations, for example, a primary configuration may be using Comcast as the Host while a secondary/backup configuration for Gmail.

The basics for setting the client is to provide a host and port. Depending on the environment the credentials and enableSsl may not need to be set while in other environments both properties may be required. Example, on a company network Active Directory will handle credentials for desktop or internal web solutions.

The following shows a raw example for setting up the parts to send an email using SMTP.

MailMessage mail = new  MailMessage();
//set the addresses
mail.From = new  MailAddress("me@mycompany.com");
mail.To.Add("you@yourcompany.com");
 
//set the content
mail.Subject = "This is an email";
mail.Body = "this is a sample body";
 
//send the message
SmtpClient smtp = new  SmtpClient("127.0.0.1");
smtp.Send(mail);

This may be all that is needed yet a developer may find themselves battling with various properties to get the setup of the SmtpClient correct.

mailSettings (Network Settings)

Back to top

The .NET Framework provides us with a way to setup properties which can be read by your code in app.config (desktop solutions) or web.config (for web solutions) under mailSettings. Note in web.config you can use transformations and have different settings for different environments (dev, test, and production). In the app.config example under Custom configuration class the pickup folder will not work for web solutions. Let's say you are using IIS, generally, the pickup folder location is under c:\Inetpub\mailroot\Pickup, you can use SmtpDeliveryMethod.PickupDirectoryFromIis to get the location (directly from IIS metabase) rather than hard-coding the path and this should be fully tested in a unit test.

Basic example

<system.net>
  <mailSettings>
   <smtp deliveryMethod="Network" from="smthg@smthg.net">
     <network defaultCredentials="false" host="localhost" port="587"
      userName="smthg@smthg.net" password=""/>
   </smtp>
  </mailSettings>
</system.net>

To read the above settings use

using System.Configuration;
using System.Net.Configuration;
// .
var smtpSection = (SmtpSection)ConfigurationManager.GetSection("system.net/mailSettings/smtp");
string username = smtpSection.Network.UserName;

The above code permits settings to be changed in the project configuration file rather than have setting in code which means rather than editing a configuration file to change setting you must alter the code, compile (optionally test) then place back into production.

Custom configuration class

Back to top

The following class is best placed in a class project as it's generic and can be used in all your solutions.

Create a new class project, in this case the name is SmtpMailConfiguration. Add a reference to the project for System.Configuration. Change Class1.cs to MailConfiguration and replace its contents with the following.

using System;
using System.Configuration;
using System.IO;
using System.Net.Configuration;
 
namespace SmtpMailConfiguration
{
  public class  MailConfiguration
  {
    private readonly  SmtpSection _smtpSection;
 
    /// <summary>
    /// Configure which setting to use for getting SMTP settings 
    /// from the configuration file.
    /// </summary>
    /// <param name="section"></param>
    public MailConfiguration(string section = "system.net/mailSettings/smtp")
    {
      _smtpSection = (ConfigurationManager.GetSection(section) as  SmtpSection);
    }
 
    /// <summary>
    /// Email address for the system
    /// </summary>
    public string  FromAddress => _smtpSection.From;
 
    #region Should be encrypted (will show how in part 2 of this series)
    public string  UserName => _smtpSection.Network.UserName;
    public string  Password => _smtpSection.Network.Password;
    #endregion
    /// <summary>
    /// Gets the system credentials of the application.
    /// </summary>
    public bool  DefaultCredentials => _smtpSection.Network.DefaultCredentials;
    /// <summary>
    /// Specify whether the SmtpClient uses Secure Sockets Layer (SSL) 
    /// to encrypt the connection.
    /// </summary>
    public bool  EnableSsl => _smtpSection.Network.EnableSsl;
    /// <summary>
    /// Gets or sets the folder where applications save mail messages to 
    /// be processed by the local SMTP server.
    /// </summary>
    public string  PickupFolder
    {
      get
      {
        string mailDrop = _smtpSection.SpecifiedPickupDirectory.PickupDirectoryLocation;
 
        if (mailDrop != null)
        {
          mailDrop = Path.Combine(
            AppDomain.CurrentDomain.BaseDirectory,
            _smtpSection.SpecifiedPickupDirectory.PickupDirectoryLocation);
        }
 
        return mailDrop;
      }
    }
    /// <summary>
    /// Determine if pickup folder exists
    /// </summary>
    /// <returns></returns>
    public bool  PickupFolderExists()
    {
      return Directory.Exists(PickupFolder);
    }
    /// <summary>
    /// Gets the name or IP address of the host used for SMTP transactions.
    /// </summary>
    public string  Host => _smtpSection.Network.Host;
 
    /// <summary>
    /// Gets the port used for SMTP transactions
    /// </summary>
    public int  Port => _smtpSection.Network.Port;
 
    /// <summary>
    /// Gets a value that specifies the amount of time after 
    /// which a synchronous Send call times out.
    /// </summary>
    public int  TimeOut => 2000;
 
    /// <summary>
    /// Allows, for debugging to review from address, host and port properties
    /// </summary>
    /// <returns>A string with main properties</returns>
    public override  string ToString() => $"From: [ {FromAddress} ]" +
                       $"Host: [{Host}] Port: [{Port}] "+ 
                       $"Pickup: {Directory.Exists(PickupFolder)}";
  }
}

Create a new unit test project followed by adding a new application configuration file (app.config).  Replace app.config contents with the following.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 <configSections>
  <sectionGroup name="mailSettings">
   <section name="smtp_1" type="System.Net.Configuration.SmtpSection"/>
  </sectionGroup>
 </configSections>
  
 <startup>
  <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
 </startup>
  
 <!-- 
  extra mail setting, main could be in here also.
  If all settings are here you should provide names that are meaningful
  rather than smtp_n where n is the index for the SMTP settings.
 -->
 <mailSettings>
  <smtp_1 from="someone@gmail.com">
   <network
    host="smtp.gmail.com"
    port="587"
    enableSsl="true"
    userName="MssGMail"
    password=""
    defaultCredentials="false" />
     
    <!-- 
     where to create emails for testing, could be a different folder then 
     the one in main but if so the Post Build event need to have another 
     macro for that folder.
    -->
    <specifiedPickupDirectory pickupDirectoryLocation="MailDrop"/>
 
  </smtp_1>
 
 </mailSettings>  
  
 
 <!-- main -->
 <system.net>
  <mailSettings>
   <smtp  from="Someone@comcast.net">
    <network
     host="smtp.comcast.net"
     port="587" 
     enableSsl="true"
     userName="MissComcast"
     password=""
     defaultCredentials="true" />
 
    <!-- 
     where to create emails for testing, could be a different folder then the one in smtp_1
     but if so the Post Build event need to have another macro for that folder.
    -->
    <specifiedPickupDirectory pickupDirectoryLocation="MailDrop"/>
 
   </smtp>
  </mailSettings>
 </system.net>
 
</configuration>

Add a reference to the project SmtpMailConfiguration. The next part will be used in the next part in the series. Open the property window for the unit test project, select the Build Events tab and insert the following into the "Post-build event command line". Each time your test project builds the code below will create a folder MailDrop under Bin\Debug if the folder does not exist. This allows you to test sending email messages without actually sending them but instead create .eml files. Although we will get into this later the basics are you can validate the MailMessage aspect of sending messages. Unfortunately, the .eml files generated for you are restrictive in reading content and verifying things like BCC. There are libraries on GitHub which can assist in this matter along with using HTML Agility pack. 

Post-build event command line

if not exist $(TargetDir)\MailDrop mkdir $(TargetDir)\MailDrop

In the default class created in the unit test project add the following test. using System;

using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SmtpMailConfiguration;
 
namespace smtpUnitTests
{
  [TestClass]
  public class  UnitTest1
  {
    protected string  GmailConfiguration = "mailSettings/smtp_1";
    protected string  ComcastConfiguration = "system.net/mailSettings/smtp";
 
    [TestMethod]
    public void  GMailFrom()
    {
      var mc = new  MailConfiguration(GmailConfiguration);
      Assert.IsTrue(mc.FromAddress == "someone@gmail.com", 
        "Wrong from address for gmail");
    }
    [TestMethod]
    public void  GMailUserName()
    {
      var mc = new  MailConfiguration(GmailConfiguration);
      Assert.IsTrue(mc.UserName == "MssGMail",
        "Wrong user name for gmail");
 
    }
    [TestMethod]
    public void  GMailHost()
    {
      var mc = new  MailConfiguration(GmailConfiguration);
      Assert.IsTrue(mc.Host == "smtp.gmail.com",
        "Wrong host for gmail");
    }
    [TestMethod]
    public void  GMailPort()
    {
      var mc = new  MailConfiguration(GmailConfiguration);
      Assert.IsTrue(mc.Port == 587,
        "Wrong port for gmail");
    }
    [TestMethod]
    public void  GMailEnableSsl()
    {
      var mc = new  MailConfiguration(GmailConfiguration);
      Assert.IsTrue(mc.EnableSsl,
        "Wrong EnableSsl for gmail");
    }
    [TestMethod]
    public void  GMailDefaultCredentials()
    {
      var mc = new  MailConfiguration(GmailConfiguration);
      Assert.IsTrue(mc.DefaultCredentials == false,
        "Wrong DefaultCredentials for gmail");
    }
    [TestMethod]
    public void  GMailPickupFolderExists()
    {
      var mc = new  MailConfiguration(GmailConfiguration);
      Assert.IsTrue(mc.PickupFolderExists(),
        "gmail pickup folder does not exists");
    }
    [TestMethod]
 
    public void  ComcastPickupFolderExists()
    {
      var mc = new  MailConfiguration(ComcastConfiguration);
      Assert.IsTrue(mc.PickupFolderExists(),
        "Comcast pickup folder does not exists");
    }
    [TestMethod]
    public void  ComcastFrom()
    {
      var mc = new  MailConfiguration(ComcastConfiguration);
      Assert.IsTrue(mc.FromAddress == "Someone@comcast.net",
        "Wrong from address for Comcast");
    }
    [TestMethod]
    public void  ComcastUserName()
    {
      var mc = new  MailConfiguration(ComcastConfiguration);
      Assert.IsTrue(mc.UserName == "MissComcast",
        "Wrong user name for Comcast");
 
    }
    [TestMethod]
    public void  ComcastHost()
    {
      var mc = new  MailConfiguration(ComcastConfiguration);
      Assert.IsTrue(mc.Host == "smtp.comcast.net",
        "Wrong host for Comcast");
    }
    [TestMethod]
    public void  ComcastPort()
    {
      var mc = new  MailConfiguration(ComcastConfiguration);
      Assert.IsTrue(mc.Port == 587,
        "Wrong port for Comcast");
    }
    [TestMethod]
    public void  ComcastEnableSsl()
    {
      var mc = new  MailConfiguration(ComcastConfiguration);
      Assert.IsTrue(mc.EnableSsl,
        "Wrong EnableSsl for Comcast");
    }
    [TestMethod]
    public void  ComcastDefaultCredentials()
    {
      var mc = new  MailConfiguration(ComcastConfiguration);
      Assert.IsTrue(mc.DefaultCredentials,
        "Wrong DefaultCredentials for Comcast");
    }
  }
}

At this point, you can run the test to ensure the proper setting are being returned for both Gmail and Comcast setting. Suppose you don't need multiple configurations. The following would be how your configuration file looks.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 <configSections>
  <sectionGroup name="mailSettings">
   <section name="smtp_1" type="System.Net.Configuration.SmtpSection"/>
  </sectionGroup>
 </configSections>
  
 <startup>
  <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
 </startup>
 
 <system.net>
  <mailSettings>
   <smtp  from="Someone@comcast.net">
    <network
     host="smtp.comcast.net"
     port="587" 
     enableSsl="true"
     userName="MissComcast"
     password=""
     defaultCredentials="true" />
    <specifiedPickupDirectory pickupDirectoryLocation="MailDrop"/>
   </smtp>
  </mailSettings>
 </system.net>
 
</configuration>

Then in MailConfiguration class, the constructor requires no parameter passed as the default will read the above settings. The next article will cover Setting up MailMessage in tangent with SmptClient to send email messages for both testing and live environment where the configuration class presented in this article will be used for unit testing your solution.

Requirements

Back to top

The code presented requires C# 6 or higher as several features of C# 6 are used but with small modifications, this code will work in prior versions of C#.

Summary

In the article, you have learned how to setup mail settings in your configuration file within a class which is reusable across multiple solutions and projects.

See also

C#: Working with SMTP email (Part 2)
SmptClient class, MailMessage class, MailSettingsSectionGroup class.

Source code

Back to top

Download