Appendix AP

Overview

UTL_SMTP, introduced for the first time in Oracle 8.1.6, is an interface to the Simple Mail Transfer Protocol. It requires that you have an SMTP server in your network somewhere - most sites I have been to have at least one SMTP server running as it is the most popular method for sending mail.

The UTL_SMTP package is best suited for sending small, text only e-mails from the database. While its API supports the sending of attachments and everything else - it is left to you to actually encode the multi-part document - for example turning binary attachments into mime-encoded documents.

In this section we'll visit the example introduced in the DBMS_JOB section, which used UTL_SMTP, build upon it by adding additional functionality. We will also look at an alternative to UTL_SMTP that provides somewhat much more functionality - including the ability to easily send attachments with the e-mail. Since SMTP is a very low level protocol, we'll reuse existing public domain code to get an SMTP interface at much higher level - and we'll get it with very little code.

UTL_SMTP - a larger example

In the DBMS_JOB section, we explore how to send an e-mail using UTL_SMTP and 'apparently' making it execute faster by doing it asynchronously. We also made e-mail transactional in nature in that section; if you rollback, the e-mail does not get sent, if you commit - out it goes. I highly recommend the use of DBMS_JOB as a layer on your e-mail's routines for these reasons. In that section, the example UTL_SMTP routine we used was:

tkyte@TKYTE816> create or replace   2  PROCEDURE send_mail (p_sender       IN VARCHAR2,   3                       p_recipient IN VARCHAR2,   4                       p_message   IN VARCHAR2)   5  as   6     l_mailhost VARCHAR2(255) := 'yourserver.acme.com';   7     l_mail_conn utl_smtp.connection;   8  BEGIN   9     l_mail_conn := utl_smtp.open_connection(l_mailhost, 25);  10     utl_smtp.helo(l_mail_conn, l_mailhost);  11     utl_smtp.mail(l_mail_conn, p_sender);  12     utl_smtp.rcpt(l_mail_conn, p_recipient);  13     utl_smtp.open_data(l_mail_conn );  14     utl_smtp.write_data(l_mail_conn, p_message);  15     utl_smtp.close_data(l_mail_conn );  16     utl_smtp.quit(l_mail_conn);  17  end;  18  / Procedure created.      tkyte@TKYTE816> begin   2          send_mail( 'me@acme.com',   3                     'you@acme.com',   4                     'Hello Tom' );   5  end;   6  /      PL/SQL procedure successfully completed. 

This works OK but is very limited in nature. It sends e-mail to exactly one recipient, you cannot CC (Carbon Copy) or BCC (Blind Carbon Copy) anyone, you cannot setup a subject; the e-mail always arrives with a 'blank' subject line. We would like to support more options with this package.

A full discussion of all of the possibilities with UTL_SMTP would require in depth knowledge of the SMTP protocol itself - something that is outside the scope of this book. Readers interested in all of the opportunities available with SMTP should review RFC812 - which is the description of SMTP. This is available online at http://www.faqs.org/rfcs/rfc821.html Below, I will simply present how to send an e-mail using UTL_SMTP that supports:

  • Multiple 'To' recipients.

  • Multiple 'CC' recipients.

  • Multiple 'BCC' recipients.

  • A single body of up to 32 KB in size.

  • A subject line.

  • A descriptive 'from' line (instead of showing just the e-mail address as the 'from' in the e-mail client).

A specification for a PL/SQL package that supports this might look like the following. Here, we define an array type to allow a caller to easily send a list of recipients as well as provide the external specification of the PL/SQL routine we will be implementing:

tkyte@TKYTE816> create or replace package mail_pkg   2  as   3          type array is table of varchar2(255);   4   5          procedure send( p_sender_e-mail in varchar2,   6                          p_from         in varchar2,   7                          p_to           in array default array(),   8                          p_cc           in array default array(),   9                          p_bcc          in array default array(),  10                          p_subject      in varchar2,  11                          p_body         in long );  12  end;  13  / Package created. 

The package body for this implementation is relatively straightforward - if understand just enough of the SMTP protocol and what an e-mail looks like (how e-mail clients get the From, To, CC and so on). Before we look at the code, we'll look at what an e-mail might actually look like. Consider the following ASCII text:

