5254 App APfinal
5254 App APfinal
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.
1231
tkyte@TKYTE816> begin
2 send_mail( '[email protected]',
3 '[email protected]',
4 'Hello Tom' );
5 end;
6 /
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:
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:
1232
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:
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:
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:
1233
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):
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:
1234
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!)
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.
tkyte@TKYTE816> begin
2 mail_pkg.send
3 ( p_sender_email => '[email protected]',
4 p_from => 'Oracle Database Account <[email protected]>',
5 p_to => mail_pkg.array( '[email protected]',' [email protected] ' ),
6 p_cc => mail_pkg.array( ' [email protected] ' ),
7 p_bcc => mail_pkg.array( '[email protected]' ),
8 p_subject => 'This is a subject',
9 p_body => 'Hello Tom, this is the mail you need' );
10 end;
11 /
1235
We saw above, this is what gets sent to all of these recipients, including [email protected], 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.
After you have downloaded these two sets of files – you will need to extract mail.jar from the JavaMail
APIdownload 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:
1236
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:
Where:
❑ -u sys/manager – is the user ID and password for your SYS account. Some of the packages are
protected and must be loaded as SYS.
❑ -o – is shorthand for –oci8, I am using the oci8 driver. You could use the thin driver as
well but you'll need to modify the command to do so
❑ -r – is short for –resolve. This will resolve all external references in the loaded classes
helping to verify that the loaded java classes will be able to function after we load them
❑ -v – is short for –verbose. This gives us something to do while loadjava is running. We can
see it work through each step of its process.
❑ -f – is short for –force. This isn't necessary on the first load but is OK to use. If you try a
loadjava and hit an error, you can correct it, and reload – then you would either need to use
the dropjava command to drop the jar file from the database or use –force. Using –force
just makes it easier for us.
❑ -noverify – does not attempt to verify the bytecode. You must be granted
oracle.aurora.security.JServerPermission(Verifier) to execute this option. In
addition, this option must be used in conjunction with -r. SYS has this privilege. This is
needed because the bytecode verifier will flag some issues with the mail.jar file and this
works around that issue.
❑ -synonym – creates public synonyms for these classes. Since we will not install the mail java
code we write as SYS, this allows us to 'see' the SYS loaded java classes.
❑ -g public – grants execute on these loaded classes to PUBLIC. If this is not desirable, change
the –g to be just the user you want to create the 'send mail' routines in, for example -g
UTILITY_ACCT.
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:
1237
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.
1238
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.
1239
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:
1240
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:
1241
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 /
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:
1242
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 => '[email protected]',
8 p_to => '[email protected]',
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...
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.
1243