Send Text, HTML, or Both

Sending Custom Multipart Messages Using SMTP Socket Commands

asp:feature

LANGUAGES: C#

ASP.NET VERSIONS: 1.0 | 1.1

 

Send Text, HTML, or Both

Sending Custom Multipart Messages Using SMTP Socket Commands

 

By Mich le Leroux Bustamante

 

This article will show you how to generate a newsletter delivery component that can generate e-mails formatted as text, HTML, or both (as multipart/mixed messages). You'll see how the SmtpMail component can be used to deliver text or HTML messages, and how to bypass its limitations with socket programming to deliver single messages that can be displayed by plain text, or rich HTML e-mail clients.

 

Employing e-mail as part of your business outreach probably means your intranet portals, Internet Web sites, or product solutions may include support for the delivery of HTML-based newsletters, marketing and public relations materials, or event invitations and registrations. HTML messages make it possible for you to brand messages with your company logos consistent with your Web presence, and they inspire better click-through from recipients because the information can be well organized and is generally more appealing than plain text.

 

Most e-mail clients for the Windows platform readily support HTML messages since this is considered standard practice today, but you still may want to send a single message that will be properly displayed by plain text and HTML clients.

 

In this article, I'll show you how easy it is to programmatically send plain text and HTML e-mail using the SmtpMail component. Then I'll explain how to write your own SMTP client using sockets, so that you can build custom plain text messages to accompany the HTML version to support the possibility of plain text clients.

 

So, You Want to Send E-mail?

The .NET Framework System.Web.Mail namespace includes many classes and enumerations to support the process of building and delivering e-mail messages. The list includes the SmtpMail, MailAttachment, and MailMessage classes, and the MailFormat, MailEncoding, and MailPriority enumeration types. The heart of the namespace is the SmtpMail class that wraps COM-based Collaboration Data Objects (CDO) libraries to send messages. SmtpMail uses reflection to invoke either of its CdoNtsHelper or CdoSysHelper child classes that respectively act as platform-dependent wrappers for the COM components CDONTS.NewMail (for NT 4.0 or previous versions) and CDOSYS.Message (for newer Windows platforms).

 

SmtpMail.Send is a static method that can be used to create and send a plain text message with a single line of code. The following example sends e-mail to my yahoo account, from my yahoo account:

 

SmtpMail.Send("[email protected]",

  "[email protected]",

  "Lame Plain Text Email",

  "Nothing interesting here, just some plain text.");

 

Send has two overloads, but the syntax of this simple example passes parameters indicating (in order of appearance) the mailbox of the sender, the mailbox of the recipient, the e-mail subject, and the text content of the e-mail. Here's the resulting message:

 

From: [email protected]

To: [email protected]

Subject: Lame Plain Text Email

Date: Sun, 2 Nov 2003 15:12:26 -0800

MIME-Version: 1.0

Content-Type: text/plain; charset="iso-8859-1"

Content-Transfer-Encoding: 7bit

Content-Length: 47

 

Nothing interesting here, just some plain text.

 

Message headers are automatically generated from the parameters passed, or carry default values. For example, Content-Type defaults to:

 

text/plain; charset="iso-8859-1"

 

since we're sending a simple character stream. SMTP protocol assumes transfer encoding to be 7-bit in the absence of a Content-Transfer-Encoding header. This means that only ASCII characters are transmitted, whereas 8-bit or binary data may contain non-ASCII characters.

 

By default, mail is queued and delivered by the local SMTP service supplied by the Windows operating system. However, a production component will usually configure an SMTP server by setting the SmtpServer property. The other method signature for Send allows me to supply a MailMessage object to identify specific features of the message for greater control. Here's an example that sends an HTML message:

 

MailMessage msg = new MailMessage();

msg.From = "[email protected]";

msg.To = "[email protected]";

msg.Subject = "More Interesting HTML Email";

msg.BodyFormat = MailFormat.Html;

msg.Body = "Coolness! This email " +

           "is much more interesting.";

SmtpMail.SmtpServer = "mysmtpserver.com";

SmtpMail.Send(msg);

 

You can see from the listing that the MailMessage class exposes properties that control the way the message is generated. Setting the BodyFormat property to MailFormat.Html changes the resulting message such that Content-Type is set to multipart/alternative. We'll talk more about this content type setting later on, but inside the message stream I can tell you that "text/html" is indicated, so clients can respect those HTML tags.

 

MailMessage also has an Attachments property that allows you to add one or more attachments to the message. By creating a MailAttachment object, and providing it with a filename, the Send method will read the file into a stream and package it up for you in the resulting message. You can also specify a list of carbon-copy recipients by setting Cc and Bcc properties:

 

MailMessage msg = new MailMessage();

