May 1999

Sending mail

by Kent Reisdorph

As many C++ programmers have found, sometimes you need to send email from your C++Builder program. You may also want your users to be able to send you mail regarding technical support, diagnostic information, or for any number of reasons.

Unfortunately, VCL doesn't provide built-in support for mail services. Instead, ActiveX controls are provided for this purpose. The ActiveX controls differ between C++Builder 1.0 and C++Builder 3.0. This article will show you how to send mail from your applications, whether you're using C++Builder 1.0 or 3.0.

SMTP basics

The Simple Mail Transfer Protocol (SMTP) is used to send Internet mail. This protocol, like most Internet protocols, was defined many years ago for the UNIX platform. It has evolved some over the years but the basic protocol remains the same. (If you want to learn more about SMTP, search the Internet for RFC 821; this document defines SMTP.) Sending mail via SMTP requires these steps:
bulletDrop an SMTP control on your form.
bulletSet the remote host and remote port.
bulletSet the properties to the address of an SMTP server.
bulletConnect to the SMTP server.
bulletSet up the message header.
bulletSend the message.

First, we'll discuss mail operations in general. Keep in mind that the information in the following sections is general since it applies to both C++Builder 1.0 and 3.0. We'll get to the specifics in later sections when we talk about sending email with either C++Builder 1.0 or 3.0.

 

Setting the remote host and port

Before you can connect to an SMTP server, you must specify a remote host name and a port. Most of the time, you can just specify mail for the remote host. This tells the SMTP control to connect the default SMTP server for your system. If you want, you can specify a specific mail server. For example, you might set the remote host property to mail.mycompany.com, or mail.myisp.net. Naturally, these are fictitious names and you should use the actual name of the server to which you're connecting. The port can usually be left set to the default value of 25. Port 25 is the default port for mail systems, so you should rarely need to change this property. If you're connecting to a mail system that's listening on a port other than 25, then set the port property accordingly.

Connecting to an SMTP server

To send an email message, you must first log on to a remote SMTP server. Most SMTP servers don't require a user name and password. This means that you can connect to a mail server without worrying about authentication. Most likely you'll connect to your company's mail server, or, if you're going through an Internet Service Provider, that ISP's mail server.

To connect to a server, you call the Connect method of the SMTP control. We'll talk more about connecting to an SMTP server a little bit later.

Setting up the message header

An email message contains a message header that has several fields. For the most part, the field names and their values are obvious. For example, it's readily apparent what the To, From, CC, and Subject fields contain. A couple of header fields, however, require further explanation. For example, the Date field usually indicates the time in either GMT (Greenwich Mean Time) or as an offset from GMT. The Date field for a typical email message might look like this:
Date: Fri, 02 Jan 1998 15:25:14 -0600

Notice the -0600 at the end of the line. This is the offset, in hours, from GMT. It tells you that the message was sent from a time zone that is GMT minus six hours (US Central Standard Time). Otherwise, you'll just see the time specified in GMT; for example:

Date: Fri, 02 Jan 1998 21:04:01 GMT

Whichever format you prefer, it's up to you to build the date and time string when you set the value of the Date field. Another field worth mentioning is the Content-Type field. This field usually looks like this:

Content-Type: text/plain; charset=US-ASCII

In the case of the SMTP control that comes with C++Builder 1.0, you'll have to set the value of this field. In the case of the SMTP control that comes with C++Builder 3.0, this field is set for you.

Finally, there's the Message-ID field. This field specifies a unique ID for each email message. There's no rule on what the message ID should contain, but it usually has a unique ID number followed by the host name from which the message originated. Here are some examples of Message-ID fields:

Message-ID: <000B3C9D.3371@mcp.com>
Message-ID: <34AD5B3A.7E9A@worldnet.att.net>
Message-ID: <349F0E4C.1DA10FD5@mtnfolk.com>

You should come up with some scheme for generating unique message ID numbers. Note that the SMTP control that comes with C++Builder 3.0 doesn't generate the Message-ID field.

Naturally, an email message contains a message body. It might also contain attachments. We'll discuss these in more detail as we look at how to send mail in the following sections.

Sending mail with C++Builder 1.0

