Sending Multiple-Part E-mails Using 4D Internet Commands


ACI - Documentation Français English German ACI Technical Notes ACI Technical Notes, By Subject Back Previous Next Find

Sending Multiple-Part E-mails Using 4D Internet Commands

By Hugo Fournier, Technical Support Engineer

Technical Note 01-12

Technical Notes for Technical Notes for 01-03 March 2001

Introduction


When you want to send e-mails using 4D Internet Commands, you have two options:

You can use SMTP_QuickSend

This command is designed to provide basic e-mailing capabilities in a single statement but does not provide much control over the contents and destination of your e-mails.

You can use the SMTP low-level commands

These commands (SMTP_New, SMTP_Host, SMTP_From, SMTP_Cc, SMTP_Bcc, SMTP_Subject, SMTP_AddHeader and SMTP_Send) are to be used in a sequence that define a mail envelope. Each of these commands addresses a specific aspect of the e-mail and refers to the same mail ID.

In the sample database that comes with this technical note, it is obviously the second method that was chosen. The reason for this choice is simple: SMTP_Quicksend does not allow you to specify contents other than plain text. Although this is fine for many applications, it is not enough when want to specify the Header of the e-mail and use contents of a mixed nature.

Choosing the Right Headers


The e-mail headers define the contents that will be in the body of the e-mail for the user-agent. They basically tell the user-agent what to expect in the body of the e-mail that follows the header. In this example, we will be using several different header types, each of which is designed to match one type of e-mail or body content.

When you send an e-mail with plain text only, you can use the following header type:

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

When you want to send an e-mail with only HTML contents, you need to use a different header type:

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

The problem becomes more complex if you want to send an e-mail that includes several types of body text. For example, suppose you want to send an e-mail that is intended for user-agents who support plain text or HTML. In this case, if you were to use a single header in a single-part e-mail, user-agents that support HTML would display it properly (provided you choose an HTML type header) and others would display only an HTML attachment.

What if you want to send an e-mail that matches the different levels of support of user-agents? In other words, what if you want e-mailers that can only display plain text to display only the plain text version of the e-mail, and others that support HTML to display the HTML version of that text. Obviously there could be other formats as well (enriched text or user-defined formats for example).

In this case you need to use a main header that lets you break down the content of the e-mail into several body parts:

Content-Type: multipart/alternative

This Header type allows you to manage several body types within the same e-mail. Among all those body types, only one will be read by the user-agent. That content type will depend on the e-mailer used and its degree of compliance with the content types present in the e-mail.

An e-mail using that header would typically look as follows:

From: Jane Doe <jd@4d.com>

To: Jim Doe <jd@weirdnames.com>

Date: Mon, 12 Mar 2001 10:53:15 -0800 (PST)

Subject: Hi There

MIME-Version: 1.0

Content-Type: multipart/alternative; boundary=myboundary

-- myboundary

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

... THIS LINE HAS TO REMAIN EMPTY!! ...

... the plain text content is here ...

-- myboundary

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

... THIS LINE HAS TO REMAIN EMPTY!! ...

... HTML version of the e-mail ...

-- myboundary --

By using this header, you let user-agents pick the contents they are most compliant with. In this message organization there are several points to notice:

Content types need to be defined for each body part. A content type that is not defined will be considered as " Content-Type: text/plain; charset=us-ascii ".

Following each content-type line, there has to be an empty line. Forgetting this will undoubtedly make you practice your troubleshooting skills.

Content types are usually organized form the simplest to the most complex (although this is not an absolute requirement).

Additional information on this header type can be found in RFC # 2046.

Getting Started


Before you can use the sample database that comes with this technical note, you need to be able to use an STMP mail server with SMTP relay enabled. Please be aware that when enabling SMTP relay, the mail server is a potential target for relaying spam. Adequate security has to be provided.

Also, for the database to be operational, you will have to enter contact names and their corresponding e-mail addresses. Contact names are saved in the [Contacts] table an their corresponding e-mail addresses are stored in the [Email_Table]. All the email addresses provided in this technical note are, obviously, fictitious.

