65.9K
CodeProject is changing. Read more.
Home

SendSmtp - a command line utility to send e-mails via SMTP

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.68/5 (6 votes)

Nov 29, 2006

9 min read

viewsIcon

74037

downloadIcon

802

A command line utility which sends e-mails using the SmtpClient class.

Contents

Introduction

SendSmtp is a tiny command line utility which uses .NET's SmtpClient class to send e-mails.

There is another very similar utility on CodeProject, Mike Ellison's Command Line EMailer.

SendSmtp distinguishes itself from Mike's utility by allowing an HTML page to be sent as the body of the e-mail. It also uses the System.Net.Mail namespace, rather than System.Web.Mail (which has been deprecated in .NET 2.0).

SendSmtp is the last of three command line utilities which were written as building blocks to create a SQL error reporting facility for the daily build process at work. I will be posting a fourth article shortly which will demonstrate and discuss this error reporting process in greater detail.

Background

Some time back, a colleague created a daily build process using FinalBuilder. As well as building the executable, it also tested the SQL deployment scripts on a recent copy of each customer's database, and saved the results to an output file for each database. This created a need to extract error messages from these SQL output files. Ideally, these errors should only be e-mailed to the developers who had created the scripts with the errors.

My solution was to write three general purpose command line utilities, then use these to generate and e-mail a personalized error report to each affected developer.

The three utilities are:

  • RegexToXml: parses the SQL output files to generate a separate XML file of errors and warnings for each database.
  • TransformXml: applies various XSL transforms to convert these XML files into an HTML file per developer, containing the SQL errors in that developer's scripts (and grouped by database).
  • SendSMTP: e-mails these HTML files, as the body of the e-mail, to each affected developer.

I have now posted separate articles on each of these utilities. A final article will be posted shortly, which will demonstrate the entire error reporting process in action.

Using the code

Structure of the application

SendSmtp has been written as an N-tier application, where N < PI / 3.

Here's the class diagram...

Class diagram for SendSmtp

Command line switches

The command line utility uses a hyphen followed by a single character to specify a command line switch. The related setting can either be appended to the switch, or passed as the next command line argument. For example:

-fsomeone@somewhere.com

or

-f someone@somewhere.com

To view the list of command line options, run SendSmtp without any parameters or with the -? switch. Below is a list of all the command line switches:

Switch Value Notes
-? Display the command line switches. If used, it must be the only parameter.
-h Host name, i.e., address of the SMTP server
-o Port on the SMTP server Defaults to 25 if not specified.
-f "From" address E.g., someone@somewhere.com or "someone@somewhere.com|Someone Else".
-r "Reply to" address The address where replies to the sent e-mail will be sent to (if this is different from the "From" address).
-t "To" address/es Multiple addresses can be specified, separated by semi-colons. E.g., "someone@somewhere.com;jsoap@nowhere.com|Joe Soap".
-c "cc" address/es Can contain multiple semi-colon delimited addresses.
-d "bcc" address/es Can contain multiple semi-colon delimited addresses.
-s Name of the file containing the subject line Ignored if the -S switch is set.
-S The subject line
-b Name of the file containing the text body of the e-mail Ignored if the -B switch is set.
-B The body of the e-mail as a text string
-w Name of the file containing the HTML body of the e-mail Ignored if the -W switch is set. Added as an alternate view if the -b or -B switch is also used.
-W The body of the e-mail as an HTML string Added as an alternate view if the -b or -B switch is also used.
-a Attachment file name The name of a file to add as an attachment. This switch can be used multiple times to add more than one file as an attachment.
-i Importance (i.e., priority) 1 or "H" or "High", then 2 or "N" or "Normal", then 3 or "L" or "Low".
-e Error log file name Errors are always written to the standard error stream. With this parameter set, they will also be written to this file. NB: The contents of the file will be overwritten.
-v + or - Verbose mode. On, by default. Shows extra progress information.
-p + or - Prompt to exit. Off by default. This is useful when running the utility in debug mode, as it gives you a chance to see the results before the console window disappears.

Customizing the config file

Your SMTP host might require additional parameters, such as a user name and password. SendSmtp doesn't have switches to support these parameters directly.

What you can do instead is to create a config file, SendSmtp.exe.config, and add a section similar to the following:

<configuration>
  <system.net>
    <mailSettings>
      <smtp ...>
        <network>
          ...
        </network>
      </smtp>
    </mailSettings>
  </system.net>
</configuration>

You can then customize this section to set those extra parameters.

For more information on this, look up the mailSettings element, SmtpSection, and SmtpNetworkElement in the MSDN library.

Alternative designs

Introduction

One of the significant advantages of submitting code to CodeProject is that it inspires one to look at one's code far more critically. So, I'd like to discuss some of the alternative designs I considered, and why I chose the design I did.

Design 1: Everything in Program.cs

Initially, all the code was in Program.cs. Separate local variables were declared for each of the command line options. As a result, most of the code was in the Main() method.

This was very quick, very dirty, and totally unacceptable! I soon set about refactoring it.

Design 2: Moving command line option variables into their own class

I then created a separate CommandLineOptions class, and moved most of the local variables into this class (including SmtpClient client and MailMessage message.)

This made it much easier to refactor the large Program.Main() method into a variety of smaller, more focused methods. Each of these methods could be passed a CommandLineOptions options parameter, rather than a long list of parameters for each of the individual variables.

This made the code far more readable, but there were a few things that were still bothering me...