From: Oracle Database Account <me@acme.com> Subject: This is a subject To: you@acme.com, us@acme.com Cc: them@acme.com      Hello Tom, this is the mail you need 

That is what you would transmit as the body of the e-mail using UTL_SMTP to have the e-mail client set the From, Subject, and so on. There are no SMTP commands to do this piece of 'magic', rather, this header information is placed right in the body of the e-mail itself; separated from the text of the e-mail by a blank line. Once we understand this, sending an e-mail with all of the options that we need is pretty easy. The only thing we need to understand beyond that is that in order to send the e-mail to more then one recipient, we simply call UTL_SMTP.RCPT more then once - with different names. That's all of the information we need to know then to send an e-mail.

So, here is the package body. We start with a couple of constants and global variables. You will of course need to change the g_mailhost to be the name of a server you have access to, in this code I have given it a generic name; yourserver.acme.com:

tkyte@TKYTE816> create or replace package body mail_pkg   2  as   3   4  g_crlf        char(2) default chr(13)||chr(10);   5  g_mail_conn   utl_smtp.connection;   6  g_mailhost    varchar2(255) := 'yourserver.acme.com';   7 

Next, we have an internal (unpublished) function to send an e-mail to many recipients - it in effect addresses the e-mail. At the same time, it builds the To: or CC: lines that we will eventually send as part of the e-mail itself and returns that formatted string. It was implemented as a separate function since we need to do this separately for the To, CC, and BCC lists:

  8  function address_email( p_string in varchar2,   9                          p_recipients in array ) return varchar2  10  is  11      l_recipients long;  12  begin  13     for i in 1 .. p_recipients.count  14     loop  15        utl_smtp.rcpt(g_mail_conn, p_recipients(i) );  16        if ( l_recipients is null )  17        then  18            l_recipients := p_string || p_recipients(i) ;  19        else  20            l_recipients := l_recipients || ', ' || p_recipients(i);  21        end if;  22     end loop;  23     return l_recipients;  24  end;  25  26 

Now we have the implementation of our published function, the one that people will actually call to send mail. It starts with an internal procedure writeData that is used to simplify the sending of the e-mail headers (the To:, From:, Subject: records). If the header record is not Null, this routine will use the appropriate UTL_SMTP call to send it - along with the necessary end of line marker (the carriage return/line feed):

 27  procedure send( p_sender_email in varchar2,  28                  p_from         in varchar2 default NULL,  29                  p_to           in array default array(),  30                  p_cc           in array default array(),  31                  p_bcc          in array default array(),  32                  p_subject      in varchar2 default NULL,  33                  p_body         in long  default NULL )  34  is  35      l_to_list   long;  36      l_cc_list   long;  37      l_bcc_list  long;  38      l_date      varchar2(255) default  39                  to_char( SYSDATE, 'dd Mon yy hh24:mi:ss' );  40  41      procedure writeData( p_text in varchar2 )  42      as  43      begin  44          if ( p_text is not null )  45          then  46              utl_smtp.write_data( g_mail_conn, p_text || g_crlf );  47          end if;  48      end; 

Now we are ready to actually send the mail. This part is not very different from the very simple routine we started with. It begins in exactly the same fashion, by connecting to the SMTP server and starting a session:

 49  begin  50      g_mail_conn := utl_smtp.open_connection(g_mailhost, 25);  51  52      utl_smtp.helo(g_mail_conn, g_mailhost);  53      utl_smtp.mail(g_mail_conn, p_sender_email);  54 

Here is where it differs, instead of calling UTL_SMTP.RCPT once; it uses the address_email function to call it (potentially) many times, building the To: and CC: list for us as well. It builds the BCC: list but we won't actually send that (we don't want the recipients to see that list!)

 55      l_to_list  := address_email( 'To: ', p_to );  56      l_cc_list  := address_email( 'Cc: ', p_cc );  57      l_bcc_list := address_email( 'Bcc: ', p_bcc );  58 