...

MailAttachment att = new MailAttachment("data.zip");

msg.Attachments.Add(att);

SmtpMail.Send(msg);

 

The beauty of all this is that the basic requirements of sending e-mail messages over SMTP are exposed to us through these simple classes, allowing you to successfully send plain text and HTML mail messages with or without attachments. The SmtpMail component doesn't provide support for generating HTML messages that have a custom plain text message, so that a single message can be properly viewed from different types of e-mail clients. I'll address this in the remainder of this article.

 

Plain Text, Anyone?

Multipurpose Internet Mail Extensions, or MIME, specifies a standard format for encapsulating multiple pieces of data into a single Internet message. E-mail clients rely on MIME headers to display messages correctly. Plain text messages are sent with the content type "text/plain" and clients simply display the body of the message. HTML messages can be sent with a content type of "text/html", which tells clients to respect HTML markup in the message body. The SmtpMail component actually sends HTML messages as "multipart/alternative" not "text/html". When attachments are part of the message, plain text and HTML messages use the content type "multipart/mixed".

 

Multipart content type indicates that the message includes one or more different parts separated by a unique string boundary. A multipart message stream consists first of headers, followed by a carriage return and linefeed , and the content enclosed in an opening and closing boundary. The boundary is indicated in the Content-Type header, and e-mail clients expect the boundary string to appear prefixed with two hyphens (--) at the beginning of the entire message content, and between each message part in the content. A closing boundary must appear at the end of the message to indicate the final message part has been read. This closing boundary includes a double hyphen prefix and suffix, as shown in the last line of Figure 1.

 

From: [email protected]

To: [email protected]

Subject: Subject

MIME-Version: 1.0

Content-Type: multipart/mixed; boundary=mime_boundary1

 

--mime_boundary1

 

Content-Type: text/plain; charset=us-ascii

Content-Transfer-Encoding: 7bit

 

 Hello from Michele!

 

--mime_boundary1

 

Content-Type: application/octet-stream

Content-Disposition: attachment; filename="data.zip"

Content-Transfer-Encoding: base64

 

  [binary attachment content goes here]

 

--mime_boundary1--

Figure 1: Sending a multipart/mixed message that contains two parts.

 

The example in Figure 1 demonstrates sending a multipart/mixed message that contains two parts: the plain text message and a zipped file attachment. The boundary string is "mime_boundary1" in this example, but can be any unique string value. Headers are placed within each message part, following the separation boundary, to specify things such as content type and transfer encoding. In the above listing, the first part represents the content of the message, and the second part is a binary attachment.

 

Content type "application/octet-stream" is a generic subtype that indicates the data can be loaded by an application, but the sender isn't specifying which application, e.g. Excel, Word, or PowerPoint. This allows the e-mail client to use the recommended filename, indicated in the Content-Disposition header, to launch the correct application when the attachment is launched. The Content-Disposition header also provides a means for the sender to indicate the file type, and a suggested archival name for the message part.

 

If more attachments were to be added, they would be separated by the same MIME boundary and include similar headers. Of course, this is already handled for you by the SmtpMail component, but we need to understand this in order to build our own messages. It can get interesting with HTML messages. Recall that the content type was set to multipart/alternative when no attachments were added? The SmtpMail component automatically generates two versions of the message when you specify MailFormat.Html: a plain text version, and an HTML version. This is packaged as shown in Figure 2.

 

From: [email protected]

To: [email protected]

Subject: Subject

MIME-Version: 1.0

Content-Type: multipart/alternative; boundary=mime_boundary1

 

--mime_boundary1

 

Content-Type: text/plain; charset=us-ascii

 

 Hello from Michele

 

--mime_boundary1

 

Content-Type: text/html

 

 Hello from Michele

 

--mime_boundary1--

Figure 2: The SmtpMail component automatically generates a plain text version and an HTML version when you specify MailFormat.Html.

 

As you can see in Figure 2, "multipart/alternative" is packaged similarly to "multipart/mixed" except that each message part is interpreted as a different version of the same data. Content type helps to distinguish the difference between them, and e-mail clients usually try to use the last message part that they support. So, if the mail client supports HTML, the second message part is displayed; if not, the first part is displayed. This is why order is important.

 

The SmtpMail component automatically generates the plain text version of the supplied HTML message body by stripping away markup tags, removing content that cannot be represented in text, and trying to insert carriage returns in an attempt to keep things organized. Frankly, even my HTML newsletter doesn't turn out too badly with this. However, I would much rather supply a customized version for plain text and have greater control over its appearance. This isn't possible using the SmtpMail component, so we must dig into SMTP protocol and have some fun with sockets.

 

SMTP 101