The Internet controls that come with C++Builder 1.0 are provided by a company called NetManage. There are several problems with these controls, the primary problem being a lack of any useable documentation. The second problem is that Borland didn't go to any particular effort to fully implement the interfaces to these controls. For those reasons, trying to send mail with the NetManage controls can be a very frustrating experience. The following section will describe the steps required to send mail with the TSMTP control. The TSMTP control, however, doesn't allow file attachments to email messages.

Getting started

To begin, you need to place a TSMTP control on your form. You can find this control on the Internet tab of the Component Palette. Once you have the control on your form, you can set the RemoteHost and RemotePort properties to the SMTP server to which you want to log on. As I said earlier, usually you can leave the RemoteHost property set to the default value of mail, and the RemotePort property set to 25.

Connecting to the SMTP server is simple. You just need to call the Connect method of TSMTP. There is one thing you need to be aware of when calling this method, though, which requires some explanation. The Connect method takes two parameters, which are instances of the Variant class. The first parameter is used to specify the remote host, and the second parameter is used to specify a remote port. These parameters are optional--you won't normally supply them because you'll use the RemoteHost and RemotePort properties instead. Since you aren't using the parameters, you need to provide a dummy value for them. You can't just use an empty string or NULL, because TSMTP is expecting a Variant. You have to create a minimal Variant object and pass that object to the Connect method. Here's how the code would look:

Variant dummy;
dummy.VType = varError;
dummy.VError = DISP_E_PARAMNOTFOUND;
SMTP1->Connect(dummy, dummy);

This will ensure that the call to Connect will succeed without error. Attempting to pass a NULL, a 0, or an empty string will result in an exception at runtime.

When the connection is established, you'll get an OnStateChanged event with a State parameter of prcConnected. Once you know you're connected, you can send the mail message. If you have a permanent Internet connection, then you should be connected right away. If you're using dial-up networking, then Windows should automatically dial when you call the Connect method.

Setting up the message header

Now you need to set up the mail message header. As discussed earlier, the message header contains fields for the To address, the From address, the CC field, the Subject field, the Date field, and so on.

The TSMTP control has a rather obtuse method of setting these parameters. The DocInput property of TSMTP itself has a Variant property called Headers. (Both DocInput and Headers are OLE objects.) You add parameters to the Headers property using the Add method, and then pass it to the SendDoc method when you send a message. If we could call the Add method directly, it would look like this:

Headers.Add("From", "jimbo@redneck.com");

We can't do that, though, because Headers is a Variant and doesn't know anything about the Add method. The OleProcedure method of the Variant class allows us to call a method of an OLE object returned as a Variant. We can use OleProcedure, then, to call the Add method. That probably doesn't make much sense, so an example is in order. Here's the code required to set the From field of an email message:

Variant Headers = SMTP1->DocInput.OlePropertyGet("Headers");
Headers.OleProcedure("Add", "From", "jimbo@redneck.com"); 

This code first creates a Variant object for the Headers property of the DocInput object. After that, OleProcedure is called to call the Add method of the Headers property. The first parameter of the OleProcedure method is the name of the OLE procedure to call. In this case, the second and third parameters are the parameters to pass to the procedure (OleProcedure allows up to nine parameters).

We repeat this step for each of the fields we want to set. The To and CC fields can contain multiple email addresses. Just separate each address with a comma.

I should note that the message header fields aren't fixed. You can add anything you want to a message header. There are certain header fields that are customary, though, and you should provide those header fields. Examine email message headers to see what fields you should provide when constructing an email message header.

Sending the message

Once you've connected to the SMTP server and have filled in all of the header fields, you can send the message. You send a message with the SendDoc method of TSMTP. Here's how it looks:
Variant dummy;
dummy.VType = varError;
dummy.VError = DISP_E_PARAMNOTFOUND;
SMTP1->SendDoc(dummy, Headers, Memo->Text, "", "");
Notice that the first parameter is another dummy Variant instance. This parameter can be used to specify a URL that contains the document to send, but you'll rarely use this parameter, so the dummy argument is used. The second parameter is the Headers object you created and filled out earlier. The third parameter is used to specify the message text.

In this example, the contents of the message are contained in a memo control so we just pass Memo->Text for the message parameter. The fourth parameter can be used to specify a text file that contains the text to be sent. Note that this parameter isn't used to specify an attachment. The text file is simply read and the contents of the file are sent as the body of the message. In this case, we aren't using a text file so an empty string is sent for this parameter. The fourth parameter doesn't apply in this case, so it's also set to an empty string.