Now, we use the OPEN_DATA call to start sending the body of the e-mail. The code on lines 61 through to 68 generates the header section of data. Line 69 sends the body of the e-mail (the contents of the e-mail) and line 70 terminates the e-mail for us.

 59      utl_smtp.open_data(g_mail_conn );  60  61      writeData( 'Date: ' || l_date );  62      writeData( 'From: ' || nvl( p_from, p_sender_email ) );  63      writeData( 'Subject: ' || nvl( p_subject, '(no subject)' ) );  64  65      writeData( l_to_list );  66      writeData( l_cc_list );  67  68      utl_smtp.write_data( g_mail_conn, '' || g_crlf );  69      utl_smtp.write_data(g_mail_conn, p_body );  70      utl_smtp.close_data(g_mail_conn );  71      utl_smtp.quit(g_mail_conn);  72  end;  73  74  75  end;  76  / Package body created. 

Now I can test this API like this:

tkyte@TKYTE816> begin   2      mail_pkg.send   3      ( p_sender_email => 'me@acme.com',   4        p_from => 'Oracle Database Account <me@acme.com>',   5        p_to => mail_pkg.array( 'you@acme.com',' us@acme.com ' ),   6        p_cc => mail_pkg.array( ' them@acme.com ' ),   7        p_bcc => mail_pkg.array( 'noone@dev.null' ),   8        p_subject => 'This is a subject',   9        p_body => 'Hello Tom, this is the mail you need' );  10  end;  11  /      PL/SQL procedure successfully completed. 

And that call is exactly what generated the ASCII text:

Date: 13 May 01 12:33:22 From: Oracle Database Account <me@acme.com Subject: This is a subject To: you@acme.com, us@acme.com Cc: them@acme.com      Hello Tom, this is the mail you need 

We saw above, this is what gets sent to all of these recipients, including noone@dev.null, although we cannot see that recipient since it was on the BCC: line.

This covers most of the typical uses of the UTL_SMTP supplied package. Earlier I did say it is capable of sending e-mail with attachments and such but this would require an inordinate amount of effort on our part. We would have to:

That would be (conservatively) a couple of hundred, if not thousands of lines of PL/SQL code. Rather then do this; I will suggest that you use the already written and very robust JavaMail API as described below.

Loading and using the JavaMail API