Now that you've seen how Internet mail messages are packaged for delivery you'll be able to build your own messages and send them without the help of SmtpMail. Again, the reason for rolling our own code is to make it possible to send both plain text and HTML where the plain text part is fully under our control. Building the message stream should be the easy part at this point, so in this section I'll give you some background on SMTP protocol.

 

Simple Mail Transfer Protocol (SMTP) is an application protocol used to transfer messages across a network. To guarantee reliable messaging between endpoints, Transmission Control Protocol (TCP) is typically used as the transport protocol for SMTP communications. Once a TCP connection is established with an SMTP server, the server is able to receive messages and send responses to the client endpoint that initiated the communication. The messages transmitted in either direction follow the rules defined in RFC 821 for SMTP communications.

 

Communicating with an SMTP server is accomplished through a series of text commands and responses. SMTP commands are character strings terminated by a carriage return and linefeed, or . The syntax is: < command> < parameters> .

 

Assuming a connection has been made with an SMTP server, the first communication is a "hello" handshake indicated by the command HELO. This command simply identifies the client to the SMTP server by passing the hostname of the client, which from my laptop is "mlbthin2":

 

HELO mlbthin2

 

The MAIL command usually follows to initiate a mail transaction. The parameter to this command usually provides the return mailbox for this transaction:

 

MAIL FROM: [email protected]

 

Once a mail transaction has been successfully started, a series of RCPT commands can be issued to provide a list of all recipients, including To, Cc, and Bcc fields. The distinction between them is made when the MIME headers are packaged for To, Cc, and Bcc. So, you might send these four commands:

 

RCPT TO: [email protected]

RCPT TO: [email protected]

RCPT TO: [email protected]

RCPT TO: [email protected]

 

And with the data transmission, send these MIME headers to allocate recipients:

 

From: [email protected]

To: [email protected]

Cc: [email protected];[email protected]

Bcc: [email protected]

 

The DATA command initiates the transmission of data to the listening SMTP server. The server considers all subsequent transmissions part of the message until a termination sequence . (a period between two ) is encountered, at which time the server processes the message. A complete transmission might look something like Figure 3 (you can assume each line is terminated with ).

 

HELO mlbthin2

250 fed1mtao06.cox.net

MAIL FROM: [email protected]

250 Sender Ok

RCPT TO: [email protected]

250 Recipient Ok

RCPT TO: [email protected]board.com

250 Recipient Ok

DATA

354 Ok Send data ending with .

From: [email protected]

To: [email protected]

Cc: [email protected]

Subject: My Subject

Content of message goes here until we send a .

  which will happen after this second line of content.

.

250 Message received: 20031105014554.GOQN8432.fed1mtao06.

  [email protected]

QUIT

221 fed1mtao06.cox.net ESMTP server closing connection

Figure 3: A complete transmission.

 

Each SMTP command you send is terminated with a . Some commands result in immediate response and others put the server in an intermediate state. In the Figure 3 example, responses in the 2yz range were indicative of successful execution of the command, whereas the 354 response in reply to the DATA command provided information about how to terminate the data transmission with .. The server then continues to buffer the data as you transmit it over the connection, until the termination clause is encountered.

 

The consistency here is that each completed command results in a reply beginning with a three-digit status code, followed by a space and any additional information about the reply. Some replies can include more than one response code, but per the SMTP specification, ancillary codes should be followed by a hyphen in lieu of a space, so that the client can make the distinction and focus on the primary response code (the one followed by a space). In the code sample for this project you'll find that I parse the first three characters of the reply, but a more sophisticated SMTP client application may warrant logic to search for multiple replies.

 

So, now you know what the data for the SMTP transmission should look like, headers and all, and you know what series of SMTP commands need to be sent to send an Internet mail message. The last step is to establish a connection with an SMTP server to issue commands and receive replies.

 

Open a Socket and Fire Away

Believe it or not, the hardest part is over, because once you understand MIME and SMTP and their role in delivering Internet mail messages, all you need is a connection to get the job done. The .NET Framework's System.Net and System.Net.Sockets namespaces contain classes to help us communicate over network boundaries. The Socket class is used to establish a connection between two endpoints, and other utility classes such as Dns, IPHostEntry, and IPEndPoint can be useful to get us the information we need to establish that connection.

 

