Sometimes the easiest way to send a message is to drop it in the mail yourself.
Simple Mail Transfer Protocol (SMTP) was developed to transfer email easily and reliably. It first appeared as Request For Comments (RFC) 821 in August 1982. Since then, SMTP has, with very few changes, become email's behind-the-scenes workhorse.
SMTP is independent of the particular transmission subsystem and requires only a reliable, ordered data-stream channel. On Unix systems, it has been implemented using the MAIL/SMTP service entry in the /etc/services file. The mail service is port 25 using the TCP protocol; hence, you can send email by simply connecting to a stream socket on a system that supports the SMTP Service. (I'll discuss sockets later.)
In this article I will show how easy it is to use the SMTP protocol to send email. I have implemented a class that inherits from Microsoft's MFC Csocket class to enable SMTP communication. I will also discuss reasons why you may or may not want to use SMTP as opposed to another mail API. (See the sidebar entitled "Why Use SMTP?")
How It Works
SMTP's underlying transport uses sockets. Using a socket is analogous to using a telephone: pick up the phone, dial a number, and start talking when someone answers. There are two types of sockets, both of which are bi-directional: Stream (TCP) and Datagram (UDP). SMTP uses the TCP socket protocol. TCP sockets operate like a stream of bytes; they are guaranteed to be received exactly as they were sent. UDP sockets are the opposite of stream sockets; they are not guaranteed to be in order, unduplicated, or even to arrive at their destinations.
The sequence of events for sending an SMTP messages is:
1) Say hello to the service.
2) Tell the service who is to receive the message.
3) Tell the service the message.
4) Say goodbye to the service.You can communicate with the SMTP service using the commands HELO, MAIL, RCPT, DATA, and QUIT. In MFC, these commands are sent as character strings to CSocket's Send method (inherited from CAsynchSocket). Service responses consist of a three-digit numeric response followed by a human-readable text message. If the numeric response is less than 400, the service encountered no problems with the request. If the numeric response is greater than 400, the service encountered problems that must be addressed.
Listing 1 shows an example session with an SMTP service. Lines beginning with "S:" are sent messages; lines beginning with "R:" are returned messages. The communication begins by saying HELO. This tells the service that I will be using the SMTP language rather than the newer enhanced version of the mail service, ESMTP. (To activate ESMTP, you can say hello with EHLO instead of HELO.) The next step is to tell the service that I am about to construct a mail message using the MAIL command. See the SMTP Commands section for command syntax. After I've initiated the mail message, I list the recipients, state the message, then close the connection.
SMTP Commands
All commands are followed by replies from the server. Consult the Interpretation of Replies section below for a detailed explanation of reply structures.
HELO usually appears on a line by itself. It identifies the sender to the server, and tells the SMTP service that the upcoming commands comply with SMTP, not ESMTP.
MAIL FROM: <FROM_ADDRESS> initiates a new mail message, and can be used inside an existing message to signify an attachment.
RCPT TO: <TO_ADDRESS> designates all message recipients, including CC: and BCC: addressees.
DATA must be on a line by itself. After you send the DATA command, the SMTP service will send back a "354 Enter mail, end with "." on a line by itself" response. After reading this initial reply, you can start the actual message. However, you cannot read another response until you have finished the data section with a period on a line by itself. Within the DATA section, use the following subheadings to format the message: FROM:, TO:, CC:, BCC:, DATE:, and SUBJECT:.
QUIT signifies that the server should send an "250 OK" response, then close the transmission channel. Your client program should not terminate the connection until it receives an "250 OK" response.
RSET (Reset Session) resets the current message, and clears all sender, recipient, data, and state tables.
The VRFY string verifies an email address and returns the fully specified mailbox. This command does not add the recipients to the recipient list.
The EXPN string expands the address to a list of recipients. This command does not add the recipients to the recipient list.
HELP explains command usage in a human-readable form.
NOOP (No Operation) does nothing other than return an "250 OK" response.
Interpretation of Replies
After connecting to the SMTP service, expect a "220" reply from the service. After that, expect a reply for every command issued to the service. Each reply is in the format of three numeric digits succeeded by either a space or a hyphen, which is then followed by a human-readable text message. The three-digit code contains all the information necessary for coded response handling. As previously mentioned, a code that is less than 400 indicates success, and a code greater than 400 indicates a problem.
Replies are formatted as follows:
XYZ<space or hyphen>Messagesignifies a good, bad or incomplete response, and can have the following values:
- '1': positive preliminary reply (not used at all)
'2': positive completion reply- '3': positive intermediate reply
- '4': transient negative completion reply
- '5': permanent negative completion reply
- 'Y' signifies the reply category, and can have the following values:
- '0': syntax
- '1': information
- '2': connections
- '3': unspecified
- '4': unspecified
- '5': mail system
Z - Signifies a reply subcategory.
A hyphen indicates that the response is part of a multi-line response. A space indicates that the response is the last line of the response. The text message is intended for informational purposes only, and can be ignored by your response handler.
An SMTP Class
I have covered everything necessary to send an email via Telnet. In this section, I will cover what you need to send an email via C++. My example uses Microsoft Visual C++ v5.0, but you can use any compiler and operating system that support socket communication. First, I customized an existing socket class for communication with the SMTP service. I simply derived a class, CsendSMTP, from the MFC's CSocket using a protected mask:
class CsendSMTP : protected CSocket { ... }Listing 2 contains the complete header file for CsendSMTP.
When deriving one class from another, you can choose from three possible protection masks: public, protected, and private. public allows any function (global or a member of another class) access to all of the base class's protected and public members, through use of the derived class. private allows the derived class to access public and protected members of its base class. No other class or function - not even a descendant of the derived class - can access members of the base class. protected is simliar to private except that descendants of the derived class have access to the base class's public and protected members.
For example:
class base1 { public: publicBase1(); protected: protectBase1(); private: privateBase1(); }; class thisClass : protected base1 { }; class newClass : public thisClass { // access to public and protected // members of base classes of thisClass // the same as if thisClass inherited // from base1 with a public protection // mask }; void Funct() //global function { // this function has no access to // base classes of thisClass; // It is as if thisClass inherited from // base1 with a private protection mask thisClass MyClass; // MyClass has // access // to public and // protected // members of its // base class }I chose this uncommon approach to protect base class functions from direct calls, while still allowing classes derived from the CsendSMTP class to access the base classes by using the protected mask. When outside functions call a member function of the CsendSMTP class, they probably don't need to access its base classes. However, derived classes might need to access the base classes directly.
Class CsendSMTP has three public functions:
BOOL OpenConnection (const char *szMailServerIPAddress); const char * GetLastResponse(); BOOL SendLine (const char *szMessage);OpenConnection returns true when a connection to an SMTP service is successful with a given IP address. GetLastResponse simply returns the last retrieved message. SendLine sends the supplied message to the SMTP service, and retrieves the response from the service. If the response is positive it returns true, otherwise it returns false. Listing 3 contains the implementation file for class CsendSMTP.
The OpenConnection function performs three basic operations. First, it creates a socket and assigns it one of the free ports on your system. Next, that socket is connected to port 25 on the requested server system. Finally, OpenConnection analyzes the server response. If the response is positive, OpenConnection returns true; if the response is negative, it returns false. At this point, you can use GetLastResponse to view the service's actual response.
The SendLine function also performs three basic operations. First, it allocates a buffer large enough to hold a copy of the current message plus a newline character. Next, it sends the newline-terminated line to the SMTP service through the socket. Finally, SendLine receives the service's response.
There is one protected function worth mentioning: ReceiveResponse. ReceiveResponse receives an SMTP service response, and checks the first character. If that character is 1, 2, or 3, the response is positive. If the first character is 4 or 5, the response is negative.
A Simple Example
Listing 4 demonstrates a Windows dialog box procedure that sends a static message. This function uses the CsendSMTP class to deliver the static message. The only items possibly needing clarification are the lines of text in the DATA section. Recall that when the DATA command is sent to the SMTP service, the service returns a response that indicates that you should continue with your message, and to terminate your message with a line containing a single period. More specifically, the service waits until a three-character sequence, newline, period, newline is sent back to the service. The service will only return after you send it a data line containing only a single period. Since SendLine checks for a response after each message issued, you must send the entire message body with one call to SendLine. The rest of the sample application sends the commands to the SMTP service, one line at a time, aborting out of the for loop when either it enounters either an error or a NULL pointer. (In my example, I chose to focus on making the code easy to read at the expense of error handling.) o
Further Information
RFC821, SIMPLE MAIL TRANSFER PROTOCOL, http://www.faqs.org/rfcs.
RFC1651, SMTP Service Extensions, http://www.faqs.org/rfcs.
RFC1893, Enhanced Mail System Status Codes, http://www.faqs.org/rfcs.
Bryan Costales and Eric Alman. Sendmail (Nutshell Handbook), 2nd ed. (O'Reilly & Associates, 1997). ISBN 1565922220.
Bryan Costales and Eric Alman. Sendmail Desktop Reference (O'Reilly and Associates, 1997). ISBN 1565922786.
Bob Fazio works as a system administrator/database administrator for Danet, Inc. in Wexford, PA. Danet develops software for the cellular phone business. Fazio's development is primarily for internal applications. He has been writing code in C/C++ since the mid 80s.