The Example Database


The sample database allows you to send e-mails whose body includes both a plain text version and an HTML version.

The table used to store the e-mails is the [Emails] table. The entry and sending of e-mails is performed through the form named "Form1":

This form allows you to define all the parameters for the e-mail. Some parameters are directly extracted from fields (From, Subject) and some are stored in variables.

The interface consists of the following:

Three pop-up menus that allow you to select the contacts whose e-mail addresses are to be used in the To, Cc, and Bcc fields for the e-mail. Please note that we are using the term field as it is defined for the SMTP standard (not 4D fields). These pop-up menus are populated with names extracted from the [Contacts] table. When a contact is selected, its e-mail addresse(s) is/are extracted from the [Email_Table] and populate the scrollable areas located next to the pop-up menu.

Three enterable areas that allow you to enter the From, Subject, and Host Name fields. These match 4D fields in the table.

One non-enterable area that displays the header that will be used for the message. If, for example, both plain text and HTML is entered in the text areas at the bottom of the form, the header type will be: multipart/alternative;boundary="MyMimeBoundary".

One tab control that lets you enter the plain text part as well as the HTML part of the body and that displays the actual body sent as well as an e-mail summary.

A Send button that sends the e-mail

A Sent check box that is checked if the e-mail is sent successfully.

Managing the Interface


Pop-Up menus

The three pop-up menus are identical in the way they function: they allow you to select e-mail addresses for the To, Cc, and Bcc e-mail fields. These pop-up menus are initialized and populated when the form is loaded and their contents are saved when the record is saved:

   ALL RECORDS([Contacts])
   ARRAY STRING(80;$MyArrayPopUp;Records in table([Contacts]))
   ARRAY STRING(80;To_Mail;Records in table([Contacts]))
   ARRAY STRING(80;Cc_Mail;Records in table([Contacts]))
   ARRAY STRING(80;Bcc_Mail;Records in table([Contacts]))

Case of 
   : (Form event=On Load )
      vPlainText:=[Emails]Plain_Text
      vHtmlContents:=HTML_Contents
      [Emails]Sent:=False
      
      $ListID:=New list
      ALL RECORDS([Contacts])
      ORDER BY([Contacts];[Contacts]First Name;>)
      $RecNumber:=Records in selection([Contacts])
      For ($i;1;$RecNumber)
         $MyArrayPopUp{$i}:=[Contacts]First Name+" "+[Contacts]Last_Name
         To_Mail{$i}:=$MyArrayPopUp{$i}
         Cc_Mail{$i}:=$MyArrayPopUp{$i}
         Bcc_Mail{$i}:=$MyArrayPopUp{$i}
         NEXT RECORD([Contacts])
      End for 
         `Initializing pop-up menus    
      If (Record number([Emails])>=0)
         To_Mail:=Find in array(To_Mail;[Emails]To)
         Cc_Mail:=Find in array(Cc_Mail;[Emails]cc)
         Bcc_Mail:=Find in array(Bcc_Mail;[Emails]bcc)
      Else 
         To_Mail:=1
         Cc_Mail:=1
         Bcc_Mail:=1
      End if 
         `Initializing scrollable areas
      $ObjPtr:=->To_Mail
      Gen_Fil_List ($ObjPtr)
      $ObjPtr:=->Cc_Mail
      Gen_Fil_List ($ObjPtr)
      $ObjPtr:=->Bcc_Mail
      Gen_Fil_List ($ObjPtr)
    
   : (Form event=On Unload )
      vHeaderType:=""
      vSummary:=""
End case

Also, although the data that is selected from those pop-up menus has a different purpose, the data they store is identical. Because of that, we decided to use pointers which allows us to use the same code for those three objects.