A socket connection is basically two IP addresses (client/server, server/server) that communicate over a particular port, using a specified transmission protocol. For SMTP communications, the IP addresses will be those of the client initiating communication, and the SMTP server. SMTP communications are typically transmitted over a TCP connection through well-known port 25 (although it's possible for SMTP services to listen on other ports in unique circumstances).

 

To get the IP address for the SMTP server you can provide the hostname to the static method Dns.Resolve. This method returns information about the host, specifically a list of possible IP addresses and related aliases, encapsulated in an IPHostEntry object. The AddressList array property contains the host's IP addresses. Only one valid IP is necessary, however, so in my example I use the first entry in the array to create an IPEndPoint as follows:

 

IPHostEntry ipHost = Dns.Resolve("mysmtpserver.com");

IPEndPoint endPoint =

   new IPEndPoint(ipHost.AddressList[0], 25);

 

The IPEndPoint object really only encapsulates the IP address and port provided to the constructor, but it also initializes its AddressFamily property. This property is set to a value from the AddressFamily enumeration based on the addressing scheme to be used by the socket for communications. In the case of Internet mail, IP addresses are used so this property will be set to one of InterNetwork (IPV4) or InterNetworkV6 (IPV6).

 

To create the socket we need to specify an addressing scheme, socket type, and protocol. These parameters are somewhat dependent on one another, so when invalid combinations are provided a SocketException is thrown. For this example we'll be using a stream-based socket (SocketType.Stream) that uses TCP and IP addressing for a reliable, two-way, peer-to-peer connection. After constructing the socket, the subsequent call to Connect passes the endPoint to try to establish the connection:

 

Socket connection = new Socket(endPoint.AddressFamily,

SocketType.Stream, ProtocolType.Tcp);

 

connection.Connect(endPoint);

 

The Connect method of the Socket class will attempt a connection to the endPoint. The open socket will also receive the server's response, and when it does, the Available property of the socket is set to the number of bytes that was returned. After making a successful TCP connection, the string response I get is the following:

 

220 fed1mtao08.cox.net ESMTP server (InterMail

  vM.5.01.06.05 201-253-122-130-105-20030824) ready Wed,

  5 Nov 2003 02:09:57 -0500

 

As I mentioned earlier, each command sent using the open socket connection must be terminated by a . Subsequently the server will execute the command and send a reply. To push commands to the server, the command string is encoded as ASCII, and the socket's Send method fires it off, like the following HELO command per my earlier example:

 

byte[] encData = Encoding.ASCII.GetBytes(String.Format(

                   "HELO {0}\r\n", Dns.GetHostName() ));

connection.Send(encData, 0, encData.Length,

                SocketFlags.None);

 

To pick up the response (which, according to the SMTP specification is always within 512 bytes) you can wait on the Available property to be greater than zero, and then call the socket's Receive method to pick up the encoded reply. After decoding the reply, my example extracts the reply code and checks for error conditions (see Figure 4).

 

byte [] bytes = new byte[512];

while (connection.Available==0)

  System.Threading.Thread.SpinWait(100);

connection.Receive(bytes, 0, connection.Available,

                   SocketFlags.None);

string reply = Encoding.ASCII.GetString(bytes);

int code = Convert.ToInt32(reply.Substring(0,3));

if (SmtpReplies.IsError(code))

   throw new Exception(String.Format(

    "An SMTP error has occurred.\r\n {0}", reply));

Figure 4: After decoding the reply, my example extracts the reply code and checks for error conditions.

 

So, to complete the process of sending a successful Internet mail message, the commands listed earlier should be sent using the socket's Send method, followed by confirmation of success using the Receive method to retrieve each reply. Finally, when the e-mail has been sent, the socket's Close method should be called to terminate communications, after the SMTP QUIT command has been issued on the open connection.

 

For those who cannot view HTML e-mail, using the code described in Figure 4 you can send e-mail that includes a custom text message as shown in Figure 5, while those with HTML support (most) can see the e-mail shown in Figure 6.

 


Figure 5: A plain text e-mail client can see the plain text version of the e-mail.

 


Figure 6: Most e-mail clients can view HTML today.

 

Wrapping It Up

In the sample code available for download, the NewsletterMailer component has a utility class, SmtpUtil, that provides all the functionality discussed in this article for sending simple plain text and HTML messages using the SmtpMail component, and custom multipart messages using SMTP socket commands.

 

With the help of the sample code, and this short guide to mail transfer on the Internet, SMTP protocol and socket programming, you should be able to build components that satisfy your mail messaging needs, or at least better understand how your third-party components handle it if you do not roll your own. Now all you have to do is decide what your messaging goals are, and how HTML and plain text versions should appear when they land in the inbox of your recipients.

 

Resources

 

The sample code in this article is available for download.

 

Mich le Leroux Bustamante is an associate of IDesign Inc., a Microsoft Regional Director, a member of the International .NET Speakers Association (INETA), a frequent conference presenter, and a published author. At IDesign, Mich le provides developer training and high-end architecture consulting. Reach her at mailto:[email protected], or visit http:// www.idesign.net and http:// www.dotnetdashboard.net.

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish