Emails aus einem Postfach abrufen und in der Application Express Anwendung darstellen

Wie man E-Mails aus einer Application Express-Anwendung heraus versenden kann, wird in vielen Dokumenten und sogar in der Application Express-Dokumentation beschrieben. Heute beschäftigen wir uns mit "der anderen Seite" - lesen Sie hier, wie Sie Emails aus einem (IMAP)-Postfach abholen und auf einer Application Express-Anwendungsseite darstellen. Anwendungsgebiet könnte die Startseite einer Applikation sein, die dem Anwender die aktuellsten Emails seines Postfachs anzeigt.

PL/SQL Email Client-Paket herunterladen und installieren

Ein PL/SQL-Paket zum Abholen von Mails aus einem POP3 oder IMAP-Postfach gibt es in der Datenbank nicht - PL/SQL unterstützt nur das Senden von E-Mails, nicht dagegen das Empfangen. Mit Java in der Datenbank ist dies jedoch möglich: Die Java Mail API ist komplett in der Datenbank enthalten - und das als Download verfügbare Paket MAIL_CLIENT baut darauf auf. Laden Sie dieses also von Sourceforge.net herunter (Abbildung 1).

PL/SQL Mail-Client Paket

Abbildung 1: PL/SQL Mail-Client Paket

Da das Mail-Client Paket komplett auf der Java-Engine in der Datenbank basiert, läuft dieser Tipp leider nicht in einer OracleXE-Datenbank.

Packen Sie das heruntergeladene ZIP-Archiv aus und installieren Sie das Paket mit dem Skript install.sql im Unterverzeichnis src. Es stehen zwei Installationsvarianten bereit:

  • Sie können das Paket privat im Parsing Schema Ihrer APEX-Anwendung installieren. Dann hat auch nur dieses Schema Zugriff auf das Paket. Sie benötigen jedoch in jedem Fall zusätzliche Netzwerkprivilegien, bevor eine Verbindung zum Mailserver hergestellt werden kann.
  • Der DBA kann das Paket öffentlich für alle Nutzer installieren. Lassen Sie dann zusätzlich das Skript grantPublic.sql laufen - dieses erstellt Public Synonyms und vergibt EXECUTE-Privilegien an PUBLIC. Beachten Sie, dass auch in diesem Fall zusätzliche Netzwerkprivilegien an jedes Schema, welches das Paket nutzen möchte, vergeben werden müssen.

Netzwerkprivilegien vergeben

Das Netzwerk wird von der Java-Engine in der Datenbank mit einem eigenen Security Manager vor unberechtigten Zugriffen geschützt. Da zum Abrufen von Emails auf jeden Fall eine Netzwerkverbindung erforderlich ist, ist das Mail-Client-Paket ohne diese nicht nutzbar. Das folgende kurze Skript muss vom DBA ausgeführt werden und gibt dem Parsing Schema Ihrer Anwendung (setzen Sie es bei GRANTEE ein) das für eine Netzwerkverbindung zu mailserver.mydomain.com nötige Privileg.

begin
  dbms_java.grant_permission( 
    grantee           => '[APEX-Workspace-User]', 
    permission_type   => 'SYS:java.net.SocketPermission',
    permission_name   => 'mailserver.mydomain.com', 
    permission_action => 'connect,resolve' 
  );
end;
/

Mail-Client-Paket in einer APEX-Anwendung nutzen: Anwendungsseite vorbereiten

Damit ist alles vorbereitet: Sie können sich nun in einer APEX-Anwendung mit dem Mailserver verbinden und Emails abrufen. Erzeugen Sie sich also nun eine Anwendungsseite mit einer HTML-Region und Formularelementen.

  1. PX_MAIL_HOST für den Hostnamen des Mailservers (Textfeld)
  2. PX_MAIL_PORT für den TCP/IP-Port des Mailservers (Textfeld)
  3. PX_MAIL_USER für den Usernamen, mit dem die Anmeldung erfolgen soll (Textfeld)
  4. PX_MAIL_PASSWORD (Passwort-Feld)
  5. PX_MAIL_PROTO für die Auswahl, ob POP3 oder IMAP verwendet werden soll (Radiogroup; LOV: STATIC:POP3,IMAP)
  6. PX_MAIL_SSL für die Auswahl, ob via SSL kommuniziert werden soll (Radiogroup; LOV: STATIC:Ja;1,Nein;0)
  7. PX_MAIL_COLL für den Namen der APEX-Collection, in welche die Email-Nachrichten eingelesen werden sollen.

Das (vorläufige) Ergebnis sollte wie in Abbildung 2 aussehen.

APEX-Formular - Angaben zur Verbindung mit dem Mailserver

Abbildung 2: APEX-Formular - Angaben zur Verbindung mit dem Mailserver

PL/SQL Prozesse zum Abholen der Emails erstellen

Nun wird der PL/SQL-Prozeß erstellt, der mit Hilfe des Pakets MAIL_CLIENT die Email-Nachrichten vom Server abholt. Der Prozeß muss sich zunächst am Mailserver anmelden, dann die Nachrichten holen und wieder abmelden. Die Verbindung zum Mailserver muss jedesmal neu aufgebaut werden, da eine APEX-Sitzung bei jedem Seitenaufbau mit einer anderen Datenbanksession arbeiten kann. Würde man die Verbindung zum Mailserver offenhalten, bedeutete dies, dass man unter Umständen beim nächsten Seitenaufbau eine andere Datenbanksession, damit eine andere Mailserver-Verbindung und damit ggfs. die Emails eines anderen Nutzers bekommen würde. Aus diesem Grund baut der folgende PL/SQL-Prozeß namens Mail holen, welcher beim Laden der Seite - Vor Headers abläuft, die Verbindung zum Mailserver am Ende wieder ab. Den PL/SQL-Code können Sie einfach per Copy & Paste übernehmen; achten Sie lediglich darauf, dass die Namen der APEX-Elemente mit denen auf Ihrer Seite übereinstimmen.

declare
  v_proto varchar2(100);
  v_ssl   boolean;
begin
  begin
    -- Verbindung trennen, für den Fall, dass noch eine vorhanden ist
    mail_client.disconnect_server;
  exception
    when others then null;
  end;

  -- Variablen passend für Aufruf von MAIL_CLIENT.CONNECT_SERVER setzen
  if :PX_MAIL_PROTO = 'IMAP' then
    v_proto := mail_client.protocol_imap;
  else 
    v_proto := mail_client.protocol_pop3; 
  end if;
  if :PX_MAIL_SSL = 1 then
    v_ssl := true;
  else
    v_ssl := false;
  end if;

  -- Zum Mailserver verbinden
  mail_client.connect_server(
    p_hostname => :PX_MAIL_HOST,
    p_port     => :PX_MAIL_PORT,
    p_protocol => v_proto,
    p_userid   => :PX_MAIL_USER,
    p_passwd   => :PX_MAIL_PASSWORD,
    p_ssl      => v_ssl
  );
  -- Ordner INBOX öffnen
  mail_client.open_folder('INBOX');

  -- Email-Nachrichten in eine Collection einlesen
  -- 1. Zuerst Collection leeren
  apex_collection.create_or_truncate_collection(:PX_MAIL_COLL);

  -- 2. Dann Emails per Cursor auslesen und in die Collection übertragen
  for m in ( 
    select 
      msg_number,
      subject,
      sender,
      sender_email,
      sent_date,
      deleted,
      read,
      recent,
      answered,
      content_type,
      message_size
    from table(mail_client.get_mail_headers_p())
  ) loop
    apex_collection.add_member(
      p_collection_name => :PX_MAIL_COLL, 
      p_c001 => m.msg_number, 
      p_c002 => m.subject, 
      p_c003 => m.sender, 
      p_c004 => m.sender_email, 
      p_c005 => to_char(m.sent_date, 'YYYY-MM-DD HH24:MI:SS'),
      p_c006 => m.deleted,
      p_c007 => m.read,
      p_c008 => m.recent,
      p_c009 => m.answered,
      p_c010 => m.content_type,
      p_c011 => m.message_size
    );
  end loop;

  -- Verbindung zum Mailserver trennen 
  mail_client.disconnect_server;