When a user clicks one pop-up menu, the following code is executed:

   Case of 
   : (Form event=On Clicked )
      $ObjPtr:=Self
      Gen_Fil_List ($ObjPtr)
         `Populating the Scrollable area based on user selection
   End case

The method Gen_Fil_List detects which of the three pop-up menus was clicked and which scrollable area to populate with the values extracted from the [Email_Table] table.

   C_POINTER(EmailListPtr)
   Case of 
      `Which pop-up is selected?    
   : ($1=(->To_Mail))
      `Based on the object
      `the corresponding scrollable area is pointed to    
      EmailListPtr:=->ToEmailList
   : ($1=(->Cc_Mail))
       EmailListPtr:=->CcEmailList
   : ($1=(->Bcc_Mail))
      EmailListPtr:=->BccEmailList
   End case 
   
   ALL RECORDS([Contacts])
   ORDER BY([Contacts];[Contacts]First Name;>)
   For ($i;1;$1->-1)
      NEXT RECORD([Contacts])
   End for 
   RELATE MANY([Contacts])
   ARRAY STRING(40;EmailListPtr->;Records in selection([Email_Table]))
   
   SELECTION TO ARRAY([Email_Table]email_Address;$AremailAdr)
   For ($j;1;Size of array($AremailAdr))
         `Filling the scrollable area with the adequate values  
      EmailListPtr->{$j}:=$AremailAdr{$j}
   End for 

Text variables

There are four text variables, each of which is displayed in a different page of the form.

The vPlainText and vHtmlContents variables house the actual body of the e-mail. When users modify their contents (or when the form is loaded), the following code is executed:

   vBoundary:=Char(13)+"--mymimeboundary"+Char(13)
   `this is the boundary variable
   C_TEXT(vPlaintext)
   C_TEXT(vHtmlContents)
   
   Case of 
   : (vPlaintext#"") & (vHtmlContents#"")
         `if there are text and html bodies    
      vAddTxtTypeVar:="Content-Type: text/plain; charset=us-ascii"+Char(13)+Char(13)
         `this var is the content type for the plain text body
      vAddHtmlTypeVar:="Content-Type: text/html; charset=us-ascii"+Char(13)+Char(13)
         `this var is the content type for the html body
      vBodySent:=vBoundary+vAddTxtTypeVar+vPlaintext+vBoundary+vAddHtmlTypeVar+vHtmlContents
      vBodySent:=vBodySent+"--mymimeboundary--"
         `this is the actual text sent through smtp_body
      vHeaderType:="multipart/alternative; boundary="+Char(34)+"mymimeboundary"
      vHeaderType:=vHeaderType+Char(34)
         `   This is the main header type
      
   : (vPlaintext="") & (vHtmlContents#"")
         `if there is an html body only   
      vBodySent:=vHtmlContents
      vHeaderType:="text/html;charset=us-ascii"
      
      
   : (vPlaintext#"") & (vHtmlContents="")
         `if there is a plain text body only       
      vBodySent:=vPlaintext
      vHeaderType:="text/plain; charset=us-ascii"
   
   : (vPlaintext="") & (vHtmlContents="")
         `if there is no body for the email   
      vBodySent:=""
      vHeaderType:="None"
   End case 

This code assigns the header type based on the presence of plain text/HTML in the body of the e-mail. Also, it defines the boundary variable that will be inserted between the different parts of the body. Please note that if there is no multiple body parts (no HTML code or no plain text), this code sets the main header to the adequate value ("text/plain; charset=us-ascii" or "text/html;charset=us-ascii").

The two other variables are present for debug purposes mainly. vBodySent displays the contents sent in the email. vSummary accumulates the values assigned to the different fields during the sending process.

Sending the E-mail


The method responsible for sending the email is the object method of the Send button. It does not use the architecture described in the documentation for the SMTP_Body command since we need to loop through a list of e-mails.

   Case of 
   : (Form event=On Clicked )
      C_LONGINT($SMTP_ID)
      C_LONGINT($err)
      
      
      SAVE RECORD([Emails])
      $err:=SMTP_New ($SMTP_ID)
      If ($err=0)
         $err:=SMTP_Host ($SMTP_ID;[Emails]Hostname)
         vSummary:="Host Name:"+Char(13)+[Emails]Hostname+Char(13)+Char(13)
         If ($err=0)
            $err:=SMTP_From ($SMTP_ID;[Emails]From)
            vSummary:=vSummary+"From:"+Char(13)+[Emails]From+Char(13)+Char(13)
            If ($err=0)
               $err:=Define_To ($SMTP_ID)
               If ($err=0)
               $err:=Define_Cc ($SMTP_ID)
               If ($err=0)
                  $err:=Define_Bcc ($SMTP_ID)
                  If ($err=0)
                     $err:=SMTP_Subject ($SMTP_ID;[Emails]Subject)
                     vSummary:=vSummary+"Subject:"+Char(13)+[Emails]Subject+Char(13)+Char(13)
                     If ($err=0)
                        $err:=SMTP_AddHeader ($SMTP_ID;"Content-Type:";vHeaderType)
                        vSummary:=vSummary+"Header Type:"+Char(13)+vHeaderType+Char(13)
                        If ($err=0)
                           $err:=SMTP_Body ($SMTP_ID;vBodySent)
                           vSummary:=vSummary+"------------------------------------------"+Char(13)
                           vSummary:=vSummary+"Plain text:"+Char(13)
                           vSummary:=vSummary+"------------------------------------------"+Char(13)
                           vSummary:=vSummary+vPlainText+Char(13)
                           vSummary:=vSummary+"------------------------------------------"+Char(13)
                           vSummary:=vSummary+"HTML Contents"+Char(13)
                           vSummary:=vSummary+"------------------------------------------"+Char(13)
                           vSummary:=vSummary+vHtmlContents+Char(13)
                           vSummary:=vSummary+"------------------------------------------"+Char(13)
                           If ($err=0)
                              $err:=SMTP_Send ($SMTP_ID)
                              If ($err=0)
                                 [Emails]Sent:=True  `message was composed and mailed successfully
                              Else 
                                 $Command:="SMTP_Send"
                                 Err_Alert ($err;$Command)
                              End if 
                           Else 
                              $Command:="SMTP_Body"
                              Err_Alert ($err;$Command)
                           End if 
                        Else 
                           $Command:="SMTP_AddHeader"
                           Err_Alert ($err;$Command)
                        End if 
                     Else 
                        $Command:="SMTP_Subject"
                        Err_Alert ($err;$Command)
                     End if 
                  Else 
                     $Command:="SMTP_Bcc"
                     Err_Alert ($err;$Command)
                  End if 
               Else 
                  $Command:="SMTP_Cc"
                  Err_Alert ($err;$Command)
               End if 
            Else 
               $Command:="SMTP_To"
               Err_Alert ($err;$Command)
            End if 
         Else 
            $Command:="SMTP_From"
               Err_Alert ($err;$Command)
         End if 
      Else 
         $Command:="SMTP_Host"
          Err_Alert ($err;$Command)
      End if 
   Else 
      $Command:="SMTP_New"
      Err_Alert ($err;$Command)
   End if 
   $err:=SMTP_Clear ($SMTP_ID)

Also, it calls three subroutines Define_To, Define_Cc, and Define_Bcc when several consecutive calls have to be implemented to pass mutilple recipiends (To, Cc, and Bcc).


   ToEmailList:=1
   If (Size of array(ToEmailList)=1) & (ToEmailList{1}="")
      ALERT("There is no To recipient!")
   Else 
      vSummary:=vSummary+Char(13)+"To:"
      For ($i;1;Size of array(ToEmailList))
         vSummary:=vSummary+Char(13)+ToEmailList{$i}
         $err:=SMTP_To ($1;ToEmailList{$i})
         ToEmailList:=ToEmailList+1
      End for 
      vSummary:=vSummary+Char(13)
      $0:=$err
   End if 


During the execution of the code for sending the e-mail, errors are displayed as well as the 4D Internet Command generating it. Any error trapped before the execution of the SMTP_Send command will result in the cancellation of the sending process.

Summary


This technical note explains how to send multiple-part e-mails using 4D Internet Commands. It also explains the structure of the e-mail you need to use as well as its corresponding content types.


ACI - Documentation Français English German ACI Technical Notes ACI Technical Notes, By Subject Back Previous Next Find