Inviare email dal DB utilizzando UTL_SMTP

This article is also available in English here.

Voglio mostrare brevemente come si può utilizzare il package UTL_SMTP per inviare email da un programma PL/SQL.
Partiamo subito con un esempio:

CREATE OR REPLACE PROCEDURE SENDMAIL (dest in varchar2,msg in varchar2) is
    mailhost    VARCHAR2(10) := 'localhost';
    sender      VARCHAR2(20) := 'massimo@massimo.it';
    mail_conn   utl_smtp.connection;
    r           utl_smtp.replies;
BEGIN
    mail_conn := utl_smtp.open_connection(mailhost, 25);
    r:=utl_smtp.ehlo(mail_conn, mailhost);
    for i in r.first..r.last loop
       dbms_output.put_line('helo code='||r(i).code||' text='||r(i).text);
    end loop;
    utl_smtp.mail(mail_conn, sender);
    utl_smtp.rcpt(mail_conn, dest);
    utl_smtp.open_data(mail_conn);
    utl_smtp.write_data(mail_conn, 'From: '||sender||chr(13)|| CHR(10));
    utl_smtp.write_data(mail_conn, 'Subject: '||'messaggio di test'||chr(13)|| CHR(10));
    utl_smtp.write_data(mail_conn, 'To: '||dest||chr(13)|| CHR(10));
    utl_smtp.write_data(mail_conn, chr(13)|| CHR(10));
    utl_smtp.write_data(mail_conn, msg||chr(13)|| CHR(10));
    utl_smtp.close_data(mail_conn);
    utl_smtp.quit(mail_conn);
END;
/ 

La procedura riceve in input l’indirizzo del destinatario ed il messaggio da inviare.
Analizziamola passo per passo:

Innanzitutto viene definito il server smtp da utilizzare. Nel mio caso ho installato un smtp server locale e dunque l’indirizzo è banalmente localhost.
Poi ho cablato nel codice l’indirizzo del mittente.
La prima chiamata è l’apertura della connessione con il server smtp. E’realizzata mediante la funzione open_connection e ritorna un handler alla connessione che sarà sempre utilizzato di qui in avanti.
La chiamata successiva è il comando ehlo che inizializza la connessione avviando la fase di handshake.
Ci sono due possibili comando ehlo. Quello mostrato in esempio è il più completo perché restituisce in output una PL/SQL table che mostra tutti le caratteristiche ricevuta dal server all’handshake.
Dopo averle ricevute le stampo a video con delle DBMS_OUTPUT. L’altra possibilità era utilizzare il comando

utl_smtp.helo(mail_conn, mailhost);

Che è identico al precedente ma non ha output.

Dopo avere fatto la conoscenza col server utilizziamo il comando MAIL per avviare il processo di invio di una email.
Il successivo comando RCPT seleziona la mailbox in cui desideriamo depositare il nostro messaggio. Questo comando può essere ripetuto più volte se i destinatari del messaggio sono più di uno.
Il comando OPEN_DATA apre la session di scrittura del corpo dell’email.
Dopo la OPEN_DATA è possibile eseguire un numero a piacere di comandi WRITE_DATA chiusi dal comando COLSE_DATA.
Nel nostro caso passiamo con write successive ciò che sarà visualizzato nei campi: From, Subject e To dell’email.
La riga vuota successiva serve a chiudere l’header dell’email ed a passare al corpo effettivo.
Anche nel corpo potremmo scrivere un numero a piacere di righe, noi accettiamo il testo come parametro di input.
Come detto completiamo l’invio con il comando CLOSE_DATA e salutiamo il server con il comando QUIT.

Eseguiamo la procedura:

SQL> exec SENDMAIL('massimo.ruocchio@mymail.it','Caro amico ti scrivo...')
BEGIN SENDMAIL('massimo.ruocchio@mymail.it','Caro amico ti scrivo...'); END;

*
ERRORE alla riga 1:
ORA-24247: accesso alla rete negato dalla lista di controllo dell'accesso (ACL)
ORA-06512: a "SYS.UTL_TCP", line 17
ORA-06512: a "SYS.UTL_TCP", line 246
ORA-06512: a "SYS.UTL_SMTP", line 115
ORA-06512: a "SYS.UTL_SMTP", line 138
ORA-06512: a "MAXR.SENDMAIL", line 7
ORA-06512: a line 1

L’errore, che si verificherà solo se state utilizzando Oracle11g, è dovuto ad una configurazione di sicurezza di Oracle. Le Access Control List sono liste che controllano la possibilità da parte di un programma PL/SQL di accedere a risorse esterne.
In pratica prima che un programma PL/SQL possa accedere ad una risorsa esterna bisogna concedergliene l’utilizzo.
Bisogna creare una ACL specifica per la risorsa SMTP ed associarla al nostro server:


BEGIN
 DBMS_NETWORK_ACL_ADMIN.CREATE_ACL (
  acl          => 'SMTP_ACL.xml', 
  description  => 'ACL for SMTP',
  principal    => 'MAXR',
  is_grant     => TRUE, 
  privilege    => 'connect',
  start_date   => null,
  end_date     => null); 
END;
/

BEGIN
 DBMS_NETWORK_ACL_ADMIN.ASSIGN_ACL (
  acl         => 'SMTP_ACL.xml',
  host        => 'localhost', 
  lower_port  => 25,
  upper_port  => 25); 
END;
/

Con la prima istruzione ho creato l’ACL che viene conservata in un documento xml (SMTP_ACL.xml) e l’ho associata all’utente oracle MAXR concedendo a questo la grant di connect alla risorsa.
Con la seconda istruzione specifico quale risorsa (localhost nel mio caso) deve essere accessibile mediante l’ACL appena definita e su quale range di porte.
Fatta questa autorizzazione riprovo a mandare la mia email:

SQL> exec  SENDMAIL('massimo.ruocchio@mymail.it','Caro amico ti scrivo...')
helo code=250 text=Hello localhost
helo code=250 text=AUTH LOGIN
helo code=250 text=ENHANCEDSTATUSCODES
helo code=250 text=PIPELINING

Procedura PL/SQL completata correttamente.

L’email è correttamente arrivata a destinazione.

Per concludere una nota importante: Fino ad Oracle11gR1 UTL_SMTP non supporta connessioni SSL, quindi se volete utilizzare i server SMTP più diffusi come gmail (che appunto richiedono una connessione SSL) dovete utilizzare un server locale (ad esempio stunnel) a cui connettervi in modalità standard usando UTL_SMTP e che sia configurato in modo da girare le connessioni al server finale (ad esempio gmail) in modalità SSL.

Non ho avuto modo di provarlo ma in Oracle 11gR2 UTL_SMTP è stato implementato per gestire anche l’SSL. C’è la funzione/procedura STARTTLS:

STARTTLS Function and Procedure
This subprogram sends the STARTTLS command to secure the SMTP connection using SSL/TLS. SSL/TLS requires an Oracle wallet which must be specified when the connection was opened by the OPEN_CONNECTION Functions.

Syntax

UTL_SMTP.STARTTLS (c IN OUT NOCOPY connection) RETURN reply;

UTL_SMTP.STARTTLS (c IN OUT NOCOPY connection);

Parameters
c SMTP connection

Return Values
reply SMTP reply

Usage Notes

The STARTTLS command must only be issued on an unencrypted connection and when the SMTP server indicates the support of the command in the reply of the EHLO command. The wallet to be used for encryption must have been specified when the initial SMTP connection was opened by the OPEN_CONNECTION function.

Examples

DECLARE
c utl_smtp.connection;
BEGIN
c := utl_smtp.open_connection(
host => ‘smtp.example.com’,
port => 25,
wallet_path => ‘file:/oracle/wallets/smtp_wallet’,
wallet_password => ‘password’,
secure_connection_before_smtp => FALSE);
utl_smtp.starttls(c);
END

Alla prossima,
Massimo

Tag: , , ,

