The UTL_SMTP
package was introduced in Oracle 8i and can be used to send emails from PL/SQL.
Related articles.
In it's simplest form a single string or variable can be sent as the message body using the following procedure. In this case we have not included any header information or subject line in the message, so it is not very useful, but it is small.
CREATE OR REPLACE PROCEDURE send_mail (p_to IN VARCHAR2, p_from IN VARCHAR2, p_message IN VARCHAR2, p_smtp_host IN VARCHAR2, p_smtp_port IN NUMBER DEFAULT 25) AS l_mail_conn UTL_SMTP.connection; BEGIN l_mail_conn := UTL_SMTP.open_connection(p_smtp_host, p_smtp_port); UTL_SMTP.helo(l_mail_conn, p_smtp_host); UTL_SMTP.mail(l_mail_conn, p_from); UTL_SMTP.rcpt(l_mail_conn, p_to); UTL_SMTP.data(l_mail_conn, p_message || UTL_TCP.crlf || UTL_TCP.crlf); UTL_SMTP.quit(l_mail_conn); END; /
The code below shows how the procedure is called.
BEGIN send_mail(p_to => 'me@mycompany.com', p_from => 'admin@mycompany.com', p_message => 'This is a test message.', p_smtp_host => 'smtp.mycompany.com'); END; /
Multi-line messages can be written by expanding the UTL_SMTP.DATA
command using the UTL_SMTP.WRITE_DATA
command as follows. This is a better method to use as the total message size is no longer constrained by the 32K limit on a VARCHAR2
variable. In the following example the header information has been included in the message also.
CREATE OR REPLACE PROCEDURE send_mail (p_to IN VARCHAR2, p_from IN VARCHAR2, p_subject IN VARCHAR2, p_message IN VARCHAR2, p_smtp_host IN VARCHAR2, p_smtp_port IN NUMBER DEFAULT 25) AS l_mail_conn UTL_SMTP.connection; BEGIN l_mail_conn := UTL_SMTP.open_connection(p_smtp_host, p_smtp_port); UTL_SMTP.helo(l_mail_conn, p_smtp_host); UTL_SMTP.mail(l_mail_conn, p_from); UTL_SMTP.rcpt(l_mail_conn, p_to); UTL_SMTP.open_data(l_mail_conn); UTL_SMTP.write_data(l_mail_conn, 'Date: ' || TO_CHAR(SYSDATE, 'DD-MON-YYYY HH24:MI:SS') || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, 'To: ' || p_to || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, 'From: ' || p_from || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, 'Subject: ' || p_subject || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, 'Reply-To: ' || p_from || UTL_TCP.crlf || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, p_message || UTL_TCP.crlf || UTL_TCP.crlf); UTL_SMTP.close_data(l_mail_conn); UTL_SMTP.quit(l_mail_conn); END; /
The code below shows how the procedure is called.
BEGIN send_mail(p_to => 'me@mycompany.com', p_from => 'admin@mycompany.com', p_subject => 'Test Message', p_message => 'This is a test message.', p_smtp_host => 'smtp.mycompany.com'); END; /
The following procedure builds on the previous version, allowing it include plain text and/or HTML versions of the email. The format of the message is explained here.
CREATE OR REPLACE PROCEDURE send_mail (p_to IN VARCHAR2, p_from IN VARCHAR2, p_subject IN VARCHAR2, p_text_msg IN VARCHAR2 DEFAULT NULL, p_html_msg IN VARCHAR2 DEFAULT NULL, p_smtp_host IN VARCHAR2, p_smtp_port IN NUMBER DEFAULT 25) AS l_mail_conn UTL_SMTP.connection; l_boundary VARCHAR2(50) := '----=*#abc1234321cba#*='; BEGIN l_mail_conn := UTL_SMTP.open_connection(p_smtp_host, p_smtp_port); UTL_SMTP.helo(l_mail_conn, p_smtp_host); UTL_SMTP.mail(l_mail_conn, p_from); UTL_SMTP.rcpt(l_mail_conn, p_to); UTL_SMTP.open_data(l_mail_conn); UTL_SMTP.write_data(l_mail_conn, 'Date: ' || TO_CHAR(SYSDATE, 'DD-MON-YYYY HH24:MI:SS') || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, 'To: ' || p_to || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, 'From: ' || p_from || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, 'Subject: ' || p_subject || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, 'Reply-To: ' || p_from || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, 'MIME-Version: 1.0' || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, 'Content-Type: multipart/alternative; boundary="' || l_boundary || '"' || UTL_TCP.crlf || UTL_TCP.crlf); IF p_text_msg IS NOT NULL THEN UTL_SMTP.write_data(l_mail_conn, '--' || l_boundary || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, 'Content-Type: text/plain; charset="iso-8859-1"' || UTL_TCP.crlf || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, p_text_msg); UTL_SMTP.write_data(l_mail_conn, UTL_TCP.crlf || UTL_TCP.crlf); END IF; IF p_html_msg IS NOT NULL THEN UTL_SMTP.write_data(l_mail_conn, '--' || l_boundary || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, 'Content-Type: text/html; charset="iso-8859-1"' || UTL_TCP.crlf || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, p_html_msg); UTL_SMTP.write_data(l_mail_conn, UTL_TCP.crlf || UTL_TCP.crlf); END IF; UTL_SMTP.write_data(l_mail_conn, '--' || l_boundary || '--' || UTL_TCP.crlf); UTL_SMTP.close_data(l_mail_conn); UTL_SMTP.quit(l_mail_conn); END; /
The code below shows how the procedure is called.
DECLARE l_html VARCHAR2(32767); BEGIN l_html := '<html> <head> <title>Test HTML message</title> </head> <body> <p>This is a <b>HTML</b> <i>version</i> of the test message.</p> <p><img src="http://oracle-base.com/images/site_logo.gif" alt="Site Logo" /> </body> </html>'; send_mail(p_to => 'me@mycompany.com', p_from => 'admin@mycompany.com', p_subject => 'Test Message', p_text_msg => 'This is a test message.', p_html_msg => l_html, p_smtp_host => 'smtp.mycompany.com'); END; /
Sending an email with an attachment is similar to the previous example as the message and the attachment must be separated by a boundary and identified by a name and mime type.
Attaching a BLOB requires the binary data to be encoded and converted to text so it can be sent using SMTP.
CREATE OR REPLACE PROCEDURE send_mail (p_to IN VARCHAR2, p_from IN VARCHAR2, p_subject IN VARCHAR2, p_text_msg IN VARCHAR2 DEFAULT NULL, p_attach_name IN VARCHAR2 DEFAULT NULL, p_attach_mime IN VARCHAR2 DEFAULT NULL, p_attach_blob IN BLOB DEFAULT NULL, p_smtp_host IN VARCHAR2, p_smtp_port IN NUMBER DEFAULT 25) AS l_mail_conn UTL_SMTP.connection; l_boundary VARCHAR2(50) := '----=*#abc1234321cba#*='; l_step PLS_INTEGER := 12000; -- make sure you set a multiple of 3 not higher than 24573 BEGIN l_mail_conn := UTL_SMTP.open_connection(p_smtp_host, p_smtp_port); UTL_SMTP.helo(l_mail_conn, p_smtp_host); UTL_SMTP.mail(l_mail_conn, p_from); UTL_SMTP.rcpt(l_mail_conn, p_to); UTL_SMTP.open_data(l_mail_conn); UTL_SMTP.write_data(l_mail_conn, 'Date: ' || TO_CHAR(SYSDATE, 'DD-MON-YYYY HH24:MI:SS') || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, 'To: ' || p_to || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, 'From: ' || p_from || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, 'Subject: ' || p_subject || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, 'Reply-To: ' || p_from || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, 'MIME-Version: 1.0' || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, 'Content-Type: multipart/mixed; boundary="' || l_boundary || '"' || UTL_TCP.crlf || UTL_TCP.crlf); IF p_text_msg IS NOT NULL THEN UTL_SMTP.write_data(l_mail_conn, '--' || l_boundary || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, 'Content-Type: text/plain; charset="iso-8859-1"' || UTL_TCP.crlf || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, p_text_msg); UTL_SMTP.write_data(l_mail_conn, UTL_TCP.crlf || UTL_TCP.crlf); END IF; IF p_attach_name IS NOT NULL THEN UTL_SMTP.write_data(l_mail_conn, '--' || l_boundary || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, 'Content-Type: ' || p_attach_mime || '; name="' || p_attach_name || '"' || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, 'Content-Transfer-Encoding: base64' || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, 'Content-Disposition: attachment; filename="' || p_attach_name || '"' || UTL_TCP.crlf || UTL_TCP.crlf); FOR i IN 0 .. TRUNC((DBMS_LOB.getlength(p_attach_blob) - 1 )/l_step) LOOP UTL_SMTP.write_data(l_mail_conn, UTL_RAW.cast_to_varchar2(UTL_ENCODE.base64_encode(DBMS_LOB.substr(p_attach_blob, l_step, i * l_step + 1)))); END LOOP; UTL_SMTP.write_data(l_mail_conn, UTL_TCP.crlf || UTL_TCP.crlf); END IF; UTL_SMTP.write_data(l_mail_conn, '--' || l_boundary || '--' || UTL_TCP.crlf); UTL_SMTP.close_data(l_mail_conn); UTL_SMTP.quit(l_mail_conn); END; /
The code below shows how the procedure is called.
DECLARE l_name images.name%TYPE := 'site_logo.gif'; l_blob images.image%TYPE; BEGIN SELECT image INTO l_blob FROM images WHERE name = l_name; send_mail(p_to => 'me@mycompany.com', p_from => 'admin@mycompany.com', p_subject => 'Test Message', p_text_msg => 'This is a test message.', p_attach_name => 'site_logo.gif', p_attach_mime => 'image/gif', p_attach_blob => l_blob, p_smtp_host => 'smtp.mycompany.com'); END; /
Attaching a CLOB is similar to attaching a BLOB, but we don't have to worry about encoding the data because it is already plain text.
CREATE OR REPLACE PROCEDURE send_mail (p_to IN VARCHAR2, p_from IN VARCHAR2, p_subject IN VARCHAR2, p_text_msg IN VARCHAR2 DEFAULT NULL, p_attach_name IN VARCHAR2 DEFAULT NULL, p_attach_mime IN VARCHAR2 DEFAULT NULL, p_attach_clob IN CLOB DEFAULT NULL, p_smtp_host IN VARCHAR2, p_smtp_port IN NUMBER DEFAULT 25) AS l_mail_conn UTL_SMTP.connection; l_boundary VARCHAR2(50) := '----=*#abc1234321cba#*='; l_step PLS_INTEGER := 12000; -- make sure you set a multiple of 3 not higher than 24573 BEGIN l_mail_conn := UTL_SMTP.open_connection(p_smtp_host, p_smtp_port); UTL_SMTP.helo(l_mail_conn, p_smtp_host); UTL_SMTP.mail(l_mail_conn, p_from); UTL_SMTP.rcpt(l_mail_conn, p_to); UTL_SMTP.open_data(l_mail_conn); UTL_SMTP.write_data(l_mail_conn, 'Date: ' || TO_CHAR(SYSDATE, 'DD-MON-YYYY HH24:MI:SS') || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, 'To: ' || p_to || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, 'From: ' || p_from || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, 'Subject: ' || p_subject || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, 'Reply-To: ' || p_from || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, 'MIME-Version: 1.0' || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, 'Content-Type: multipart/mixed; boundary="' || l_boundary || '"' || UTL_TCP.crlf || UTL_TCP.crlf); IF p_text_msg IS NOT NULL THEN UTL_SMTP.write_data(l_mail_conn, '--' || l_boundary || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, 'Content-Type: text/plain; charset="iso-8859-1"' || UTL_TCP.crlf || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, p_text_msg); UTL_SMTP.write_data(l_mail_conn, UTL_TCP.crlf || UTL_TCP.crlf); END IF; IF p_attach_name IS NOT NULL THEN UTL_SMTP.write_data(l_mail_conn, '--' || l_boundary || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, 'Content-Type: ' || p_attach_mime || '; name="' || p_attach_name || '"' || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, 'Content-Disposition: attachment; filename="' || p_attach_name || '"' || UTL_TCP.crlf || UTL_TCP.crlf); FOR i IN 0 .. TRUNC((DBMS_LOB.getlength(p_attach_clob) - 1 )/l_step) LOOP UTL_SMTP.write_data(l_mail_conn, DBMS_LOB.substr(p_attach_clob, l_step, i * l_step + 1)); END LOOP; UTL_SMTP.write_data(l_mail_conn, UTL_TCP.crlf || UTL_TCP.crlf); END IF; UTL_SMTP.write_data(l_mail_conn, '--' || l_boundary || '--' || UTL_TCP.crlf); UTL_SMTP.close_data(l_mail_conn); UTL_SMTP.quit(l_mail_conn); END; /
The code below shows how the procedure is called.
DECLARE l_clob CLOB := 'This is a very small CLOB!'; BEGIN send_mail(p_to => 'me@mycompany.com', p_from => 'admin@mycompany.com', p_subject => 'Test Message', p_text_msg => 'This is a test message.', p_attach_name => 'test.txt', p_attach_mime => 'text/plain', p_attach_clob => l_clob, p_smtp_host => 'smtp.mycompany.com'); END; /
When dealing with multiple recipients, the UTL_SMTP.RCPT
procedure needs to be called for each recipient, whether they are a "TO", "CC" or "BCC". The destinction between the types of recipient is made in the descriptions in the WRITE_DATA
calls. The following procedure accepts comma separated "TO", "CC" and "BCC" parameters. The "TO" is mandatory, but the others are optional. If present, they are processed appropriately by splitting the strings up using the string_api package.
CREATE OR REPLACE PROCEDURE send_mail (p_to IN VARCHAR2, p_cc IN VARCHAR2 DEFAULT NULL, p_bcc IN VARCHAR2 DEFAULT NULL, p_from IN VARCHAR2, p_subject IN VARCHAR2, p_message IN VARCHAR2, p_smtp_host IN VARCHAR2, p_smtp_port IN NUMBER DEFAULT 25) AS l_mail_conn UTL_SMTP.connection; PROCEDURE process_recipients(p_mail_conn IN OUT UTL_SMTP.connection, p_list IN VARCHAR2) AS l_tab string_api.t_split_array; BEGIN IF TRIM(p_list) IS NOT NULL THEN l_tab := string_api.split_text(p_list); FOR i IN 1 .. l_tab.COUNT LOOP UTL_SMTP.rcpt(p_mail_conn, TRIM(l_tab(i))); END LOOP; END IF; END; BEGIN l_mail_conn := UTL_SMTP.open_connection(p_smtp_host, p_smtp_port); UTL_SMTP.helo(l_mail_conn, p_smtp_host); UTL_SMTP.mail(l_mail_conn, p_from); process_recipients(l_mail_conn, p_to); process_recipients(l_mail_conn, p_cc); process_recipients(l_mail_conn, p_bcc); UTL_SMTP.open_data(l_mail_conn); UTL_SMTP.write_data(l_mail_conn, 'Date: ' || TO_CHAR(SYSDATE, 'DD-MON-YYYY HH24:MI:SS') || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, 'To: ' || p_to || UTL_TCP.crlf); IF TRIM(p_cc) IS NOT NULL THEN UTL_SMTP.write_data(l_mail_conn, 'CC: ' || REPLACE(p_cc, ',', ';') || UTL_TCP.crlf); END IF; IF TRIM(p_bcc) IS NOT NULL THEN UTL_SMTP.write_data(l_mail_conn, 'BCC: ' || REPLACE(p_bcc, ',', ';') || UTL_TCP.crlf); END IF; UTL_SMTP.write_data(l_mail_conn, 'From: ' || p_from || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, 'Subject: ' || p_subject || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, 'Reply-To: ' || p_from || UTL_TCP.crlf || UTL_TCP.crlf); UTL_SMTP.write_data(l_mail_conn, p_message || UTL_TCP.crlf || UTL_TCP.crlf); UTL_SMTP.close_data(l_mail_conn); UTL_SMTP.quit(l_mail_conn); END; /
The UTL_SMTP
package requires Jserver which can be installed by running the following scripts as SYS.
SQL> @$ORACLE_HOME/javavm/install/initjvm.sql SQL> @$ORACLE_HOME/rdbms/admin/initplsj.sql
If you are attempting to use this method from Oracle 11g, you must remember to configure fine grained access to network services for the mail server.
For more information see:
Hope this helps. Regards Tim...