Click here to Skip to main content
16,020,188 members
Articles / Programming Languages / XML
Article

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

Rate me:
Please Sign up or sign in to vote.
4.68/5 (6 votes)
29 Nov 20069 min read 74K   802   38   5
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:

SwitchValueNotes
-?Display the command line switches. If used, it must be the only parameter.
-hHost name, i.e., address of the SMTP server
-oPort on the SMTP serverDefaults to 25 if not specified.
-f"From" addressE.g., someone@somewhere.com or "someone@somewhere.com|Someone Else".
-r"Reply to" addressThe address where replies to the sent e-mail will be sent to (if this is different from the "From" address).
-t"To" address/esMultiple addresses can be specified, separated by semi-colons. E.g., "someone@somewhere.com;jsoap@nowhere.com|Joe Soap".
-c"cc" address/esCan contain multiple semi-colon delimited addresses.
-d"bcc" address/esCan contain multiple semi-colon delimited addresses.
-sName of the file containing the subject lineIgnored if the -S switch is set.
-SThe subject line
-bName of the file containing the text body of the e-mailIgnored if the -B switch is set.
-BThe body of the e-mail as a text string
-wName of the file containing the HTML body of the e-mailIgnored if the -W switch is set. Added as an alternate view if the -b or -B switch is also used.
-WThe body of the e-mail as an HTML stringAdded as an alternate view if the -b or -B switch is also used.
-aAttachment file nameThe 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.
-iImportance (i.e., priority)1 or "H" or "High", then 2 or "N" or "Normal", then 3 or "L" or "Low".
-eError log file nameErrors 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:

XML
<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:

C#
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...

C#
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:

C#
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():

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

... replaced ...

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

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

C#
ParseArguments(args, options)

... became ...

C#
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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Architect Dariel Solutions
South Africa South Africa
Andrew Tweddle started his career as an Operations Researcher, but made the switch to programming in 1997. His current programming passions are Powershell and WPF.

He has worked for one of the "big 4" banks in South Africa as a software team lead and an architect, at a Dynamics CRM consultancy and is currently an architect at Dariel Solutions working on software for a leading private hospital network.

Before that he spent 7 years at SQR Software in Pietermaritzburg, where he was responsible for the resource planning and budgeting module in CanePro, their flagship product for the sugar industry.

He enjoys writing utilities to streamline the software development and deployment process. He believes Powershell is a killer app for doing this.

Andrew is a board game geek (see www.boardgamegeek.com) with a collection of over 190 games! He also enjoys digital photography, camping and solving puzzles - especially Mathematics problems.

His Myers-Briggs personality profile is INTJ.

He lives with his wife, Claire and his daughters Lauren and Catherine in Johannesburg, South Africa.

Comments and Discussions

 
QuestionError while sending Pin
Amish Jariwala31-Mar-12 1:50
Amish Jariwala31-Mar-12 1:50 
QuestionSlight bug Pin
illiniguy8-Nov-11 11:15
illiniguy8-Nov-11 11:15 
GeneralGreat and Free Pin
SoylentGreenIP18-Mar-10 8:24
SoylentGreenIP18-Mar-10 8:24 
GeneralThis is what I am looking for! Pin
lexa_k17-Sep-08 1:11
lexa_k17-Sep-08 1:11 
GeneralThis is what I am looking for! Pin
lexa_k17-Sep-08 1:11
lexa_k17-Sep-08 1:11 
Thanx! I am going to use this program in my server!
I was looking for UNIX-like sendmail (with docs and examples of usages), but - I can't beleive - it's hard to find it for Windows! Functionality of the program and help message look excellent.
---
Compiled, set up the config and it works!

<configuration>
<system.net>
<mailsettings>
<smtp deliverymethod="Network">
<network>
host="xxx"
port="25"
userName ="xxx"
password ="xxx"
/>





Thank you, Andrew!

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.