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






4.68/5 (6 votes)
Nov 29, 2006
9 min read

74037

802
A command line utility which sends e-mails using the SmtpClient class.
Contents
- Introduction
- Background
- Using the code
- Alternative designs
- Introduction
- Design 1: Everything in Program.cs
- Design 2: Moving command line option variables into their own class
- Design 3: CommandLineOptions becomes MessageMailer
- Design 4: Move MailMessage and SmtpClient out of CommandLineOptions
- Design 5: Duplicating the properties of MailMessage and SmtpClient in CommandLineOptions
- My final choice
- History
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...
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.