Oddly enough, the TSMTP control doesn't have an event that tells you a message was sent successfully. What you can do, though, is monitor the Busy property as follows:

SMTP1->SendDoc(dummy, Headers, Memo1->Text, "", "");
while (SMTP1->Busy)
	Application->ProcessMessages();
StatusBar->SimpleText = "Message Sent!";

While I don't really like this approach, it's the best we can do under the circumstances.

The TSMTP control has few useful events. The OnStateChanged event will tell you when you're connected to a mail server, and when you disconnect. The OnError event will tell you if a mail operation failed. These are probably the only events you'll use regularly.

Sending the message

Listing A and Listing B contain the header and source file for a C++Builder 1.0 program that uses the TSMTP control to send a mail message. The program has just a main form that contains the primary components in
Table A.

Table A: Main form's primary components
Component Name
TSMTP SMTP
TButton ConnectBtn
TButton DisconnectBtn
TButton SendBtn
TEdit ToEdit
TEdit FromEdit
TEdit SubjectEdit
TMemo Message

Place these components on a form and then enter the code from Listing B. 

Sending mail with C++Builder 3.0

Sending mail with C++Builder 3.0 is more straightforward than sending mail with C++Builder 1.0. This is because the SMTP control provided with C++Builder 3.0 is the TNMSMTP control that NetMasters provided. This control is much more intuitive to use than the NetManage control that comes with C++Builder 1.0. Connecting to an SMTP server with the TNMSMTP control is a trivial exercise:
SMTP->Host = "mail";
SMTP->Connect();

It's necessary to set the Host property because the TNMSMTP control doesn't have a default value for this property. The Port property defaults to 25 and you shouldn't have to change it. The OnConnect event will be generated when you've successfully connected to the server.

Setting up the message header is straightforward, as well. The PostMessage property of TNMSMTP is a class which itself contains properties representing the message header fields. Table B lists the properties of the PostMessage class.

Table B: Properties of PostMessage
Property Use
FromAddress The email address of the sender
FromName The name of the sender in plain text
ToAddress One or more email addresses to which you're sending the message
ToCarbonCopy One or more email addresses that will receive carbon copies of the message
ToBlindCarbonCopy One or more email addresses to receive blind carbon copies of the message
Body The body of the message
Attachments One or more files to attach
Subject The message subject
LocalProgram The name of the program sending the email

Filling out the header is a simple matter of providing values for the PostMessage properties:

SMTP->ClearParameters();
SMTP->PostMessage->FromAddress = FromEdit->Text;
SMTP->PostMessage->FromName =  FromEdit->Text;
SMTP->PostMessage->ToAddress->Add(ToEdit->Text);
SMTP->PostMessage->Subject = SubjectEdit->Text;
SMTP->PostMessage->LocalProgram = "My Mailer";
SMTP->PostMessage->Body->Assign(MsgMemo->Lines);
SMTP->PostMessage->Attachments->Add("test.txt");

Notice that the ClearParameters method is called first to clear all of the properties of the PostMessage class. In this example, the values of many of the fields are taken from Edit components on a form. The Body field is filled from the lines of a Memo component.

Once you have the mail header set up, you can send the mail message. This is also a trivial exercise with the TNMSMTP control:

SMTP->SendMail();
You can respond to the OnSuccess or OnFailure events to determine whether or not the mail message was sent successfully. The TNMSMTP control has a rich set of events that you can use to monitor all aspects of the mail operation. (Our Web site contains a C++Builder 3.0 project, which sends a simple email message with TNMSMTP. Go to the www.zdjournals.com and click on the Source Code hyperlink.)

Deploying a mail application

As I said earlier, the SMTP controls provided with C++Builder are ActiveX controls. You need to take special steps to deploy an application using ActiveX controls. You need to be sure that you register the ActiveX controls when you install your program. This can be done with the REGSRVR.EXE application provided with C++Builder, or with a good installation program. We suggest using a commercial installation program. A commercial installation program takes most of the work out of installing ActiveX controls.

The most important thing about deploying an application using ActiveX controls is knowing which files to ship. Check the ActiveX documentation for details on which files to ship. If you want to avoid the ActiveX hassle, you should check out the various Internet sources for freeware, shareware, or commercial native VCL components.