10 Risposte to “Inviare email dal DB utilizzando UTL_SMTP”

  1. FABRIZIO Says:

    Ciao,
    ho letto molto attentamente le tue spiegazioni, e le ho eseguite attentamente.
    Ma ugualmente non riesco a creare la procedura per l invio delle mail tramite oracle 10ex,

    errore rilevato:
    7 17 PLS-00201: l’identificativo ‘UTL_SMTP’ deve essere dichiarato

    • massimoruocchio Says:

      Ciao,

      in Oracle 10XE l’utilizzo di UTL_SMTP non è concesso per default a tutti gli utenti come sulle altre versioni.
      Di conseguenza devi collegarti con l’utente SYS e fare

      GRANT EXECUTE ON UTL_SMTP TO il_tuo_utente;

      Dopo dovresti poterlo utilizzare senza problemi…

      Massimo

  2. Amedeo Says:

    Ciao Massimo
    dopo tanto scrutare sulla rete sono approdato sul tuo blog(Complimenti!!) ed ho notato una soluzione ad un problema molto simile al mio.Spero davvero tu possa aiutarmi ebbene:

    invio email tramite utl_smt grazie ad un package molto famoso in rete chiamato demo_mail.
    Solo che tutto questo non funziona se parliamo di posta certificata pec su aruba.
    Ho capito che utl_smtp non supporta il protocollo SSL e tu stesso consigliavi un rimedio con STUNNEL ma come fare?
    La mia procedura passa al package demo_mail
    smtp_host VARCHAR2(256) := ‘smtps.pec.aruba.it’;
    smtp_port PLS_INTEGER := 465;

    ma stunnel come deve essere configurato? Io ho provato in questo modo ma nulla
    accept =127.0.0.1:259 connect = smtps.pec.aruba.it:465

    (questo 259 poi non saprei proprio è preso d aun esempio, so che è una porta di ascolto ma quale devo mettere?)

    Potresti aiutarmi?
    Grazie mille

    • massimoruocchio Says:

      Ciao,
      il numero di porta che metti nella accept del file di configurazione di stunnel lo puoi scegliere a piacere, è la porta su cui stunnel sarà in ascolto sulla tua macchina.
      Devi solo fare attenzione a non utilizzare una porta già usata da qualche altro software che gira sulla tua macchina.
      connect invece indica indirizzo e porta su cui è in ascolto il server remoto, Aruba nel tuo caso.
      A questo punto quando chiami utl_smtp (attraverso demo_mail) devi usare come server smtp localhost e porta 259. dunque

      smtp_host VARCHAR2(256) := ‘127.0.0.1’;
      smtp_port PLS_INTEGER := 259;

      In pratica mandi la mail a stunnel in modalità non SSL che la gira ad aruba in modalità SSL.

      Spero risolva.
      Massimo

  3. amedeo Says:

    Gentile Massimo innanzitutto ti ringrazio per l’immediata risposta, purtroppo ho ancora problemi e spero tu abbia la pazienza e la voglia di rispondermi se ti possibile. Dunque
    ho parametrizzato stunnel nel seguente modo:

    ; Use it for client mode
    client = yes
    [ssmtp]
    accept =999
    connect = smtps.pec.aruba.it:465
    sembra funzionare poiche se da riga di comando scrivo telnet localhost 999 mi risponde aruba.

    La mia procedura (demo mail) pur mettendo come porta 999 e come smtp 127.0.0.1 mi ritorna il seguente errore:
    ORA-29278: SMTP transient error: 421 Service not available
    nel package demo_mail nella parte spec ho parametrizzato ne seguente modo:
    smtp_host VARCHAR2(256) := ‘127.0.0.1’;
    smtp_port PLS_INTEGER := 999;
    smtp_domain VARCHAR2(256) := ‘127.0.0.1’;

    e nella parte body alla procedura che si occupa della connessione c’è scritto:

    FUNCTION begin_session RETURN utl_smtp.connection IS
    conn utl_smtp.connection;
    BEGIN
    conn := utl_smtp.open_connection(smtp_host, smtp_port);
    utl_smtp.helo(conn, smtp_domain);
    utl_smtp.command(conn, ‘AUTH LOGIN’);
    utl_smtp.command(conn,UTL_RAW.CAST_TO_VARCHAR2(utl_encode.base64_encode(utl_raw.cast_to_raw(‘xxx@mailpec.it’))));
    utl_smtp.command(conn,UTL_RAW.CAST_TO_VARCHAR2(utl_encode.base64_encode(utl_raw.cast_to_raw(‘xxxxpassword’))));
    RETURN conn;
    END;

    ma allora perchè non funziona?
    grazie mille
    Amedeo

    • amedeo Says:

      purtroppo neanche stunnel ci può essere d’aiuto!

      Dalla tua esperienza hai alternative strade da consigliarci.
      Grazie sempre.

      • massimoruocchio Says:

        Non ho avuto modo di provarlo ma in Oracle 11gR2 UTL_SMTP è stato implementato per gestire anche l’SSL. C’è la funzione/procedura STARTTLS:

        STARTTLS Function and Procedure
        This subprogram sends the STARTTLS command to secure the SMTP connection using SSL/TLS. SSL/TLS requires an Oracle wallet which must be specified when the connection was opened by the OPEN_CONNECTION Functions.

        Syntax

        UTL_SMTP.STARTTLS (c IN OUT NOCOPY connection) RETURN reply;

        UTL_SMTP.STARTTLS (c IN OUT NOCOPY connection);

        Parameters
        c SMTP connection

        Return Values
        reply SMTP reply

        Usage Notes

        The STARTTLS command must only be issued on an unencrypted connection and when the SMTP server indicates the support of the command in the reply of the EHLO command. The wallet to be used for encryption must have been specified when the initial SMTP connection was opened by the OPEN_CONNECTION function.

        Examples

        
        DECLARE
          c utl_smtp.connection;
        BEGIN
          c := utl_smtp.open_connection(
             host => 'smtp.example.com',
             port => 25,
             wallet_path => 'file:/oracle/wallets/smtp_wallet',
             wallet_password => 'password',
             secure_connection_before_smtp => FALSE);
          utl_smtp.starttls(c);
        END
        
        

        Se hai modo di migrare puoi dovresti risolvere…in alternativa potresti fare una stored procedure in java e quindi non utilizzare per niente UTL_SMTP.

        Massimo

  4. Aruba Says:

    Your mode of describing the whole thing in this piece of writing is truly fastidious, every one be capable
    of without difficulty be aware of it, Thanks a lot.

  5. Davide Says:

    Ciao, ho usato la procedura e ha fuzionato alla perfezione.
    Grazie per il codice.

    Una domanda…come si allega un file?

    Grazie, Davide.

Lascia un commento