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.
| Drop an SMTP control on your form. | |
| Set the remote host and remote port. | |
| Set the properties to the address of an SMTP server. | |
| Connect to the SMTP server. | |
| Set up the message header. | |
| Send 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.
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.
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.
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.
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.
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.
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.
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.)
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.
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.