Conclusion

Sending mail using the ActiveX controls that ship with C++Builder is fairly easy once you know how. Unfortunately, not all applications can benefit from mail support. For those that can, however, it can make the difference between your application and that of the competition.

Listing A: SMTPU1.H

//--------------------------------------------
#ifndef SMTPU1H
#define SMTPU1H
//--------------------------------------------
#include <vcl\Classes.hpp>
#include <vcl\Controls.hpp>
#include <vcl\StdCtrls.hpp>
#include <vcl\Forms.hpp>
#include <vcl\ISP.hpp>
#include <vcl\OleCtrls.hpp>
#include <vcl\ComCtrls.hpp>
//--------------------------------------------
class TForm1 : public TForm
{
  __published:  // IDE-managed Components
	TSMTP *SMTP1;
	TLabel *Label1;
	TLabel *Label2;
	TLabel *Label3;
	TLabel *Label4;
	TButton *ConnectBtn;
	TButton *DisconnectBtn;
	TButton *SendBtn;
	TEdit *ToEdit;
	TEdit *FromEdit;
	TEdit *SubjectEdit;
	TMemo *Message;
	TStatusBar *StatusBar;
	
	
	void __fastcall SMTP1StateChanged(
		TObject *Sender, short State);
	
	void __fastcall ConnectBtnClick(
		TObject *Sender);
	void __fastcall DisconnectBtnClick(
		TObject *Sender);
	void __fastcall SendBtnClick(TObject *Sender);
	
  private:  // User declarations
  public:   // User declarations
	__fastcall TForm1(TComponent* Owner);
};
//--------------------------------------------
extern TForm1 *Form1;
//--------------------------------------------
#endif

Listing B: SMTPU1.CPP

//--------------------------------------------
#include <vcl\vcl.h>
#pragma hdrstop

#include "SMTPU1.h"
//--------------------------------------------
#pragma resource "*.dfm"
TForm1 *Form1;
//-------------------------------------------- 
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
}
//--------------------------------------------
void __fastcall TForm1::SMTP1StateChanged(TObject *Sender, short State)
{
  switch (State) {
    case prcConnected : {
      DisconnectBtn->Enabled = true;
      SendBtn->Enabled = true;
      ConnectBtn->Enabled = false;
      break;
    }
    case prcDisconnected : {
      DisconnectBtn->Enabled = false;
      SendBtn->Enabled = false;
      ConnectBtn->Enabled = true;
    }
  } 
  StatusBar->SimpleText = SMTP1->StateString;
}
//--------------------------------------------
void __fastcall TForm1::ConnectBtnClick(TObject *Sender)
{
  Variant dummy;
  dummy.VType = varError;
  dummy.VError = DISP_E_PARAMNOTFOUND;
  SMTP1->Connect(dummy, dummy);
}
//--------------------------------------------
void __fastcall TForm1::DisconnectBtnClick(TObject *Sender)
{
  SMTP1->Quit();
}
//--------------------------------------------
void __fastcall TForm1::SendBtnClick(TObject *Sender)
{
  Variant dummy;
  dummy.VType = varError;
  dummy.VError = DISP_E_PARAMNOTFOUND;
  Variant Headers = SMTP1->DocInput.OlePropertyGet("Headers");
  Headers.OleProcedure("Clear");
  Headers.OleProcedure("Add", "To", ToEdit->Text);
  Headers.OleProcedure("Add", "From", FromEdit->Text);
  Headers.OleProcedure("Add", "Subject", SubjectEdit->Text);
  Headers.OleProcedure("Add", "Date", Now().DateTimeString());
  Headers.OleProcedure("Add", "Content-Type", "text/plain; charset=us-ascii");
  SMTP1->SendDoc(dummy, Headers, Message->Text, "", "");
  while (SMTP1->Busy)
    Application->ProcessMessages();
  StatusBar->SimpleText = "Message Sent!";
}
//--------------------------------------------

Kent Reisdorph is a editor of the C++Builder Developer's Journal as well as director of systems and services at TurboPower Software Company, and a member of TeamB, Borland's volunteer online support group. He's the author of Teach Yourself C++Builder in 21 Days and Teach Yourself C++Builder in 14 Days. You can contact Kent at editor@bridgespublishing.com.