Send HTML e-mail on IBM i using QtmmSendMail api

Send HTML e-mail on IBM i using QtmmSendMail api
Send HTML e-mail on IBM i using QtmmSendMail api. email on ibmi, qtmmsendmail, as400 email program rpgle
Send HTML e-mail on IBM i using QtmmSendMail api

In this article, we discuss about sending emails on the IBM i. There are a lot of ways to do this like SNDDST command but i used QtmmSendMail api to do this as it is more better than the SNDDST command.

SNDDST command does not give us the control over the format of the messages. Think about the way if it is possible for you to embed HTML scripts into your email messages.

QtmmSendMail API

  • Its an IBM system API that allows you to send a MIME(allows to send non-ASCII data over email that SMTP does not allows email without using the SNDDST command. It gives us full control over the message to define using HTML tags.
  • This api is part of the service program QTMMSNDM present in the library QTCP.
  • It accepts parameter as sender name and email address, recipient name and email address, stream file on the ifs containing email message.
  • This api is also used to send SMTP mail messages with multiple attachments.
  • Working of QtmmSendMail API

    The QtmmSendMail api works as follows:

  • First, we create an IFS stream file containing the email message
  • Then call the QtmmSendMail api and pass the name of the ifs stream file and recipient address and then SMTP use it to forward emails.
  • Required parameter by QtmmSendMail api

  • File Name: Input parameter of varsize character. Pass the IFS stream file name containing the mail message.
  • Length of file Name: Input paramter of binary(4) and can be defined as 10i 0 in RPG program. Pass the number of bytes in the file name. Max. length is 255. Not including any NULL at the end.
  • Originator Address(SMTP): Input parameter of varsize character. Pass the sender email address.
  • Length of Originator: Input paramter of binary(4) and can be defined as 10i 0 in RPG program. Pass the sender email address. Pass the length of the sender email address not including any NULL. Max. Length is 256 bytes.
  • First Recipient Address(SMTP): Input paramter of type ADDT0100(*) data structure format. defined later in this article. Pass the recipient address list.
  • Total Number of Recipients: Input paramter of binary(4) and can be defined as 10i 0 in RPG program. Number of email recipients. This must be atleast one.
  • Error Code: Input/Output parameter of varsize character. This struture returns the error information.
  • ADDTO100 Format

    Format is as follows:

  • Offset to next address structure: binary(4) i.e. 10i 0 in RPG. The number of bytes from the start of this address structure to the start of the next one.
  • Address Length: binary(4) i.e. 10i 0 in RPG. Length in bytes of the SMTP email address. MAx. length is 256 bytes and is as per the internet standard.
  • Address Format Name:char(8). Value is ADDT0100 and is used to control the structure.
  • Distribution Type: binary(4) i.e. 10i 0 in RPG. The type of recipient. Possible values are 0 (TO), 1(CC) and 2(BCC) where TO stands for normal, CC stands for carbon copy and BCC stands for Blind carbon copy.
  • Reserved: This field is reserved and should be set to 0.
  • Address: SMTP email address with no nulls included.
  • RPGLE code for sending email

         H BNDDIR('QC2LE')                                                           
                                                                                     
         D SENDEMAIL       PR                                                        
         D FromAdddress                 100A   CONST                                 
         D FromName                     100A   CONST                                 
         D ToAddress                           CONST LIKEDS(EmailAddress) DIM(20)    
         D Subject                       80A   CONST                                 
         D HtmlMessage                 5000A   CONST                                 
                                                                                     
          // entry parameters to this program                                        
         D SENDEMAIL       PI                                                        
         D FromAddress                  100A   CONST                                 
         D FromName                     100A   CONST                                 
         D ToAddress                           CONST LIKEDS(EmailAddress) DIM(20)    
         D Subject                       80A   CONST                                 
         D HtmlMessage                 5000A   CONST                                 
                                                                                     
          // Recipient Email Address Data Structure                                  
         D EmailAddress    DS                  qualified                             
         D  unused                        4b 0                                                
         D  type                          3A                                                  
         D  name                        100A                                                  
         D  address                      50A                                                  
                                                                                              
          // QTMmSendMail API Prototypes (Send MIME mail)                                     
          // Service Program Name: QTCP/QTMMSNDM                                              
          // Use this api to send e-mail from an IBM i program.                               
          // This api supports sending multiple mail attachments at one time                  
          // but the SNDDST(Send distribution) command does not.                              
                                                                                              
                                                                                              
          // This api works as follows:                                                       
          // We create an ASCII file with the entire note.                                    
          // Then call the api and provide it the name of file and address that               
          // the SMTP use to forward the e-mail.                                              
                                                                                              
         D QtmmSendMail    PR                  ExtProc('QtmmSendMail')                        
         D  FileName                    255A   const options(*varsize)              *Input    
         D  FileNameLength...                                                       *Input    
         D                               10I 0 const                                               
         D  OriginatorAddressSMTP...                                                *Input         
         D                              256A   const options(*varsize)                             
         D  LengthofOriginator...                                                   *Input         
         D                               10I 0 const                                               
         D  FirstRecipientAddressSMTP...                                            *Input         
         D                                     likeds(ADDTO100)                                    
         D                                     dim(32767)                                          
         D                                     options(*varsize)                                   
         D  TotalNumberofRecipients...                                              *Input         
         D                               10I 0 const                                               
         D  ErrorCode                  8000A   options(*varsize)                    *Input/Output  
                                                                                                   
          // ADDTO100 Format                                                                       
         D ADDTO100        ds                  qualified                                           
         D                                     based(Template)                                     
         D  OffsettoNextAddressStructure...                                                        
         D                               10I 0                                                     
         D  AddressLength                10I 0                                                     
         D  AddressFormatName...                                                                   
         D                                8A                                
         D  DistributionType...                                             
         D                               10I 0                              
         D  Reserved                     10I 0                              
         D  Address                     256A                                
                                                                            
          // Recipient Email Address Data Structure used by QTMMSENDMAIL    
         D recipientList   ds                  likeds(ADDTO100)             
         D                                     dim(%elem(ToAddress))        
                                                                            
          // variables                                                      
         D recipientCount  s              3  0                              
         D temporaryFileName...                                             
         D                 s            100A                                
         D mailDate        s             30A                                
                                                                            
          // C Language IFS Prototypes                                      
          // Creates temporary file name for IFS file system                
          // the file name is 10 chars and has naming convention            
          // as (QACXxxxxxx). The directory for the file is _/tmp_.         
          // Example: /tmp/QACX01YTRS                                       
          // BNDDIR('QC2LE') in H-specs so that it find function            
                                                                            
          // Note that  _C_IFS_tmpnam function only generates a temporary   
          // file name for a file in the corresponding file system. The     
          // file will not be created by this function.                     
                                                                            
          // Also, as this function returns pointer to the file name        
          // we can use the %str built-in function to get the value instead.
                                                                            
         DGenerateTemporaryStreamFileName...                                
         D                 PR              *   extproc('_C_IFS_tmpnam')     
         D buffer                        39A   options(*omit)               
                                                                            
          // Deletes the defined streamed file.                             
         DremoveStreamFile...                                               
         D                 PR            10I 0 extproc('_C_IFS_remove')     
         D filename                        *   VALUE OPTIONS( *String)      
                                                                            
          // Open File for buffered reading/writing                         
          // filename = (input) path to file in the IFS                    
          // mode = (input) various open mode flags.                       
          // returns *NULL upon error, or a pointer to a FILE structure    
         DopenStreamFile   PR                  extproc('_C_IFS_fopen')     
         D                                     like(Filepointer)           
         D filename                        *   value options(*string)      
         D mode                            *   value options(*string)      
                                                                           
          // Close File                                                    
          //    stream = (input) pointer to FILE structure to close        
         DcloseStreamFile  PR            10i 0 extproc('_C_IFS_fclose')    
         D streamFile                          like(FILEpointer) value     
                                                                           
         D filepointer     s               *   based(prototype_only)       
         D filedescriptor  s                   like(openStreamFile)        
         D index           s              3  0 inz(0)                      
         D header          s          32767a                               
                                                                           
          // fputs(): Write string                                         
          //    string = (input) string to write to file                   
          //    stream = (input) FILE structure designating the file to                                   
          //                write to.                                                                     
          //  returns a non-negative value if successful                                                  
          //       or -1 upon error                                                                       
         DfputsStreamFile  PR            10i 0 extproc('_C_IFS_fputs')                                    
         D String                          *   value options(*string)                                     
         D fileStream                          like(FILEpointer) value                                    
                                                                                                          
          * Get current local date or time in three formats(all output parms)                             
         D CEELOCT         PR                  opdesc                                                     
         D  Liliandate                   10I 0                                      *nodayssinc14oct1582  
         D  LilianSeconds                 8F                                        *00:00:00 14 Oct1582  
         D  GregorianChar                23A                                        *YYYYMMDDHHMISS999    
         D   fc                          12A   options(*omit)                       *12byte feedbackcode  
          * get system offset from UTC(Universal time coordinated)                                        
         D CEEUTCO         PR                  opdesc                                                     
         D  Hours                        10I 0                                                            
         D  Minutes                      10I 0                                                            
         D  Seconds                       8F                                                              
         D  fc                           12A   options(*omit)                                             
          * convert seconds to character timestamp                                     
         D CEEDATM         PR                  opdesc                                  
         D   input_secs                   8F   const                                   
         D   date_format                 80A   const options(*varsize)                 
         D   char_date                   80A   options(*varsize)                       
         D   feedback                    12A   options(*omit)                          
                                                                                       
          // Variables used to generate RFC2822 date format                            
          // An Internet Message Format used to uniformly represent date and time,     
          // including in HTTP and email headers.                                      
          // RFC 2822 includes the shortened day of week, numerical date,              
          // three-letter month abbreviation, year, time and time zone,                
          // displaying as 01 Jun 2016 14:31:46 -0700.                                 
                                                                                       
         D rfc2822format   c                   'Www, DD Mmm YYYY HH:MI:SS'             
         D junk1           s              8F                                           
         D junk2           s             10I 0                                         
         D junk3           s             23A                                           
         D hours           s             10I 0                                         
         D minutes         s             10I 0                                         
         D timezone_hours  s              2P 0                  
         D timezone_minutes...                                  
         D                 s              2P 0                  
         D timezone        s              5A   varying          
         D currentTime     s              8F                    
         D tempDate        s             25A                    
                                                                
         D nullErrorDS     ds                                   
         D   BytesProv                   10I 0 inz(0)           
         D   BytesAvail                  10I 0 inz(0)           
                                                                
          // Email Address Type Constants                       
         D CONST_TO_ADDRESS...                                  
         D                 c                   0                
         D CONST_CC_ADDRESS...                                  
         D                 c                   1                
         D CONST_BCC_ADDRESS...                                 
         D                 c                   2                
                                                                
          // Line Feed Character                                
         D CONST_LF        c                   x'25'                      
         D CONST_CRLF      c                   x'0d25'                    
          /free                                                           
            temporaryFileName =                                           
                  %trim(%str(GenerateTemporaryStreamFileName(*omit)));    
                                                                          
            // create new output file                                     
            filedescriptor = openStreamFile(%trim(temporaryFileName):     
                                            'w codepage=1252');           
            if (filedescriptor = *NULL);                                  
              *INLR = *ON;                                                
              return;                                                     
            endif;                                                        
                                                                          
            // close file & reopen in text mode so that                   
            // data will be automatically translated                      
            closeStreamFile(filedescriptor);                              
            filedescriptor = openStreamFile( %trim(temporaryFileName) :   
                                            'a codepage=37');             
            if (filedescriptor = *NULL);                                  
              *INLR = *ON;                                                          
              return;                                                               
            endif;                                                                  
                                                                                    
            // Calculate the Timezone in format '+0000', for example                
            // CST should show up as '-0600'                                        
                                                                                    
            CEEUTCO(hours: minutes: junk1: *omit);                                  
            timezone_hours = %abs(hours);                                           
            timezone_minutes = minutes;                                             
            if (hours < 0);                                                         
              timezone = '-';                                                       
            else;                                                                   
              timezone = '+';                                                       
            endif;                                                                  
                                                                                    
            timezone += %editc(timezone_hours:'X') + %editc(timezone_minutes:'X');  
                                                                                    
            //  Get the current time and convert it to the format                   
            //    specified for e-mail in RFC 2822                                  
            CEELOCT(junk2: CurrentTime: junk3: *omit);                             
            CEEDATM(CurrentTime: rfc2822format: tempDate: *omit);                  
            maildate = tempDate + ' ' + timezone;                                  
            recipientCount = 0;                                                    
            header = 'From: "' + %trim(FromName)                                   
                     + ' "<' + %trim(FromAddress) + '>' + CONST_LF;                
            for index = 1 to %elem(ToAddress);                                     
              if %trim(ToAddress(index).type) = '';                                
                leave;                                                             
              endif;                                                               
              header = %trim(header) + %trim(ToAddress(index).type) + ': '         
                       + %trim(ToAddress(index).name) + ' <'                       
                       + %trim(ToAddress(index).address) + '>' + CONST_LF;         
              recipientList(index).OffsettoNextAddressStructure                    
                                           = %size(ADDTO100);                      
              recipientList(index).AddressFormatName = 'ADDT0100';                 
              select;                                                              
                when ToAddress(index).type = 'TO';                                 
                  recipientList(index).DistributionType   = CONST_TO_ADDRESS;      
                when ToAddress(index).type = 'CC';                                 
                  recipientList(index).DistributionType   = CONST_CC_ADDRESS;             
                when ToAddress(index).type = 'BCC';                                       
                  recipientList(index).DistributionType   = CONST_BCC_ADDRESS;            
              other;                                                                      
                  recipientList(index).DistributionType   = CONST_TO_ADDRESS;             
              endsl;                                                                      
                                                                                          
              recipientCount += 1;                                                        
              recipientList(index).Reserved = 0;                                          
              recipientList(index).Address = %trim(ToAddress(index).address);             
              recipientList(index).AddressLength =                                        
                                  %len(%trim(ToAddress(index).address));                  
            endfor;                                                                       
                                                                                          
            header = %trim(header) +'Date: ' + maildate + CONST_LF                        
                     +'Subject: ' + Subject + CONST_LF                                    
                     +'MIME-Version: 1.0' + CONST_LF                                      
                     +'Content-Type: multipart/related; boundary="MSG_PART"'              
                     + CONST_LF + CONST_LF + '--MSG_PART' + CONST_LF                      
                     +'Content-Type: text/html' + CONST_LF                                
                     +'Content-Disposition: inline;' + CONST_LF + CONST_LF         
                     + CONST_LF;                                                   
                                                                                   
            fputsStreamFile(%trim(header): filedescriptor);                        
            fputsStreamFile(%trim(HtmlMessage) + CONST_LF: filedescriptor);        
            fputsStreamFile('--MSG_PART--' + CONST_CRLF: filedescriptor);          
            closeStreamFile(filedescriptor);                                       
                                                                                   
            //  Use the QtmmSendMail() API to send the                             
            //  IFS file via SMTP                                                  
            QtmmSendMail( %trim(temporaryFileName): %len(%trim(temporaryFileName)) 
                                 : %trim(FromAddress): %len(%trim(FromAddress))    
                                 : recipientList: recipientCount: nullErrorDS);    
            removeStreamFile(%trim(temporaryFileName));                            
            *inlr = *on;                                                           
            return;                                                                
          /end-free                                                                                 
    

    Compile command for SendEMail program

    We will create module and program object of SendEmail.

  • First perform, Addlible QTCP and addlible EASYCLASS1(developer library)
  • Create Module:
  • CRTSQLRPGI OBJ(EASYCLASS1/SENDEMAIL)        
               SRCFILE(EASYCLASS1/SEP2023)      
               SRCMBR(SENDEMAIL)                
               COMMIT(*NONE)                    
               OBJTYPE(*MODULE)                 
               REPLACE(*YES)                    
               DBGVIEW(*SOURCE)                 
  • Create Program:
  • CRTPGM PGM(EASYCLASS1/SENDEMAIL)   
           MODULE(EASYCLASS1/SENDEMAIL)
           BNDSRVPGM((QTCP/QTMMSNDM))  
           ACTGRP(*NEW)                

    RPGLE code for sending email: Explanation

         H BNDDIR('QC2LE')  

    this will bind the QC2LE binding directory present in QSYS to this program as we are calling various C IFS apis like fclose, fopen, fputs, _C_IFS_tmpnam. So, to get their definition we wrote above line in H specs. Service program QC2IFS in library QSYS is part of the QC2LE binding directory that has these procedures existence. we can do dspsrvpgm qsys/QC2IFS to see procedure names.

         D SENDEMAIL       PR                                                      
         D FromAdddress                 100A   CONST                               
         D FromName                     100A   CONST                               
         D ToAddress                           CONST LIKEDS(EmailAddress) DIM(20)  
         D Subject                       80A   CONST                               
         D HtmlMessage                 5000A   CONST                               
                                                                                   
          // entry parameters to this program                                      
         D SENDEMAIL       PI                                                      
         D FromAddress                  100A   CONST                               
         D FromName                     100A   CONST                               
         D ToAddress                           CONST LIKEDS(EmailAddress) DIM(20)  
         D Subject                       80A   CONST                               
         D HtmlMessage                 5000A   CONST                               

    we defined the procedure prototype and procedure Interface for SENDEMAIL program which is the replacement for *entry parameter list to this program. PR is definition and PI is parameters Input. We are taking following input parameters from the user to this program. So, whosoever wants to call this SENDEMAIL program has to pass the following input parameters to it.

  • Sender SMTP email address
  • Sender Name
  • Recipient list in the ds structure like emailaddress and for now DIM(20) is defined which means we can pass max 20 recipient address at one time.
  • Subject of the email
  • Email HTML message to be sent to recipient
  •       // Recipient Email Address Data Structure      
         D EmailAddress    DS                  qualified 
         D  unused                        4b 0           
         D  type                          3A             
         D  name                        100A             
         D  address                      50A             

    Declared the EmailAddress data Structure that is referred by program input parameter ToAddress. Here, ds is qualified and main subfields are type, name and address. Here type is the distribution type i.e. 0 if TO(normal), 1 if CC(Carbon Copy) and 2 if BCC(Blind Carbon Copy). Name is the Recipient Name and Address is the recipient SMTP email address.

    D QtmmSendMail    PR                  ExtProc('QtmmSendMail') 
    D  FileName                    255A   const options(*varsize) 
    D  FileNameLength...                                          
    D                               10I 0 const                   
    D  OriginatorAddressSMTP...                                   
    D                              256A   const options(*varsize) 
    D  LengthofOriginator...                                      
    D                               10I 0 const                   
    D  FirstRecipientAddressSMTP...                               
    D                                     likeds(ADDTO100)        
    D                                     dim(32767)              
    D                                     options(*varsize)       
    D  TotalNumberofRecipients...                                 
    D                               10I 0 const                   
    D  ErrorCode                  8000A   options(*varsize)       

    This is the IBM api which is used to send MIMI email message. The parameter list is already described above in section 1(ii).

          // ADDTO100 Format                                     
         D ADDTO100        ds                  qualified         
         D                                     based(Template)   
         D  OffsettoNextAddressStructure...                      
         D                               10I 0                   
         D  AddressLength                10I 0                   
         D  AddressFormatName...                                 
         D                                8A                     
         D  DistributionType...                                  
         D                               10I 0                   
         D  Reserved                     10I 0                   
         D  Address                     256A                     

    This is the format used by QTmmSendMail api. The format ADDTO100 is already described above in section 1(iii).

          // Recipient Email Address Data Structure used by QTMMSENDMAIL    
         D recipientList   ds                  likeds(ADDTO100)             
         D                                     dim(%elem(ToAddress))        

    We defined another data structure named recipientList same as DS format ADDTO100 defined above and set %elem(ToAddress) in DIM keyword which is 20 elements.

          // variables                             
         D recipientCount  s              3  0     
         D temporaryFileName...                    
         D                 s            100A       
         D mailDate        s             30A       

    declare some variables like recipient count, temporaryfileName and maildate to be using in the main program logic.

         DGenerateTemporaryStreamFileName...                            
         D                 PR              *   extproc('_C_IFS_tmpnam') 
         D buffer                        39A   options(*omit)           

    Prototype declaration for _C_IFS_tmpnam function. This function is C IFS function and its definition can be found by binding the QC2LE binding directory to the program. _C_IFS_tmpnam api generates temporary file name for the IFS file system, the file name is of 10 characters and has naming convention like QACXxxxxxx. The directory for the file is _/tmp_. Example : /tmp/QACX01YTRS. Please note that it only generates a temporary file name for a file in the corresponding file system. The file will not be created by this function. Also, as this function returns pointer to the file name we can use the %str built-in function to get the value instead.

         DremoveStreamFile...                                          
         D                 PR            10I 0 extproc('_C_IFS_remove')
         D filename                        *   VALUE OPTIONS( *String) 

    Prototype declaration for _C_IFS_remove C function. It deletes the defined streamed file.

         DopenStreamFile   PR                  extproc('_C_IFS_fopen') 
         D                                     like(Filepointer)       
         D filename                        *   value options(*string)  
         D mode                            *   value options(*string)  

    Prototype declaration for _C_IFS_fopen C function. It Open File for buffered reading/writing. This will create the file as well if not already present.

         DcloseStreamFile  PR            10i 0 extproc('_C_IFS_fclose') 
         D streamFile                          like(FILEpointer) value  

    Prototype declaration for _C_IFS_fclose C function. It Closes File.

          D filepointer     s               *   based(prototype_only)   
          D filedescriptor  s                   like(openStreamFile)    
          D index           s              3  0 inz(0)                  
          D header          s          32767a                           

    declared filepointer variable as pointer variable based on prototype_only, filedescriptor again as pointer variable using like(openStreamFile) as openStreamfile procedure returns the pointer variable. declared index variable for loop processing and header variable for preparing email header text.

         DfputsStreamFile  PR            10i 0 extproc('_C_IFS_fputs')   
         D String                          *   value options(*string)    
         D fileStream                          like(FILEpointer) value   

    Prototype declaration for _C_IFS_fputs C function.It is used to write string on the ifs file.

         D CEELOCT         PR                  opdesc                                                      
         D  Liliandate                   10I 0                                      *nodayssinc14oct1582   
         D  LilianSeconds                 8F                                        *00:00:00 14 Oct1582   
         D  GregorianChar                23A                                        *YYYYMMDDHHMISS999     
         D   fc                          12A   options(*omit)                       *12byte feedbackcode   

    Prototype declaration for CEELOCT function. It is used to get current local date or time. All the above parameters are output parameters to this program.

    CEELOCT returns the current local date or time in three formats as follows:

  • Lilian date 32-bit binary integer (the number of days since 14 October 1582)
  • Lilian seconds 64-bit double-floating point number (the number of seconds since 00:00:00 14 October 1582)
  • Gregorian character string 17-byte fixed-length character string (in the form YYYYMMDDHHMISS999)
  • Also, if you notice tha opdesc is specified with the CEELOCT api.The OPDESC keyword specifies that operational descriptors are to be passed with the parameters that are defined within a prototype. The keyword applies both to a prototype definition and to a procedure-interface definition. It cannot be used with the EXTPGM keyword. Sometime it is mandatory to pass some procedure parameters even though the data type is not precisely known to the called procedure. In that case you can use operational descriptors to provide descriptive information to the called procedure regarding the form of the parameter. The additional information allows the procedure to properly interpret the string. We should only use the opdesc when they are actually expected by the called procedure. Many ILE apis expect opdesc. If any parameter is defined as 'by descriptor', then we should pass opdesc to the API.

         D CEEUTCO         PR                  opdesc             
         D  Hours                        10I 0                    
         D  Minutes                      10I 0                    
         D  Seconds                       8F                      
         D  fc                           12A   options(*omit)     

    Prototype declaration for CEEUTCO function. It is used to get system offset from UTC(Universal time coordinated).

         D CEEDATM         PR                  opdesc                   
         D   input_secs                   8F   const                    
         D   date_format                 80A   const options(*varsize)  
         D   char_date                   80A   options(*varsize)        
         D   feedback                    12A   options(*omit)           

    Prototype declaration for CEEDATM function. It is used to convert seconds to character timestamp. The second and third parameters require an operational descriptor.

         D rfc2822format   c                   'Www, DD Mmm YYYY HH:MI:SS'
         D junk1           s              8F                              
         D junk2           s             10I 0                            
         D junk3           s             23A                              
         D hours           s             10I 0                            
         D minutes         s             10I 0                            
         D timezone_hours  s              2P 0                            
         D timezone_minutes...                                            
         D                 s              2P 0                            
         D timezone        s              5A   varying                    
         D currentTime     s              8F                              
         D tempDate        s             25A                              

    Variables used to generate RFC2822 date format. An Internet Message Format used to uniformly represent date and time, including in HTTP and email headers. RFC 2822 includes the shortened day of week, numerical date, three-letter month abbreviation, year, time and time zone, displaying as 01 Jun 2016 14:31:46 -0700.

         D nullErrorDS     ds                          
         D   BytesProv                   10I 0 inz(0)  
         D   BytesAvail                  10I 0 inz(0)  

    declare nullErrorDs data structure for getting error information from the QtmmSendMail api.

          D CONST_TO_ADDRESS...                                
          D                 c                   0              
          D CONST_CC_ADDRESS...                                
          D                 c                   1              
          D CONST_BCC_ADDRESS...                               
          D                 c                   2              
                                                                         
          D CONST_LF        c                   x'25'          
          D CONST_CRLF      c                   x'0d25'        

    declare constants to hold values for TO, CC and BCC distribution type and LF and CRLF hex value is defined.

           /free                                                             
             temporaryFileName =                                             
                   %trim(%str(GenerateTemporaryStreamFileName(*omit)));      
                                                                             
             // create new output file                                       
             filedescriptor = openStreamFile(%trim(temporaryFileName):       
                                             'w codepage=1252');             
             if (filedescriptor = *NULL);                                    
               *INLR = *ON;                                                  
               return;                                                       
             endif;                                                          

    Main logic starts from here. First we called procedure GenerateTemporaryStreamFileName to generate the temporary file name and returned into variable temporaryFileName. Then we create the IFS stream file by calling procedure openStreamFile and pasing path to temporary filename and mode. we create the file and opened it in write mode with codepage 1252. Once, the file is created and opened successfully the filedescriptor is assigned to it and if its not created or opened then file descriptor will be *NULL and in that case no need to proceed further and return from the program by setting the last record indicator to *ON.

            closeStreamFile(filedescriptor);                            
            filedescriptor = openStreamFile( %trim(temporaryFileName) : 
                                            'a codepage=37');           
            if (filedescriptor = *NULL);                                
              *INLR = *ON;                                              
              return;                                                   
            endif;                                                      

    Once, the file is created and opened then we close that file by calling procedure closeStreamFile and passing file descriptor to it. Once file is closed, we will reopen that file by calling procedure openStreamFile and passing path to temporaryFileName and mode as append with codepage 37 i.e. reopen the file in text mode so that data will be automatically translated. Once the file is reopened successfully in text mode a file descriptor is assigned to it and return from the openStreamFile procedure. If is *NULL that means error opening file and return from program by setting *inlr to *ON.

            CEEUTCO(hours: minutes: junk1: *omit);                                  
            timezone_hours = %abs(hours);                                           
            timezone_minutes = minutes;                                             
            if (hours < 0);                                                         
              timezone = '-';                                                       
            else;                                                                   
              timezone = '+';                                                       
            endif;                                                                  
                                                                                    
            timezone += %editc(timezone_hours:'X') + %editc(timezone_minutes:'X');  

    Call CEEUTCO procedure and calculate the Timezone in format '+0000', for example CST should show up as '-0600'

            CEELOCT(junk2: CurrentTime: junk3: *omit);               
            CEEDATM(CurrentTime: rfc2822format: tempDate: *omit);    
            maildate = tempDate + ' ' + timezone;                    

    Call CEELOCT and CEEDATM procedure to get the current time and convert it to the format specified for e-mail in RFC 2822

            recipientCount = 0;                                              
            header = 'From: "' + %trim(FromName)                             
                     + ' "<' + %trim(FromAddress) + '>' + CONST_LF;          

    Set the recipientcount as zero and prepare the header by appending Sender name and sender email address.

            for index = 1 to %elem(ToAddress);                                  
              if %trim(ToAddress(index).type) = '';                             
                leave;                                                          
              endif;                                                            
              header = %trim(header) + %trim(ToAddress(index).type) + ': '      
                       + %trim(ToAddress(index).name) + ' <'                    
                       + %trim(ToAddress(index).address) + '>' + CONST_LF;      
              recipientList(index).OffsettoNextAddressStructure                 
                                           = %size(ADDTO100);                   
              recipientList(index).AddressFormatName = 'ADDT0100';              
              select;                                                           
                when ToAddress(index).type = 'TO';                              
                  recipientList(index).DistributionType   = CONST_TO_ADDRESS;   
                when ToAddress(index).type = 'CC';                              
                  recipientList(index).DistributionType   = CONST_CC_ADDRESS;   
                when ToAddress(index).type = 'BCC';                             
                  recipientList(index).DistributionType   = CONST_BCC_ADDRESS;  
              other;                                                            
                  recipientList(index).DistributionType   = CONST_TO_ADDRESS;   
              endsl;                                                         
                                                                             
              recipientCount += 1;                                           
              recipientList(index).Reserved = 0;                             
              recipientList(index).Address = %trim(ToAddress(index).address);
              recipientList(index).AddressLength =                           
                                  %len(%trim(ToAddress(index).address));     
            endfor;                                                          

    Process the for loop until %elem(ToAddress). User can pass 20 recipient email addresses. Once ToAddress(index).type is blank i.e. no more recipient left then stop appending sender information to the header and recipientList data structure. Otherwise append the recipient information in header and recipientList ds in loop.

            header = %trim(header) +'Date: ' + maildate + CONST_LF          
                     +'Subject: ' + Subject + CONST_LF                      
                     +'MIME-Version: 1.0' + CONST_LF                        
                     +'Content-Type: multipart/related; boundary="MSG_PART"'
                     + CONST_LF + CONST_LF + '--MSG_PART' + CONST_LF        
                     +'Content-Type: text/html' + CONST_LF                  
                     +'Content-Disposition: inline;' + CONST_LF + CONST_LF  
                     + CONST_LF;                                            

    Message header contains the "from" name and address along with the RFC2822 formatted "sent date" and the message subject. The program adds the message headers to identify the content type of the message along with the boundary identifier that is used to define the message body.

            fputsStreamFile(%trim(header): filedescriptor);                   
            fputsStreamFile(%trim(HtmlMessage) + CONST_LF: filedescriptor);   
            fputsStreamFile('--MSG_PART--' + CONST_CRLF: filedescriptor);     
            closeStreamFile(filedescriptor);                                  

    Once all of the message headers have been generated, the string containing those message headers is written out to the temporary file that we created earlier by calling procedure fputsStreamFile and passing header message and file descriptor. After the message headers have been written, the entire content of the HTML message body is written out to the same file, followed by an identifier for the end of this part of the message. After that close the temporary ifs file by calling closeStreamFile procedure.

            QtmmSendMail( %trim(temporaryFileName): %len(%trim(temporaryFileName))  
                                 : %trim(FromAddress): %len(%trim(FromAddress))     
                                 : recipientList: recipientCount: nullErrorDS);     

    Call the QtmmSendMail API to send our email message. Pass the parameters as temporaryFileName path and its length, Sender address and its length, recipientList data structure, recipient count and nullErrorDS.

    Write Calling program CALLPGM to call program SENDEMAIL

         DSENDEMAIL        PR                  EXTPGM('SENDEMAIL')                   
         D  FROMADDR                    100A   CONST                                 
         D  FROMNAME                    100A   CONST                                 
         D  TOADDRS                            CONST LIKEDS(EMAILADDRS) DIM(20)      
         D  SUBJECT                      80A   CONST                                 
         D  HTMLMSG                    5000A   CONST                                 
                                                                                     
          // Recipient Email Address Data Structure                                  
         D EmailAddress    DS                  qualified DIM(1)                      
         D   unused                       4b 0                                       
         D   type                         3A                                         
         D   name                       100A                                         
         D   address                     50A                                         
                                                                                     
          /free                                                                      
            emailaddress(1).type = 'TO';                                               
            emailaddress(1).name = ' ';                                                
            emailaddress(1).address = 'myeasyclasses2@gmail.com';                      
            SENDEMAIL('myeasyclasses2@gmail.com':
            'MY EASY CLASSES':              
            emailaddress:                     
            'TEST':                         
            '<h1>Test message</h1>');       
            return;                                        
          /end-free                                        

    Defined the prototype of the SendEmail program to be called as procedure and then declare the EmailAddress data structure to pass the recipient list. Assign type as TO, name as blank and recipient email address as myeasyclasses2@gmail.com. Finally call the SENDEMAIL and pass the sender email, sender name, recipient emailaddress ds and Subject and HTML message and then return from the program.

    Compile command for SendEMail program

    We will create module and program object of CALLPGM.

  • First perform, Addlible QTCP and addlible EASYCLASS1(developer library)
  • Create Module:
  • CRTSQLRPGI OBJ(EASYCLASS1/CALLPGM)        
               SRCFILE(EASYCLASS1/SEP2023)      
               SRCMBR(CALLPGM)                
               COMMIT(*NONE)                    
               OBJTYPE(*MODULE)                 
               REPLACE(*YES)                    
               DBGVIEW(*SOURCE)                 
  • Create Program:
  • CRTPGM PGM(EASYCLASS1/CALLPGM)   
           MODULE(EASYCLASS1/CALLPGM)
                  

    1 comment

    1. Excellent !!!
    © AS400 and SQL Tricks. All rights reserved. Developed by Jago Desain