exception 
  when others then 
    wwv_flow.debug(sqlerrm);
end;

Exkurs: Besonderheiten bei Verwendung von SSL

Wenn Sie mit SSL arbeiten, kann es auf manchen Plattformen zu folgenden Fehlermeldungen kommen (Sie sehen die Fehlermeldungen im Debug-Modus) ...

ORA-29532: Java call terminated by uncaught Java exception: javax.mail.MessagingException: java.security.ProviderException: nextBytes() failed;

Grund ist die fehlende "Entropie" zum Generieren von Zufallszahlen. Da diese auch aus Tastatureingaben oder Mausbewegungen gewonnen wird, tritt dieser Fehler vor allem bei Servern oder virtuellen Umgebungen ohne Maus und Tastatur auf. Das Problem ist aber einfach zu beheben: Ändern Sie den PL/SQL-Code des Prozesses zum Anmelden am Mailserver wie folgt:

declare
  v_proto varchar2(100);
  v_ssl   boolean;
  v_prop  varchar2(100);
begin
  v_prop := dbms_java.set_property('java.security.egd', '/dev/urandom');
  begin
    -- Verbindung trennen, für den Fall, dass noch eine vorhanden ist
    :

Sie benötigen dann noch ein zusätzliches Java-Privileg von Ihrem DBA:

begin
  dbms_java.grant_permission( 
    grantee           => '[APEX-Workspace-User]', 
    permission_type   => 'SYS:java.util.PropertyPermission',
    permission_name   => 'java.security.egd', 
    permission_action => 'read,write' 
  );
end;
/

Inhalte der Mailbox als APEX-Bericht darstellen

Wenn Ihre APEX-Webseite aufgebaut ist, befinden sich die abgeholten Emails in einer APEX-Collection - deren Namen haben wir mit dem Textfeld PX_MAIL_COLL dynamisch gehalten. Um die Emails als Liste darzustellen, reicht nun ein einfacher Bericht (Klassisch oder Interaktiv) auf diese Collection.

select 
  c001 msg_number,
  'Eine Mail '||rownum subject,
  'Max Muster' sender,
  'max.muster@email.com' sender_email,
  to_date(c005, 'YYYY-MM-DD HH24:MI:SS') sent_date,
  c006 deleted,
  c007 read,
  c008 recent,
  c009 answered,
  c010 content_type,
  apex_util.filesize_mask(c011) message_size
from apex_collections where collection_name = v('P1_MAIL_COLL') and c006 = 'N'

Das Ergebnis kann, nachdem die eine oder andere Berichtsspalte ein wenig formatiert wurde, wie in Abbildung 3 aussehen. Beachten Sie hinsichtlich der Spalte MSG_NUMBER bitte, dass diese stets von 1 an hochgezählt wird. Wenn Sie also Nachrichten aus der Mailbox löschen, sich danach ab- und wieder anmelden, erhalten die Nachrichten eine andere MSG_NUMBER. Die Java Mail API, auf der das Paket basiert, sieht eindeutige IDs gar nicht vor. Mit der Funktion MAIL_T.GET_UID können Sie (sofern der Mailserver das anbietet), eine eindeutige ID zu einer Nachricht dennoch abrufen.

Mailbox-Inhalte als APEX-Bericht

Abbildung 3: Mailbox-Inhalte als APEX-Bericht

Weitere Überlegungen: Nachrichten löschen

Das Paket MAIL_CLIENT bietet noch wesentlich mehr Funktionalität an. So kann eine Nachricht MAIL_CLIENT.GET_MESSAGE abgerufen werden - sie steht in PL/SQL dann in einer Variable vom Typ MAIL_T zur Verfügung. Dieser stellt wiederum Funktionen zum Umgang mit der Nachricht bereit. Im folgenden sei dargestellt, wie Sie eine Email-Nachricht als gelöscht markieren können. Ändern Sie dazu das Berichts-SQL für die Mail-Übersicht wie folgt um:

select 
  apex_item.checkbox(1, c001) msg_number,
  c002 subject,
  c003 sender,
  c004 sender_email,
  to_date(c005, 'YYYY-MM-DD HH24:MI:SS') sent_date,
  c006 deleted,
  c007 read,
  c008 recent,
  c009 answered,
  c010 content_type,
  apex_util.filesize_mask(c011) message_size
from apex_collections where collection_name = v('P1_MAIL_COLL') and c006 = 'N'

Nun können die Nachrichten im Bericht einzeln angeklickt werden. Fügen Sie der Seite dann noch eine Schaltfläche zum Absenden der Seite hinzu. Ihre Seite sollte dann in etwa so aussehen:

Mailbox-Inhalte als APEX-Bericht mit Checkbox zum Löschen

Abbildung 4: Mailbox-Inhalte als APEX-Bericht mit Checkbox zum Löschen

Wenn die Schaltfläche Mail löschen geklickt wird, soll der PL/SQL-Prozeß Mail loeschen ausgeführt werden - er wird beim Weiterleiten der Seite (onSubmit) ausgeführt und enthält folgenden PL/SQL Code.

declare
  v_proto   varchar2(100);
  v_ssl     boolean;
  v_prop    varchar2(100);

  v_mail    mail_t;
begin
  v_prop := dbms_java.set_property('java.security.egd', '/dev/urandom');
  begin
    -- Verbindung trennen, für den Fall, dass noch eine vorhanden ist
    mail_client.disconnect_server;
  exception
    when others then null;
  end;

  -- Variablen passend für Aufruf von MAIL_CLIENT.CONNECT_SERVER setzen
  if :P1_MAIL_PROTO = 'IMAP' then
    v_proto := mail_client.protocol_imap;
  else 
    v_proto := mail_client.protocol_pop3; 
  end if;
  if :P1_MAIL_SSL = 1 then
    v_ssl := true;
  else
    v_ssl := false;
  end if;

  -- Zum Mailserver verbinden
  mail_client.connect_server(
    p_hostname => :P1_MAIL_HOST,
    p_port     => :P1_MAIL_PORT,
    p_protocol => v_proto,
    p_userid   => :P1_MAIL_USER,
    p_passwd   => :P1_MAIL_PASSWORD,
    p_ssl      => v_ssl
  );
  -- Ordner INBOX öffnen
  mail_client.open_folder('INBOX');

  -- Angeklickte Nachrichten holen und löschen
  for m in apex_application.g_f01.first..apex_application.g_f01.last loop
    v_mail := mail_client.get_message(apex_application.g_f01(m));
    v_mail.mark_deleted();
  end loop;

  -- Verbindung zum Mailserver trennen
  mail_client.disconnect_server;
exception 
  when others then 
    wwv_flow.debug(sqlerrm);
end;

Markieren Sie nun die eine oder andere Mail, tragen Sie das Passwort für den Mailserver ggfs. neu ein und klicken Sie auf die Schaltfläche Mail löschen. Analog dazu können Sie die Mails in einen anderen Ordner auf dem Mailserver verschieben und natürlich auch die Inhalte auslesen. Für das Auslesen der Inhalte sind folgende Funktionen wichtig:

Mit dem Paket MAIL_CLIENT und dem "Datentypen" MAIL_T haben Sie umfangreiche Möglichkeiten zum Arbeiten mit einem POP3 oder IMAP-Server. Damit können Sie Ihre Anwendungen auch in die Lage versetzen, auf Emails zu reagieren. So wird es möglich ...

  • ... Benachrichtigungen mit APEX_MAIL zu versenden ...
  • ... darin zur Antwort mit Genehmigt oder Abgeleht aufzufordern ...
  • ... mit PL/SQL und MAIL_CLIENT in einem Scheduler-Job die Mailbox zu überwachen ...
  • ... und schließlich - je nach eingegangener Email - die Aktion durchzuführen.

Zurück zur Community-Seite