CommandLineOptions was exposing public properties of type MailMessage and SmtpClient:

    class CommandLineOptions
    {
        private SmtpClient client = new SmtpClient();
        private MailMessage message = new MailMessage();
        ...
        
        public SmtpClient Client
        {
            get { return client; }
        }

        public MailMessage Message
        {
            get { return message; }
        }
        ...
    }

The Progam class then sets properties of CommandLineOptions.Client and CommandLineOptions.Message directly...

        private static void ApplySettingToOptions(
            CommandLineOptions options, 
            char currSwitch, string setting)
        {
            switch (currSwitch)
            {
                case 'h':
                    options.Client.Host = setting;
                    break;

                case 'o':
                    options.Client.Port = int.Parse(setting);
                    break;

                case 'f':
                    options.Message.From 
                        = ParseAddressAndDisplayName(setting);
                    break;

                case 't':
                    foreach (MailAddress address
                        in ParseAddresses(setting))
                    {
                        options.Message.To.Add(address);
                    }
                    break;
                
                ...

I was a bit unhappy with this design, as I felt that the instances of SmtpClient and MailMessage should be encapsulated better.

Additionally, although the SmptClient and MailMessage classes contain properties which correspond to command line options, they don't strictly fit the definition of "command line options" themselves. This felt slightly wrong.

These are fairly minor quibbles in such a small application, and in other circumstances, I would probably have ignored them. But for learning purposes, I felt it would be worthwhile to investigate some alternate designs.

Design 3: CommandLineOptions becomes MessageMailer

One solution to this encapsulation problem is to remove the public accessor properties which expose the MailMessage and SmtpClient instances.

For each property of these classes which I wished to expose, I created a corresponding property of CommandLineOptions, which was simply a wrapper around the same property of the encapsulated class:

    class CommandLineOptions
    {
        private SmtpClient client = new SmtpClient();
        private MailMessage message = new MailMessage();
        ...
        
        public string Host
        {
            get { return client.Host; }
            set { client.Host = value; }
        }

        public int Port
        {
            get { return client.Port; }
            set { client.Port = value; }
        }

        public MailAddress From
        {
            get { return message.From; }
            set { message.From = value; }
        }

        public MailAddress ReplyTo
        {
            get { return message.ReplyTo; }
            set { message.ReplyTo = value; }
        }

        public MailAddressCollection To
        {
            get { return message.To; }
        }

        public MailAddressCollection CC
        {
            get { return message.CC; }
        }
        ...

This largely addresses the encapsulation issue. But it creates a new problem.

The MailMessage and SmtpClient instances are no longer accessible, so the Program class can no longer use them to send messages. Hence, the responsibility for sending the e-mails must be delegated to the CommandLineOptions class, since it alone has access to these private instance variables.

With this refactoring, about half the methods of the Program class can be moved to the CommandLineOptions class.

Since the responsibilities of the class have changed, its title should also be changed (and I guess it deserves a salary increase too!). I chose to rename the class to "MessageMailer".

In one sense, this felt like the right design, as there was a more even spread of responsibilities between the two classes.

However, in terms of its changed responsibilities, it made no sense for MessageMailer to have properties like VerboseMode, PromptToExit, and ErrorFileName. So, I had to move these variables back into Program.Main():

        static void Main(string[] args)
        {
            bool verboseMode = true;
            bool promptToExit = false;
            string errorFileName = null;
            MessageMailer mailer = new MessageMailer();
            ...

... replaced ...

        static void Main(string[] args)
        {
            CommandLineOptions options = new CommandLineOptions();
            ...

Now, the code was looking messier again. For example, the call to:

ParseArguments(args, options)

... became ...

ParseArguments(args, mailer, out errorFileName, 
               out verboseMode, out promptToExit)

While ref and out parameters have their place, I prefer not to use them where possible. [This is because it's often unclear how the parameter has been changed, so one is often forced to step into the method, rather than just over it, when scanning through the calling code.]

So now, it felt like I was once again needing a class whose responsibility would be to store the command line options.

By this point, I was getting quite frustrated. SendSmtp is a small stand-alone program. This alone makes it very easy to maintain. So it just wasn't economical to spend this much time on getting the "perfect" design!

Design 4: Move MailMessage and SmtpClient out of CommandLineOptions

It occurred to me that with design 3, I was being forced to move variables like verboseMode and promptToExit out of the former CommandLineOptions class.

Perhaps the better alternative was to move the MailMessage and SmtpClient classes out of CommandLineOptions instead.

After all, these are significant classes in their own right, and it makes far more sense for them to lead a separate existence than to make an orphan of verboseMode and promptToExit.

So instead of:

ParseArguments(args, options)

or

ParseArguments(args, mailer, out errorFileName, 
               out verboseMode, out promptToExit)

I ended up with...

ParseArguments(args, options, client, message)

This felt a lot better than the previous design!

Design 5: Duplicating the properties of MailMessage and SmtpClient in CommandLineOptions

The purest design would probably be to give CommandLineOptions its own separate private variables and public properties for each relevant property of MailMessage or SmtpClient.

There would then be no need to encapsulate MailMessage and SmtpClient. And there would be no need to pass them as parameters between the methods of the Program class, because they could be instantiated just prior to sending the message.

I was almost ready to settle on this design, when I remembered that the default constructor for the SmtpClient class initializes some of its properties (such as Host and Port) from the <mailSettings> element of the <system.net> section of the configuration file. By storing my own version of these properties in the CommandLineOptions file, I would be subverting this mechanism.

My final choice

Because of the technical consideration discussed above, the "pure" design 5 was not an option. I felt that design 4 was the best remaining choice.

History

28 November 2006

  • Initial version submitted.