In order to use the UTL_SMTP package, you must already have a Java enabled database in Oracle 8i. This is because UTL_SMTP relies on UTL_TCP and UTL_TCP which in turn are built on Java functions. (Remember, if you don't have a Java enabled database you can use UTL_HTTP (see that section) to send simple e-mails). So, if you are able to use UTL_SMTP, we can go to the Sun website and download their JavaMail API. This will give us the ability to send much more complicated e-mails from the database; including attachments. The following is based on work performed by a co-worker of mine, Mark Piermarini who helps me out with lots of my Java issues.

If you go to http://java.sun.com/products/javamail/index.html you'll be able to download their JavaMail API. The download you get will consist of a couple of hundred files; only one of which we are interested in. After you download the JavaMail API - make sure also to get their the JavaBeansTM Activation Framework extension or JAF (javax.activation). This is needed to run the JavaMail API package.

After you have downloaded these two sets of files - you will need to extract mail.jar from the JavaMail API download and activation.jar from the JAF download. This is all you will need from this - feel free to read through the documentation, there is a lot of functionality in there we are not using, we are just using the 'send an e-mail' part of the API. The API includes functions for receiving mail as well from IMAP, POP, and other sources.

We will need to load the mail.jar and activation.jar into the database using loadjava but before we can do that we must repackage them. These jar files are compressed in a format that is not understood by the database byte code interpreter. You need to 'unjar' and 'rejar' them without compression or use a tool such as WinZip to 'rejar' them into a zip file. What I did on Windows 2000 was:

  1. Used WinZip to extract the contents of mail.jar into my c:\temp\mail directory

  2. Used WinZip to create a new archive c:\temp\mail8i.zip

  3. Put the contents of c:\temp\mail\*.* including subdirectories into this new archive

I did the same thing for activation.jar - only replacing mail with activation in the above steps. Now we are ready to load these zip (or jar files, whatever you named them) into the database. These files need to be loaded into the database using the SYS user since they have 'protected' Java packages that regular users cannot upload. We will use the loadjava commands:

loadjava -u sys/manager -o -r -v -f -noverify -synonym -g public activation8i.zip loadjava -u sys/manager -o -r -v -f -noverify -synonym -g public mail8i.zip 

Where:

You can find out more about loadjava and the above options in the Oracle8i Java Developers Guide.

After these packages are loaded, we are ready to create a Java stored procedure to actually send the mail. This procedure will act as a thin layer on top of the JavaMail API and will let us ultimately write a PL/SQL binding layer with the following spec:

tkyte@TKYTE816> desc send FUNCTION send RETURNS NUMBER  Argument Name                  Type                    In/Out Default?  ------------------------------ ----------------------- ------ --------  P_FROM                         VARCHAR2                IN  P_TO                           VARCHAR2                IN  P_CC                           VARCHAR2                IN  P_BCC                          VARCHAR2                IN  P_SUBJECT                      VARCHAR2                IN  P_BODY                         VARCHAR2                IN  P_SMTP_HOST                    VARCHAR2                IN  P_ATTACHMENT_DATA              BLOB                    IN  P_ATTACHMENT_TYPE              VARCHAR2                IN  P_ATTACHMENT_FILE_NAME         VARCHAR2                IN 

This function will give us the ability to use CC's and BCC's and send an attachment. It is left as an exercise for the reader to implement passing arrays of BLOBs or overloading this to support CLOB or BFILE types for attachments as well.

The Java stored procedure we will create follows. It uses the basic functionality of the JavaMail API class and is relatively straightforward. Again, we are not going into all of the uses of the JavaMail API (that could be a book in itself), just the basics here. The mail class below has a single method; send. This is the method we will use to send a message. As it is implemented, it returns the number 1 if it is successful in sending the mail and a 0 otherwise. This implementation is very basic - it could be much more sophisticated, providing support for many attachment types (CLOBs, BFILEs, LONGs, and so on). It could also be modified to report back to the caller the exact error received from SMTP such as invalid recipient, no transport.

tkyte@TKYTE816> create or replace and compile   2  java source named "mail"   3  as   4  import java.io.*;   5  import java.sql.*;   6  import java.util.Properties;   7  import java.util.Date;   8  import javax.activation.*;   9  import javax.mail.*;  10  import javax.mail.internet.*;  11  import oracle.jdbc.driver.*;  12  import oracle.sql.*;  13  14  public class mail  15  {  16    static String dftMime = "application/octet-stream";  17    static String dftName = "filename.dat";  18  19    public static oracle.sql.NUMBER  20                         send(String from,  21                              String to,  22                              String cc,  23                              String bcc,  24                              String subject,  25                              String body,  26                              String SMTPHost,  27                              oracle.sql.BLOB attachmentData,  28                              String attachmentType,  29                              String attachmentFileName) 

The above argument list matches up with the SQL call specification we outlined above - the arguments are mostly self-explanatory. The two that need some clarification are the attachmentType and the attachmentFileName. The attachmentType should be a MIME (Multi-purpose Internet Mail Extensions) type - as you may be familiar with, from HTML documents. The MIME type of a GIF image for example is image/gif, the mime type of a plain text document would be text/plain, and a HTML attachment would be text/html. The attachmentFileName in this example is not the name of an existing OS file that would be attached but rather the filename of the attachment in the e-mail itself - what the recipient of this e-mail will see the name of the attachment as. The actual attachment is the oracle.sql.BLOB that is sent to this routine. Now, onto the body of the code. We begin by setting the session property mail.smtp.host to the name of the SMTP host the caller sent to us - the JavaMail API reads this value when deciding what SMTP server to connect to:

 30    {  31      int rc = 0;  32  33      try  34      {  35        Properties props = System.getProperties();  36        props.put("mail.smtp.host", SMTPHost);  37        Message msg =  38          new MimeMessage(Session.getDefaultInstance(props, null));  39 

Next, we set up the e-mail headers. This part tells the JavaMail API who the message is from, who to send it to, who to send a carbon copy (CC) or blind carbon copy (BCC), what the subject of the e-mail is and what date should be associated with the e-mail:

 40       msg.setFrom(new InternetAddress(from));  41  42       if (to != null && to.length() > 0)  43          msg.setRecipients(Message.RecipientType.TO,  44                            InternetAddress.parse(to, false));  45  46       if (cc != null && cc.length() > 0)  47          msg.setRecipients(Message.RecipientType.CC,  48                            InternetAddress.parse(cc, false));  49  50       if (bcc != null && bcc.length() > 0)  51          msg.setRecipients(Message.RecipientType.BCC,  52                            InternetAddress.parse(bcc, false));  53  54       if ( subject != null && subject.length() > 0 )  55            msg.setSubject(subject);  56       else msg.setSubject("(no subject)");  57  58        msg.setSentDate(new Date());  59 

Next, we use one of two methods to send an e-mail. If the attachmentData argument is not Null, then we will MIME encode the e-mail - a standard that supports the sending of attachments and other multi-part documents. We do this by setting up multiple MIME body parts - in this case two of them, one for the body of the e-mail (the text) and the other for the attachment itself. Lines 76 through 78 need a little additional explanation. They are how we can send an e-mail via a BLOB. The JavaMail API doesn't understand the oracle.sql.BLOB type natively (it is after all a generic API). In order to send the BLOB attachment, we must provide a method for the JavaMail API to get at the BLOB data. We accomplish that by creating our own DataHandler - a class with an interface that the JavaMail API understands how to call in order to get data to populate the attachment. This class (BLOBDataHandler) is implemented by us as a nested class below.

 60        if (attachmentData != null)  61        {  62          MimeBodyPart mbp1 = new MimeBodyPart();  63          mbp1.setText((body != null ? body : ""));  64          mbp1.setDisposition(Part.INLINE);  65  66          MimeBodyPart mbp2 = new MimeBodyPart();  67          String type =  68              (attachmentType != null ? attachmentType : dftMime);  69  70          String fileName = (attachmentFileName != null ?  71                             attachmentFileName : dftName);  72  73          mbp2.setDisposition(Part.ATTACHMENT);  74          mbp2.setFileName(fileName);  75  76          mbp2.setDataHandler(new  77             DataHandler(new BLOBDataSource(attachmentData, type))  78          );  79  80          MimeMultipart mp = new MimeMultipart();  81          mp.addBodyPart(mbp1);  82          mp.addBodyPart(mbp2);  83          msg.setContent(mp);  84        } 

If the e-mail does not have an attachment - setting the body of the e-mail is accomplished very simply via the single call to setText:

 85        else  86        {  87          msg.setText((body != null ? body : ""));  88        }  89        Transport.send(msg);  90        rc = 1;  91      } catch (Exception e)  92      {  93        e.printStackTrace();  94        rc = 0;  95      } finally  96      {  97        return new oracle.sql.NUMBER(rc);  98      }  99    } 100 

Now for our nested class BLOBDataSource. It simply provides a generic interface for the JavaMail API to access our oracle.sql.BLOB type. It is very straightforward in its implementation:

101    // Nested class that implements a DataSource. 102    static class BLOBDataSource implements DataSource 103    { 104      private BLOB   data; 105      private String type; 106 107    BLOBDataSource(BLOB data, String type) 108      { 109          this.type = type; 110          this.data = data; 111      } 112 113      public InputStream getInputStream() throws IOException 114      { 115        try 116        { 117          if(data == null) 118            throw new IOException("No data."); 119 120          return data.getBinaryStream(); 121        } catch(SQLException e) 122        { 123          throw new 124          IOException("Cannot get binary input stream from BLOB."); 125        } 126      } 127 128      public OutputStream getOutputStream() throws IOException 129      { 130        throw new IOException("Cannot do this."); 131      } 132 133      public String getContentType() 134      { 135        return type; 136      } 137 138      public String getName() 139      { 140        return "BLOBDataSource"; 141      } 142    } 143  } 144  /      Java created. 

Now that we have the Java class created for PL/SQL to bind to, we need to create that binding routine to map the PL/SQL types to their Java types and to bind the PL/SQL routine to this Java class. This is simply done by the following:

tkyte@TKYTE816> create or replace function send(   2      p_from                  in varchar2,   3      p_to                    in varchar2,   4      p_cc                    in varchar2,   5      p_bcc                   in varchar2,   6      p_subject               in varchar2,   7      p_body                  in varchar2,   8      p_smtp_host             in varchar2,   9      p_attachment_data       in blob,  10      p_attachment_type       in varchar2,  11      p_attachment_file_name  in varchar2) return number  12  as  13  language java name 'mail.send( java.lang.String,  14                                 java.lang.String,  15                                 java.lang.String,  16                                 java.lang.String,  17                                 java.lang.String,  18                                 java.lang.String,  19                                 java.lang.String,  20                                 oracle.sql.BLOB,  21                                 java.lang.String,  22                                 java.lang.String  23                               ) return oracle.sql.NUMBER';  24  /      Function created. 

Now, the very last thing we must do before using this is to ensure our user (the owner of the above mail class and send stored procedure) has sufficient privileges to execute the routine. These would be the following:

sys@TKYTE816> begin   2    dbms_java.grant_permission(   3       grantee => 'USER',   4       permission_type => 'java.util.PropertyPermission',   5       permission_name => '*',   6       permission_action => 'read,write'   7    );   8    dbms_java.grant_permission(   9       grantee => 'USER',  10       permission_type => 'java.net.SocketPermission',  11       permission_name => '*',  12       permission_action => 'connect,resolve'  13    );  14  end;  15  /      PL/SQL procedure successfully completed. 

Note that in the grant on java.net.SocketPermission, I used a wildcard in the permission_name. This allows USER to connect to and resolve any host. Technically, we could put in there just the name of the SMTP server we will be using. This would be the minimal grant we need. It is needed in order to resolve the hostname of our SMTP host and then connect to it. The other permission, java.util.PropertyPermission, is needed in order to set the mail.smtp.host in our sessions properties.

Now we are ready to test. I reused some code from the DBMS_LOB section where we had a routine load_a_file. I modified that and the DEMO table to have a BLOB column instead of a CLOB and loaded the mail8i.zip file we loaded in as a class into this demo table. Now I can use the following PL/SQL block to send it to myself as an attachment in an e-mail from the database:

tkyte@TKYTE816> set serveroutput on size 1000000 tkyte@TKYTE816> exec dbms_java.set_output( 1000000 )      tkyte@TKYTE816> declare   2    ret_code number;   3  begin   4    for i in (select theBlob from demo )   5    loop   6      ret_code := send(   7                    p_from => 'me@acme.com',   8                    p_to => 'you@acme.com',   9                    p_cc => NULL,  10                    p_bcc => NULL,  11                    p_subject => 'Use the attached Zip file',  12                    p_body => 'to send email with attachments....',  13                    p_smtp_host => 'yourserver.acme.com',  14                    p_attachment_data => i.theBlob,  15                    p_attachment_type => 'application/winzip',  16                    p_attachment_file_name => 'mail8i.zip');  17      if ret_code = 1 then  18        dbms_output.put_line ('Successfully sent message...');  19      else  20        dbms_output.put_line ('Failed to send message...');  21      end if;  22    end loop;  23  end;  24  / Successfully sent message...      PL/SQL procedure successfully completed. 

You definitely want to set serverouput on and call the DBMS_JAVA.SET_OUTPUT routine when testing this. This is because the exception is being printed by the Java stored procedure to System.out and by default that will go into a trace file on the server. If you want to see any errors in your SQL*PLUS session, you need to make these two settings. It will be very useful for debugging purposes!

Summary

In this section, we briefly reviewed the existing UTL_SMTP package. Here we have seen how to send e-mails to multiple recipients with a custom From: and Subject: header. This should satisfy most people's needs for sending e-mail from the database. UTL_SMTP is good for sending simple text only e-mails but sending attachments or complex e-mails is beyond its capabilities (unless you want to encode the entire e-mail yourself). In the cases where you need this additional sophistication, we looked at how to use the JavaMail API. Since Sun has graciously supplied us with all of the logic we would need to do this - we'll just reuse their code. This section has demonstrated not only how to send mail but a powerful side effect of having Java as an alternative stored procedure language. Now you can use the entire set of public domain code and class libraries that are available. We can enable the database to do many things that were not previously possible. In fact, the PL/SQL developers at Oracle used the same technique themselves. UTL_TCP is built on Java itself in Oracle 8i.



Expert One on One Oracle
Full Frontal PR: Getting People Talking about You, Your Business, or Your Product
ISBN: 1590595254
EAN: 2147483647
Year: 2005
Pages: 41
Authors: Richard Laermer, Michael Prichinello
BUY ON AMAZON

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net