For that reason, we as developers, try to maximize the benefits that can be obtained from this utility, building tools that use this standard, ranging from mass-mailing catalog applications sending to many addresses, to simple price list message sent to a customer via email.
Some history
Between September and October 1971, Ray Tomlinson, the American engineer who is considered the "father of email", sent an email for the first time in history. Tomlinson can't remember the content of that email nor who was the receiver, but "I only remember that the whole thing was in capital letters".
Objectives
Many of us send email using third-party tools such as Outlook Express or Microsoft Outlook, among others, and most likely have encountered registration problems at the client, licensing problems, OLE errors that only occur at the client's site but not in the developer environment. Also there could be problems if both Outlook Express and Microsoft Outlook are being used at the same time, just to mention a few.
To use these tools we utilize MAPI.Session or Outlook.Application, as you know (maybe smiling while you are reading this), creating the objects and calling their properties and methods, from data in the interfaces.
But have you ever wonder "how do these components work? What is it they really do in order to send an email?"? This is what we are going to find out in this article, by only using FOX and the operating system, with all the advantages that could be derived from it.
It is my intention that at the end of this article you will become capable of sending an email from Visual Fox Pro without the need of any OCX or an external component. Just using the tools provided by FoxPro and the operating system would be enough to do the job.
In order to do this I am going to explain two basic points:
Connect to the eMail Server
I think that this is going to be the most difficult point for those who are only FoxPro programmers, because we are used to have high level functions that perform low level jobs, so we can completely ignore its inner workings.
I understand that this is going to be more complicated because we will be using the Windows API, using structures for those API, information conversions and other things to carry out the task of connecting to the eMail server.
It is only fair to mention that this point could be quickly solved by using the Microsoft WinSock Control OCX, but in this way we would not be doing what we want, which is to only use FoxPro and Windows.
Those familiar with the MS WinSock know that in order to connect to a server it is necessary to have three fundamental data to establish a conversation: the first one is the name or the IP Address of the server, to be able to localize it in the Net; the second one is the port number to which we want to communicate; and the third one is the communications protocol to be used (UPD, TCP, etc).
For eMail servers, the last two usually default to port 25 for SMTP (110 for POP3) and the TCP communications protocol.
API functions
Then, to establish the connection to the Mail server, we have to make a Socket connection, doing the same as the Microsoft WinSock Control does. So we are going to use a number of API functions provided by the operating system.
DECLARE LONG socket IN "wsock32.dll" LONG af, LONG prototype, LONG protocol DECLARE LONG WSAStartup IN "wsock32.dll" INTEGER wVersionRequested, STRING @lpWSAData DECLARE LONG connect IN "wsock32.dll" LONG s, STRING @Name, LONG namelen DECLARE LONG inet_addr IN "wsock32.dll" STRING cp DECLARE INTEGER htons IN "wsock32.dll" INTEGER hostshort DECLARE LONG recv IN "wsock32.dll" LONG s, STRING @buf, LONG length, LONG flags DECLARE LONG ioctlsocket IN "wsock32.dll" LONG s, LONG cmd, LONG @argp DECLARE LONG closesocket IN "wsock32.dll" LONG s DECLARE LONG send IN "wsock32.dll" LONG s, STRING buf, LONG length, LONG flags
The idea is not to explain all the various properties of these API functions, but how to use them for our application, because it would make no sense to explain something already well covered in the MSDN from Microsoft.
Pseudocode
We are going to describe here the logic for connection to a server using the operating system API that we have been seen in FoxPro.
If createsock() If connectserver() If send( "Hello " ) Answervar = Receive() Disconnect() Else Error( "Could not send message" ) else Error( "Could not connect to server" ) else Error( "O.S. could not create socket" )
Development of a socket class
At this point we are able to develop a class entirely written in FOX, capable of connecting to a server, with no need of third party tools. For this we are going to assume that the API functions are already declared as described above.
DEFINE CLASS WSocket AS CUSTOM m_Error = .NULL. PROCEDURE SendInformation IF ( THIS.CreateSock() ) IF ( THIS.ConnectServer() ) IF ( THIS.Send( "Test information" ) ) AnswerVar = this.receive() THIS.Desconnect() ELSE THIS.m_Error = "Could not send message" RETURN .F. ENDIF ELSE THIS.m_Error = "Could not connect to server" RETURN .F. ENDIF ELSE THIS.m_Error = "O.S. could not create socket" RETURN .F. ENDIF ENDPROC
PROTECTED PROCEDURE MakeWord( Val1, Val2 ) *|------------------------------------------------------ *| ((WORD) (((BYTE) (a)) | ((WORD) ((BYTE) (b))) << 8)) *|------------------------------------------------------ RETURN BITOR( Val1, BITLSHIFT( Val2, 8 ) ) ENDPROC PROTECTED m_HandleSocket m_HandleSocket = .NULL. PROCEDURE CreateSock #DEFINE PF_INET 2 #DEFINE SOCK_STREAM 1 #DEFINE INVALID_SOCKET 0xFFFF && 65535 LOCAL wSpaceData wSpaceData = SPACE( 400 ) && Space for the WSAData structure && Initializes the WS2_32.DLL to use the Socket. IF ( WSAStartup( THIS.MAKEWORD(1, 1), @wSpaceData) != 0 ) RETURN .F. && O.S. could not initialize socket ENDIF && Get the socket handle from the O.S. THIS.m_HandleSocket = socket( PF_INET, SOCK_STREAM, 0 ) IF THIS.m_HandleSocket == INVALID_SOCKET RETURN .F. && O.S. could not get socket handle ENDIF ENDPROC
You will surely be asking yourselves, what is all this?. But do not despair. It looks complicated but it really is not so. If we look closely we see that what it does in the first place is initialize the WS2_32.dll library for the process being executed, and then ask Windows to create a new socket. When it is created, a handle is returned.
You will notice that the first thing done is a call to the WSAStartup function, because if the latter is not executed first, any of the functions needing the WS2_32.dll library will return an error, because the library is not loaded. But what is the parameter being sent?
The first parameter is the result of executing the MAKEWORD method, which was transcribed from VC++, basically to combine two values into one and then be able to restore both.
But if you look closely, this function has a second parameter, passed by reference, which contains the information returned by the API. We are not analyzing the result from that parameter, because the only thing of interest to us is that the function does not fail when it is loading the library.
m_HostName = .NULL. m_HostPort = .NULL. PROTECTED PROCEDURE Long2ToString( m.Value ) LOCAL I1, m.StringToReturn m.StringToReturn = "" FOR I1 = 24 TO 0 STEP -8 m.StringToReturn = CHR(INT(m.Value/(2^I1))) + m.StringToReturn m.Value = MOD(m.Value, (2^I1)) NEXT RETURN m.StringToReturn ENDPROC PROTECTED PROCEDURE Short2ToString( m.Value ) RETURN CHR( BITAND( m.Value, 0xFF ) ) + CHR( BITRSHIFT( m.Value, 8 ) ) ENDPROC PROTECTED PROCEDURE StringToLong( m.StrToConvert ) LOCAL I1, m.NbrToReturn m.NroARetornar = 0 FOR I1 = 0 TO 24 STEP 8 m.NroARetornar = m.NroARetornar + (ASC(m.StrToConvert) * (2^I1)) m.StrToConvert = RIGHT(m.StrToConvert, LEN(m.StrToConvert) - 1) NEXT RETURN m.NbrToAReturn ENDPROC PROCEDURE ConnectServer #DEFINE INADDR_NONE 0xFFFFFFFF #DEFINE AF_INET 2 LOCAL lsipAddress, lsStructConn, lnReturn lsipAddress = inet_addr( THIS.m_HostName )
IF ( lsipAddress == INADDR_NONE OR lsipAddress <= 0 ) *|-- IP address not found, it is probably the name *|-- of the Server instead of its address. lsipAddress = This.ResolveName() IF lsipAddress == -1 RETURN .F. ENDIF ENDIF *|----- *|-- typedef struct SOCKADDR{ *|-- short sin_family // 2 Bytes *|-- unsigned short sin_port // 2 Bytes *|-- long sin_addr // 4 Bytes *|-- char sin_zero[8] // 8 Bytes *|-- }; *|----- lsStructConn = "" lsStructConn = lsStructConn + THIS.Short2ToString( AF_INET ) lsStructConn = lsStructConn + THIS.Short2ToString( ; htons( THIS.m_HostPort ) ) lsStructConn = lsStructConn + THIS.Long2ToString( lsipAddress ) lsStructConn = lsStructConn + SPACE( 8 ) lnReturn = connect( THIS.m_HandleSocket, @lsStructConn,; LEN( lsStructConn ) ) && sizeof(SOCKADDR) -> 16 Bytes RETURN IIF( lnReturn == -1, .F., .T. ) ENDPROC
The ConnectServer method is the one realizing the connection to the server, using the two above named API: inet_addr and connect; but in order to use the connect function it is necessary to pass one structure by parameter to it, which we built in the lsStructConn variable, formed by the connection type (two 'Short' bits), server address in the net (four 'Long' bits) and finally some eight chars which are reserved, and placed empty for that reason. Once the variable that will work as a "Struc type variable" is formed, we pass it as a parameter to the function and with this we connect to the server.
You will notice that if the inet_addr function returns a number less or equal to zero, or else a value equal to 0xFFFFFFFF, the ResolveName method will be called. This method resolves the address occupied by the server in the network through its name. We are not going to explain this in detail because it has nothing to do with the connection itself, but it is the resolution of a name through the DNS.
PROCEDURE Send( msg ) #DEFINE FIONBIO 0x8004667E #DEFINE SIOCATMARK 0x40047307 #DEFINE SOCKET_ERROR -1 LOCAL slen, nlenMsg, nindex, smsgAux nlenMsg = LEN( msg ) nindex = 0 slen = 0 nRead = 0x1
slen = ioctlsocket( THIS.m_HandleSocket, FIONBIO, @nRead ) IF ( slen == SOCKET_ERROR ) RETURN .F. ENDIF smsgAux = msg DO WHILE( nlenMsg > 0 ) slen = send( THIS.m_HandleSocket, smsgAux, nlenMsg, 0 ) IF ( slen < 1 ) RETURN .F. && Sending to server failed ENDIF nlenMsg = nlenMsg - slen nindex = nindex + slen smsgAux = SUBSTR( msg, nindex, LEN( msg ) - nindex ) ENDDO RETURN .T. ENDPROC
We are then going to analyze the two API functions in depth. The first function, ioctlsocket, receives three parameters. The first one is on what socket connection it is going to be working; the second one is the configuration type it is going to modify and the third one is the value we are going to specify.
Then we have the Send function. It receives four parameters. The first one is the Connection socket on which the function will work; the second one is the text going to be sent; the third one is the length of the text and the fourth one is a flag that can be sent to the function but that we are not going to use.
You will notice that this API Send function is within a conditioned iteration cycle. This is due to the fact that this API function returns the number of characters from the original string that it could send to the server, because the function does not send the whole string at once, but whatever it can. This is why we verify how much it could send, and we send the remainder in the next iteration of the cycle.
PROCEDURE Receive() DO WHILE( retval = 0 ) buffer = SPACE( 4096 ) retval = recv( THIS.m_HandleSocket, @buffer, LEN( buffer ), 0 ) IF retval <> 0 AND retval <> SOCKET_ERROR reply = reply + LEFT( buffer, retval ) ENDIF ENDDO ENDPROC
The modifications we can see are self evident, and it is simple to understand what the code tries to do. When a call is made to the recv function, it needs four parameters.The first one is the socket connection from which it will receive the information; the second one is a variable where the information sent from the server will be stored. This parameter is sent by reference, because this is the way in which FoxPro handles variable pointers to API functions. The third one is the string size. The API function will copy in the variable as a maximum the value which is sent in this parameter. The last one is a flag that we shall not use.
You will notice that the recv API function is within a conditioned iteration cycle, just as the Send API function which was used in the Send method.
Both functions work in a similar way, with a fundamental difference, which is: the first one receives information from the server, while the second one sends information to it, but the way of using them is very similar.
The recv function returns the information that was sent and copies it to the variable passed by reference in the second parameter. If the number of characters in the variable was not enough to copy the information sent by the server, the cycle is repeated until it finalizes, because it finished copying all the information sent by the server, or because an error occurred while receiving the information.
PROCEDURE Disconnect() #DEFINE INVALID_SOCKET 0xFFFF && 65535 #DEFINE SOCKET_ERROR -1 LOCAL nClose && Validates the connection IF THIS.m_HandleSocket == INVALID_SOCKET RETURN .F. ENDIF nClose = closesocket( THIS.m_HandleSocket ) RETURN IIF ( nClose != SOCKET_ERROR, .T., .F. ) ENDPROC ENDDEFINE
With the disconnect method we are finishing the creation of a class completely developed in FoxPro, intelligent enough to connect to any server through sockets, thus replacing the Microsoft WinSock ActiveX Control.
Email standard protocol
The protocol to send email is a standard known to us as SMTP (Simple Mail Transport Protocol), based in the sending of plain text with a predetermined format to the email server. The latter recognizes the format and generates the email going over different servers, as many as necessary, until it reaches the destination server of the email address, where it will be stored until the destination email owner searches it and recovers it by means of another standard email protocol, POP3 (Post Office Protocol v.3).
We are going to analyze the SMPT protocol to see some of its variations to generate an email. For those of you wishing to read the standard, it is available in the Internet at ftp://ftp.aha.ru/support/rfc/rfc822.txt or else at http://www.faqs.org/rfcs/rfc822.html. In there you can see all the specifications of the email sending protocol.
As mentioned before, the email protocol is based on the sending of text between the client and the email server, with a predetermined text format. With this we mean that, in order to send an email, it is not necessary to have a very complex email program, such as Microsoft Outlook. Rather it can be done with a simple Telnet.
Yes it is just as you read it. It is possible to do it with Telnet. In order to do this it is necessary to connect to the email server, to the STMP port and send the mail with the corresponding format. That's all there is to it !!
For those of you who do not know Telnet, it is an application distributed with the operating system since a long time ago, and useful to establish a connection with a server through a determined port.
Many of us have used Telnet to connect to Linux or to the very Unix, making a remote connection in text mode. But its usefulness does not end there. A good example is what we are going to do, is a connection to an email server.
The idea is to show you first how to send emails with Telnet and then that you understand how to do it. I shall explain in more detail some fundamental points of the email standard, and then we shall build our own VFP class to send emails with what we have learned. Lastly we shall use the WSocket class we developed above, as a means to connect with the server.
Using Telnet to send mails
As mentioned, we can send email with Telnet and do everything a sophisticated email program can do. We need to know how to connect to the server and then what to send to it so it will understand us.
To connect we only have to pass Telnet the IP address or the server name, and the port we are going to connect to, as a parameter. Down below we specify how to connect to an assumed mail server published in the Internet. It is worth mentioning that the SMTP servers are public in the Internet and can be used by any user. Contrarywise, the POP3 protocol is not public and asks for a username and password to enable working with it. There is a reason for this: if it were public everybody would be able to see the emails belonging to everybody else.
C:\>telnet smtp.ciudad.com.ar 25
HELO 10.10.10.1 MAIL FROM: RCPT TO: DATA X-Sender: MyAddress@MyDominion X-Mailer: M$ SendMail Build 1.0.0019 Date: Sat, 24 Jan 2004 13:45:00 -0300 From: "My Name and My Last name" Subject: Telnet mail test To: "IANNI, ROBERTO" "Blank line for the beginnig of the mail body" Text test in the body of the mail "Blank line indicating end of mail body" . "Blank line indicating end of message" QUIT
If we copy line by line the above example we would be sending the email, but you will notice that the server will answer with an ok to each command you send to it, obtaining a result similar to the one below:
220 postino3.prima.com.ar ESMTP HELO 10.10.10.1 250 postino3.prima.com.ar MAIL FROM: 250 ok RCPT TO: 250 ok DATA 354 go ahead
X-Sender: MyAddress@MyDominion X-Mailer: M$ SendMail Build 1.0.0019 Date: Sat, 24 Jan 2004 13:45:00 0300 From: "My Name and My Lastname" Subject: Telent mail test To: "IANNI, ROBERTO" Text test in the mail body . 250 ok 1074969345 qp 85748 502 unimplemented (#5.5.1) QUIT 221 postino3.prima.com.ar connection with host has been lost C:\>
First, we are greeting the server with the HELO command and telling it who we are "HELO 10.10.10.1". Here we are saying, we are this IP; then we define the mail sender with the MAIL FROM command and after we define the mail destination with the RCPT TO command . We have thus part of the mail header.
Then, the DATA command begins to define our email properties. For instance, the day and time in which the mail is sent, in "Date:"; sender data in "From:"; mail subject in "Subject:", destination data in "To:". All of these are the properties that our mail applications read to enrich the information they will show.
We then take the body of the email. Once entered the body of the emal, all we have to do is finish it. To do this we just write..."(point) and we press Enter. This is to finish our email and tell the server that you are ready for it to start sending.
We then disconnect from the email server with this command:QUIT And that is all. Our message has been sent.
The properties X-Mailer and X-Sender are optional, and do not create the mail itself, they just offer information to the mail client so that it can have more data to give the user. For example the first one,X-Mailer, only identifies with what application the email has been sent. In our case with the "M$ SendMail Build 1.0.0019" application. But it can be anything. Even the name of your application.
Fundamental points of the standard
We shall now review some fundamental points of the email standard. We refer here to the commands and properties to enable us to format our message.
You might wonder about things such as the following:
How to define the importance of the email?
We do this specifying two properties in the command DATA: X-Priority and Importance. Let us see an example of how to do this.
We define a normal importance email thus:
DATA X-Priority: 3 Importance: Normal
A high importance email is defined like so:
DATA X-Priority: 1 Importance: High
A low priority email is defined thus:
DATA X-Priority: 5 (Lowest) Importance: Low
How do we send the same email to more than one person?
You will remember that in order to define the destination address, the RCPT TO command is used. To add more people we only have to repeat the same command for every person we want to send the message to. Then it should be defined in the TO property of the DATA, specifying the different persons separated by a comma (","). With this we are sending the same email to more than one person, but let us see some examples.
We send an email to just one person in this manner:
RCPT TO: DATA To: "IANNI, ROBERTO"
In this way we send an email to more than one person:
RCPT TO: RCPT TO: DATA To: "IANNI, ROBERTO" , "SECOND, MAIL"
How do we send email with one or more hidden copies?
In order to send an email with one or more hidden copies all we have to do is add all the people involved in the email as destination. For copies or hidden copies we do this, up until now, with the RCPT TO command, and then we define their type in the DATA command.
Here we are going to tell, from all those involved, to whom the TO or CC properties correspond. You might wonder where the hidden copies are. Well, we declare the destination by means of the RCPT command and then we define, for every one of them, whether or not they are TO or CC. If we do not provide anything for the hidden copies, when they open the mail there will be no information at the client, to show the hidden copies. Doing this, we achieve our purpose. He received the email but no one else did.
RCPT TO: RCPT TO: RCPT TO: DATA To: "IANNI, ROBERTO" Cc: "SECOND, MAIL"
How do we send emails with HTML format ?
This is something that we, as email consumers, are used to utilize often when we want to give a different touch to our messages. Many companies also use this in mass mailings of their products published in the Internet, taking advantage in this manner of the possibility to send HTML in an email.
It is not complicated to include HTML in an email. All we have to do is define the property Content-Type in the command DATA. We call this one HTML and this is enough to be able to include HTML when we start writing the body of the email, and for the client to show it as such. It is also possible to use Java or VB scripts. Being HTML, all properties and facilities provided by it can be used. If the Content-Type property is by passed at the DATA command, what was sent is assumed to be text by default and it will be shown in text format. If we were to send HTML with text format, what the client would see would not be the result of the HTML, but the latter code. Let's see the examples.
Message with Text format:
RCPT TO: DATA Content-Type: text/plain;charset=iso-8859-1 To: "IANNI, ROBERTO" Mail body with text format
RCPT TO: DATA Content-Type: text/html;charset=iso-8859-1 To: "IANNI, ROBERTO" HTML window title HTMl format text
HTMl format text
How to have the email answered to another address?
This point is also very used in mailings, which send messages from a garbage address but want to be answered to an existent address. This is not very difficult to do either. There is a property plus the DATA command that we can use to do this: Reply-To, where we indicate where we wish the mail client to respond by default. Let us see an example.
MAIL FROM: RCPT TO: DATA Content-Type: text/plain;charset=iso-8859-1 Reply-To: company@dominion.com To: "IANNI, ROBERTO"
How to specify the email character?
The email character is used a lot when we want to specify beforehand to the receiver how it should treat the email contents.The possible variations are: Normal, Personal, Private, Confidential. As in the previous examples this is also a property specified in the DATA command, called Sensitivity, with a different value for each case, except for the Normal case, which is the default. Therefore the property must not be specified. Here are some examples:
Email of personal character:
MAIL FROM: RCPT TO: DATA To: "IANNI, ROBERTO" Sensitivity: Personal
Email with private character:
MAIL FROM: RCPT TO: DATA To: "IANNI, ROBERTO" Sensitivity: Private
Email with confidential character:
MAIL FROM: RCPT TO: DATA To: "IANNI, ROBERTO" Sensitivity: Company-Confidential
How to ask for a reading confirmation?
We use this when we want to know at what time a person reads the mail, and is no more than just another property of the DATA command: Disposition-Notification-To. Once specified and found by the mail client, it usually asks whether you wish to send the confirmation, but in some organizations mail client applications are used with this property set by default, without user intervention. Let us see an example:
MAIL FROM: RCPT TO: DATA Content-Type: text/plain;charset=iso-8859-1 To: "IANNI, ROBERTO" Disposition-Notification-To: "IANNI, ROBERTO"
Adding files to the email
This maybe the most complicated thing concerning sending an email, because a few more things should be considered. I am going to explain in more detail how to attach a file.
As a first step we have to realize that what we see as an attached file is really no more than a copy of the whole file within the message, in the body portion, as an identifying attribute that shows to it that it begins from a certain part and ends in another. In the middle is found all the text file as it is also in the disk where it is being copied from.
Another thing to clarify is that I am going to concentrate on how to attach a file based on the text format, because binary files have certain characters that have to be replaced before sending them and it is not worth the time, because it does not affect the explanation. For those of you who are as curious as myself, you can find all these explanations in the RFC822 standard, at the addresses already shown.
What we commented should be enough to understand the idea, but, what is it that we specify? Once all the email heading is defined, we have to set a new property of the DATA command, the Content-Type property and tell it that it will be divided in several parts.
One part will be the email text and another one will be the attached file. To do this it is necessary to set the Content-Type property with the multipart/mixed value, with the corresponding ID to identify it within the body of the emial. Let us see an example.
MAIL FROM: RCPT TO: DATA Content-Type: text/plain;charset=iso-8859-1 Reply-To: To: "IANNI, ROBERTO" Content-Type: multipart/mixed; boundary="----=_NextPart_000_333d_1506_5025"
"Blank line for the beginning of the body of the mail" ------=_NextPart_000_333d_1506_5025 "Blank line" Text test in the body of the mail "Blank line" ------=_NextPart_000_333d_1506_5025 Content-Disposition: attachment; filename="Roberto Ianni.txt" "Blank line" Here begins the text file and here it ends in the second line.- ------=_NextPart_000_333d_1506_5025-- "Blank line indicating the end of the body of the email" . "Blank line indicating the end of the body of the email"
With this we declare all necessary characteristics for the file, but once it ends it is necessary to close the ID with "--" (double hyphen) so that the client understands it. In this manner, when the mail leaves the server it will no longer be text, but when it arrives to the email client, the latter will give it shape and the attached file specified will be seen, and you will not note the difference, as we all are used to do it daily.
This is the end of the explanation on how to send email with Telnet. You will realize that it is not very difficult to do and that no third party tools are necessary, or complicated components.
Developing a FoxPro class to send emails
By developing this class we shall materialize what we said at the beginning of the article: "Develop a class to send email only using FoxPro". You will notice how simple it is now that all the elements are clear in our minds, from how to connect to the email server up to how the protocol really works, and so we can put our hands on to finish this last section.
To be able to develop this class I am going to base it on the WSocket class developed above, so we can connect to the email server without having to use any socket component, as for instance MSWinSocket.
I shall beging the class by inheriting from WSocket and naming it WSendMail. It already has all that is necessary to connect to the server but we still have to add all the logic so it generates the emails. With the functionality provided by WSocket, it will almost be the same as if we were inside Telnet.
Figure 1: SendMail - Logic diagram
The only public methods in the WSendMail class will be SendMail and AdjuntarArchivo.
The first one will be in charge of sending the mail with all the characteristics defined into the class before calling the method.
As a first step, it has to create the Socket and subsequently use it in the connection with the email server. Once the connection is established, it has to greet the server and send it the whole email header with the specified properties, as for instance the receiver and the kind of body that will be sent to it. Once all this is finished, it sends the body of the email in the corresponding format and finalizes it with its own command, and then send whatever attached files it has. After this, it only remains to disconnect from the server and exit from the method.
The logic diagram we just described with words, can be seen in figure 1.
The remaining method is AttachFile, in charge of adding each file that is passed as a parameter to an internal array of the class, which is then scanned to send the attached files.
Now we are going to explain the properties that our WSendMail class will have to expose in order to do all the tasks we have been explaining so far.
Table of all properties exposed by WSendMail
Examples on the use of the WSendMail class
We shall now focus on how to use the WSendMail class already developed, and not on how to develop it, because it is something very simple for us FoxPro programmers and there is nothing strange about it. The only thing it does is implementing the RFC822 standard of SMTP, already explained.
Therefore, our previous questions on how to work with the SMTP and Telnet standards, are to be asked again, but this time with the WSendMail class. We can see that our class is very simple to use, and it has nothing to envy to those third party components currently around the developer market. On the contrary, it has a lot of things to its favour. For instance, it is OpenSource and is completely developed in FoxPro, our language of choice.
All examples to be explained below will assume that the programs defined so far are available to you: WSendMail.prg and WSocket.prg.
Sending a simple email
oMail = CREATEOBJECT( "WSendMail" ) oMail.m_HostName = 'smtp.ciudad.com.ar' oMail.m_Originator = 'ianni_roberto@hotmail.com' oMail.m_Destination = 'ianni_roberto@hotmail.com' oMail.m_From = '"IANNI, ROBERTO" ' oMail.m_Subject = 'Our first Mail" oMail.m_To = '"IANNI, ROBERTO" ' oMail.m_Body = 'Body of mail' oMail.SendMail() RELEASE oMail
To do this just define the priority given in the property m_Priority of our class. Let us see the examples.
We give a high priority to the email:
oMail = CREATEOBJECT( "WSendMail" ) oMail.m_From = '"IANNI, ROBERTO" ' oMail.m_Priority = 'H' RELEASE oMail
oMail = CREATEOBJECT( "WSendMail" ) oMail.m_From = '"IANNI, ROBERTO" ' oMail.m_Priority = 'N' RELEASE oMail
oMail = CREATEOBJECT( "WSendMail" ) oMail.m_From = '"IANNI, ROBERTO" ' oMail.m_Priority = 'B' RELEASE oMail
How to send the same email to more than one person?
To do this the standard demands that we repeat the RCPT TO command as many times as people we want to send the email to. Our class will only ask that we put all destinations in the m_Destination property separated by ";" (semi colon). Here is an example.
oMail = CREATEOBJECT( "WSendMail" ) oMail.m_From = '"IANNI, ROBERTO" ' oMail.m_Destination = 'ianni_roberto@hotmail.com' RELEASE oMail
How to send an email with copies or hidden copies ?
This is also very easy. You may recall that the standard tells us that we have to send the mail to all destinations and only set in the properties of the DATA command the ones that we want the email client to show. Let us see an example.
oMail = CREATEOBJECT( "WSendMail" ) oMail.m_Destination = 'ianni_roberto@hotmail.com; second_mail@hotmail.com; tercer_mail@hotmail.com' oMail.m_From = '"IANNI, ROBERTO" ' oMail.m_To = '"IANNI, ROBERTO" ' oMail.m_Cc = '"SECOND, MAIL" ' RELEASE oMail
How to send email with HTML format?
We had to use the Content-Type property, but in our class we only have to use the m_TipoMail property and then write the body of the mail with the format previously defined. Let us see an example.
Email with text format:
oMail = CREATEOBJECT( "WSendMail" ) oMail.m_Destination = 'ianni_roberto@hotmail.com' oMail.m_From = '"IANNI, ROBERTO" ' oMail.m_MailType = 'T' oMail.m_Body = 'Test text' RELEASE oMail
oMail = CREATEOBJECT( "WSendMail" ) oMail.m_Destination = 'ianni_roberto@hotmail.com' oMail.m_From = '"IANNI, ROBERTO" ' oMail.m_MailType = 'H' oMail.m_Body = ' HTML window title' ; + 'HTMl text format' RELEASE oMail
HTMl text format
How to make the email be responded to into another address?
In the standard, this was done with the Reply-To property and in our class it will be the m_ReplyTo property. Just defining the address is enough for the email client to interpret it. Let us see the example.
oMail = CREATEOBJECT( "WSendMail" ) oMail.m_Destination = 'ianni_roberto@hotmail.com' oMail.m_From = '"IANNI, ROBERTO" ' oMail.m_ReplyTo = 'company@dominion.com' RELEASE oMail
To do this we informed the Sensitivity property of the standard. In our class we shall inform the m_TipoCaracter property, where we shall specify whether it is Personal; Private, Confidential, Normal. By default the class considers the last character type for the mail. Let us see the examples.
oMail = CREATEOBJECT( "WSendMail" ) oMail.m_Destination = 'ianni_roberto@hotmail.com' oMail.m_From = '"IANNI, ROBERTO" ' oMail.m_CharacterType = 'V' RELEASE oMail
oMail = CREATEOBJECT( "WSendMail" ) oMail.m_Destination = 'ianni_roberto@hotmail.com' oMail.m_From = '"IANNI, ROBERTO" ' oMail.m_CharacterType = 'N' RELEASE oMail
How do we ask for reading confirmation?
In the standard we defined the Disposition-Notification-Toproperty. In our class we shall do it in the m_NotifyTo property, specifying the address where reading notification of the email sent should be made. Let us see an example.
oMail = CREATEOBJECT( "WSendMail" ) oMail.m_Destination = 'ianni_roberto@hotmail.com' oMail.m_From = '"IANNI, ROBERTO" ' oMail.m_NotifyTo = '"IANNI, ROBERTO" ' RELEASE oMail
How do we attach a file to the email?
To attach files the standard required the definition of the Content-Type: multipart/mixed property and then an ID and with this ID define the body of the email. Separately, the body of the file. In our class it is simpler, because it only requires that we call the AttachFile method with the name of the file as many times as files should be attached. The class will take care of the rest. Let us see an example.
oMail = CREATEOBJECT( "WSendMail" ) oMail.m_Destination = 'ianni_roberto@hotmail.com' oMail.m_From = '"IANNI, ROBERTO" ' oMail.AttachFile( 'C:\ParaRoberto.txt' ) oMail.AttachFile( 'C:\OtroParaRoberto.txt' ) oMail.SendMail() RELEASE oMail
Conclusions
As seen, the SMTP protocol is not very complex. It only requires a little attention to the details so as not to overlook something important. Considering this, we could concentrate in developing a totally FoxPro class,capable of handling all these specifications.
In order to develop the class totally in FoxPro we had to previously implement a WSocket class to connect to the email server, but we have seen that with the help of the Operating System API functions this was very easy to do.
We finally developed an OpenSource class in FoxPro that has nothing to envy the third party products, and which brings us, FoxPro programmers, a lot of advantages from different view points, ranging from economy to scalability.
I hope this article is useful to you. Bear in mind that it can be useful for many other things, as for instance a mailing program.
I thank you for having read this article, it has been a pleasure for me to write it.
Source code