Command Line Emailer






4.93/5 (32 votes)
Sep 25, 2003
4 min read

156093

1472
A console application for sending email from the command line
Introduction
This article describes a .NET console application to send email using a command line. It shows methods for parsing command line arguments, loading and parsing a simple parameter file, and using the System.Web.Mail
namespace to construct and send emails.
Background
I continue to use batch files for a number of administrative tasks. Recently when working with a batch file of NTBACKUP commands, I was interested in having the log file text sent through email and developed this application for that purpose.
Using the code
The application receives all arguments as either command line switches, or as supplied through a parameter file. The following shows the usage of the console command and its available switches.
Usage:
CommandLineEmailer [/p:parameterFile] [/smtp:smtpserver] [/f:from]
[/to:recipients] [/cc:courtesyCopies]
[/bcc:blindCourtesyCopies] [/s:subject] [/b:body]
[/a:attachment] [/i:insertFile] [/?]
Switches:
/p: file containing message parameters such as the Smtp server,
recipients lists, and the message subject.
/smtp: smtp server
/f: message sender (from)
/to: semicolon-delimited list of message recipients
/cc: semicolon-delimited list of message courtesy-copy recipients
/bcc: semicolon-delimited list of message blind courtesy-copy recipients
/s: message subject
/b: message body
/a: file attachment
/i: insert the supplied text file in the body
/? display help text
The parameter file that may be supplied with the /p: switch is an ASCII file with lines in the form:
key = value
The following describes the keys that may be used in a parameter file:
smtpserver = the SMTP mail server used to send the message
from = the message sender
to = a semicolon-delimited list of message recipients
cc = courtesy copies (semicolon-delimited list)
bcc = blind courtesy copies (semicolon-delimited list)
subject = the message subject
body = A line of the message body; multiple body= lines
may appear in the parameter file to include multiple
lines in the message body
attachment = A full or partial path to a file that will be attached
to the email message; multiple attachment= lines
may appear in the parameter file to attach multiple files
insertfile = A full or partial path to a text file whose contents will
be inserted into the body of the email message; multiple
insertfile= lines may appear in the parameter file
This shows the text of an example parameter file: smtpserver = myserver.com
from = me@myserver.com
to = recipient1@myserver.com; recipient2@myserver.com
cc = cc1@myserver.com
subject = Test Message
body = This should appear as the first line of the body.
body = This should appear as the second line of the body.
body = This should appear as the third line of the body.
body =
body = The text of file "myprocess.log" follows:
body = --------------------------------------
insertfile = myprocess.log
body = --------------------------------------
body =
body = Finally, some attachments:
attachment = c:\files\FirstAttachment.txt
attachment = c:\files\SecondAttachment.txt
If the above parameter file is saved as myParms.txt, then the CommandLineEmailer program may be executed with the following at the command line or in a batch file: CommandLineEmailer /p:myParms.txtAs an alternative, all required values may be supplied as command line switches. Only one line of body text may be added this way.
CommandLineEmailer /smtp:myserver.com /from:me@myserver.com
/to:recipient@myserver.com
/subject:Test Message /b:This is the message body.
A combination of switches and a parameter file may be also used. For example, with this parameter file defining the "smtpserver" and "from" values: smtpserver = myserver.com
from = me@myserver.com
values for "to", "subject", and "insertfile" may be supplied on the command line: CommandLineEmailer /p:myParms.txt /to:recipient@myserver.com
/subject:Log Report /i:myprocess.log
Describing the code
Three code samples from CommandLineEmailer are described here to show approaches for command line parsing, line by line text file parsing, and email sending.
CommandLineParser Class
This class shows one approach to parsing the command line. For this application, all arguments are supplied through switches. The class constructor is defined with the command line as a string argument, which is converted to an array of strings using the .Split('/')
method. Each individual string in the array is inspected to see if a colon exists, intended to separate the switch from its supplied value. If the colon exists, the switch and value are parsed out and added to a local StringDictionary
object, with the switch being the key. The StringDictionary
class, exposed through the System.Collections.Specialized
namespace, represents a collection of string values associated with string keys. If the colon does not exist, the switch and a blank string ""
as its associated value are added to the StringDictionary
.
public CommandLineParser(string sCommandLine)
{
// given the command line setup an ArrayList of switch items
// switches in the form /s:xxx are supported
_switches = new StringDictionary();
// create an array of items split by the / symbol
string[] arr = sCommandLine.Split('/');
// loop through each string in the array
foreach (string s in arr)
{
string st = s.Trim();
if (st != "")
{
// if the switch has a value it will follow a
// colon after the switch
int i = st.IndexOf(":");
if (i > 0)
{
// we have a switch and a value;
// add to the StringDictionary
// as a convenience, strip out any double quotes
string k = st.Substring(0, i);
string v = st.Substring(i + 1);
_switches.Add(k.Trim(), v.Trim().Replace("\"", ""));
}
else
{
// we have a switch without a value;
// add to the StringDictionary with a blank string
_switches.Add(st, "");
}
}
}
}
The class also defines HasSwitch
and SwitchValue
methods, and an AllSwitches
property:
private StringDictionary _switches;
public bool HasSwitch(string sSwitch)
{
// return TRUE if the given switch (without the slash) exists
return (_switches.ContainsKey(sSwitch));
}
public string SwitchValue(string sSwitch)
{
// return the value associated with the given switch,
// or null if the switch doesn't exist
if (HasSwitch(sSwitch) )
{
return _switches[sSwitch];
}
else
{
return null;
}
}
public StringDictionary AllSwitches
{
get {return _switches;}
}
The class is then used in the application's main()
function like this:
static void Main(string[] args)
{
// parse the command line
CommandLineParser cp = new CommandLineParser(
System.Environment.CommandLine);
.
.
.
// if a parameter file has been supplied, process it first
if (cp.HasSwitch("p")) ProcessParameterFile(cp.SwitchValue("p"));
// then process any other switches that may
// override values or add to values
if (cp.HasSwitch("smtp")) _smtpserver = cp.SwitchValue("smtp");
if (cp.HasSwitch("f")) _from = cp.SwitchValue("f");
.
.
.
}
This approach to command line parsing is limited in that arguments are expected to be passed as /switches. However, if that is suitable for the application at hand, the class may be used as is.
Line by line text file parsing
Given the parameter files used by this application are typically short, the approach used here is to read the entire file into a string and again use the string's .Split()
method to create a string array of lines that are individually parsed. Reading the file into a string is accomplished with the following:
public static string LoadTextFile(string f) {
FileStream fs = File.OpenRead(f);
StreamReader sr = new StreamReader(fs);
String contents = sr.ReadToEnd();
sr.Close();
fs.Close();
return contents;
}
Then the array of individual lines is created with the following function: public static string[] GetLinesFromString(string contents)
{
return contents.Split('\n');
}
Each line is then parsed with the following code to set message variables.
public static void ParseLine(string line)
{
string[] a = line.Split('=');
string key = (a.Length == 0 ? "" : a[0].Trim());
string val = (a.Length == 1 ? "" : a[1].Trim());
switch (key.ToLower())
{
case "smtpserver" : _smtpserver = val; break;
case "from" : _from = val; break;
case "to" : _to = val; break;
case "cc" : _cc = val; break;
case "bcc" : _bcc = val; break;
case "subject" : _subject = val; break;
case "body" : _textblocks.Add(val); break;
case "attachment" : _attachments.Add(val); break;
case "insertfile" :
try
{
_textblocks.Add(LoadTextFile(val));
}
catch (Exception ex)
{
_textblocks.Add("<Error inserting text file " + val +
": " + ex.Message + ">");
}
break;
// everything else is ignored;
}
}
Most of the message variables are simple strings. Body text (supplied through either 'body=' or 'insertfile=' lines) is handled as an ArrayList
of string objects and enumerated prior to sending to construct the message body. This allows multiple body lines to be identified and/or multiple text files to be inserted if desired. The try/catch
block in the case "insertfile:"
case attempts to load the text file and insert its contents; if there is an error, the message body is still constructed with the error text inserted instead.
Sending the Email
The System.Web.Mail
namespace exposes three classes used here for constructing and sending the email. The SmtpMail
class is used both to set the mail server (through the SmtpMail.Server
property), and to send the message itself (through the SmtpMail.Send
method). The MailMessage
class represents the message, identifying the sender, recipients, subject, and body text. The MailAttachment
class represents a file attachment in a MailMessage.Attachments
collection.
The beginning of the SendEmailMessage
function in the CommandLineEmailer application sets up the basic message properties with application variables:
public static void SendEmailMessage()
{
SmtpMail.SmtpServer = _smtpserver;
MailMessage mm = new MailMessage();
mm.From = _from;
mm.To = _to;
mm.Cc = _cc;
mm.Bcc = _bcc;
mm.Subject = _subject;
The body of the email message is constructed using a StringBuilder
object by iterating through each string in the _textblocks
ArrayList
: // add all textblocks to the body
StringBuilder sb = new StringBuilder();
foreach(string s in _textblocks)
{
sb.Append(s + System.Environment.NewLine);
}
mm.Body = sb.ToString() + System.Environment.NewLine;
Attachments are then added to the message's Attachments
collection. A try/catch
block is used to insert error text in the message body if an attachment cannot be added. // attempt to add any attachments; on an error here,
// add error text to the body
foreach(string s in _attachments)
{
try
{
string fullPath = System.IO.Path.GetFullPath(s);
mm.Attachments.Add(new MailAttachment(fullPath));
}
catch (Exception ex)
{
mm.Body += System.Environment.NewLine;
mm.Body += "<Error attaching file " + s + ": " +
ex.Message + ">";
mm.Body += System.Environment.NewLine;
}
}
Finally, the message is sent through the SmtpServer
object:
SmtpMail.Send(mm);
}
Summary
This article demonstrated how to use the CommandLineEmailer console application for sending mail from a command line or batch file. It also described a method for parsing command line arguments and encapsulating such functionality in a reusable class. An approach for parsing a simple parameter file line by line was explored, as were the SmtpServer
, MailMessage
, and MailAttachment
objects from the System.Web.Mail
namespace.