Benutzer-Werkzeuge

Webseiten-Werkzeuge


prog:apex_mail_template_22_2

Oracle Apex 22.2.3 - APEX Mail Templates einsetzen - Wie meist sieht es viel einfacher aus als es am Ende ist

Die Probleme mit der API:

Große Probleme:

1) Mail Subject Text im Header von Outlook mit Umlauten wird nicht richtig dargestellt; Mit neuen Templates nach dem Upgrade auf 22.2.3 erstellt, oder Template wurde aktualisiert.

 Fehlerhafte Darstellung in APEX 22.2

Das Text für das Feld „EMail Subject“ Template dazu: Dies ist das Newsletter äöo subject - #MAIL_SUBJECT#.

Der erste Teil des Strings diente nur dazu zu beweisen das Umlaute an sich übertragen werden können, das Problem also in dem Parsen von #MAIL_SUBJECT# liegen muss.

2) Inline Images werden in der Apple Welt leider inline und dann nochmal als Anhang geöffnet angezeigt, in der MS Welt klappt es in Outlook aber perfekt.

Kleine / keine Probleme

3) Wird das ganze über SQL*Developer/SQL*Plus getestet, muss zuvor eine APEX Session gesetzt werden. Nach dem ersten Aufruf eines Templates wird diese in der Session gecached und nicht mehr neu aus der Definition geladen. Das macht ja auch Sinn wenn eine große Anzahl von Mails versandt werden, diese nicht immer neu aus den Metadaten zu lesen. Allerdings muss dann beachtet werden, das bei jeder Änderung man sich auch wieder aus- und einloggt um eine saubere neue Session zu erhalten, sonst wundert man sich warum sich in der Mail gar nichts verändert.

Lösung:

Für alles gibt es eine Lösung…

Zu 1) Sogar relativ einfach, im Subjekt muss mit !RAW (#MAIL_SUBJECT!RAW#) verhindert werden das die Substitution nochmal geparst wird!

Zu 2) Kurzfristig auf inline verzichten und mittelfristig Oracle nerven, das die Mail RFC kleinteilig besser umgesetzt wird.

zu 3) Das muss man einfach nur wissen, dann spart man sich etwas Zeit beim Verstehen warum Änderungen am Template nicht gleich sichtbar werden.



Voraussetzung

Anbindung E-Mail Relay, am besten verschlüsselt ⇒ Oracle Apex 5.0 Mail Versandt mit SSL -Hinterlegen einer ACL's in 11g und 12c und Hinterlegen von SSL Zertifkaten

Anzahl der Mails pro Tag auf Instance Ebene höher setzen, Default 1000, für produktive Umgebung meist zu klein. Einstellung auf der APEX Instance:  Mail Settings auf Instance Ebene

Prüfen ob der APEX Mail Job alle 5 Minuten auch wirklich regelmäßig zur erwarten Zeit läuft; Problem können zum Beispiel ein zu niedrige JOB_QUEUE Parameter Eintrag sein.


Der Weg zum Ziel

Mail Template in APEX anlegen

Ab Apex 22.3 scheint sich das Mail Template grundlegende geändert zu haben, alte Mail Templates unterstützen keine „Template Directives“, mit diesen „Template Directives“ kann zusätzliche Logik in das Templating (if then / else /case etc.) in so einer Art PHP Stil eingebettet werden.

Als Template kann ein reines Text Template hinterlegt werden oder auch ein komplexes HTML Template bei hohen Layout Anforderungen. Alternativ solle auch bei einem HTML Template der Texte Template Bereich gefüllt werden, kann der E-Mail Client keine HTML oder wurde das dort verboten, kann der E-Mail Client beim Empfänger auf TEXT zurückgreifen.

Das besondere in >22.3 in der Subjekt Zeile

Durch das interessante Feature „Template Directives“ hat sich das Parsing Verhalten verändert. Per Default werden Umlaute nun in HTML Notation umgewandelt. Mit dieser kann der E-Mail Client aber nichts anfangen und zeigt keine Umlaute mehr an (siehe Screenshot oben).

Das betrifft nur neue oder nicht migrierte Templates und führt dazu das schnell ein halber Tag verbraucht wird weil die Umlaute nicht dargestellt werden im neuen Code, in der anderen App mit ähnlichen Code aber alles funktioniert.

D.H. im Subjekt muss mit !RAW (#MAIL_SUBJECT!RAW#) verhindert werden das die Substitution nochmal geparst wird-

Ist der Fehler erkannt ist er schnell gelöst, mit dem ! Syntax in Ersetzungsvariablen lässt sich der Text im Original ausgeben und das Problem ist behoben. Wäre schön wenn das in der Online Hilfe gestanden wäre, so Effekte dämpfen doch die APEX Begeisterung kurzfristig wieder sehr stark.

Siehe dazu Controlling Output Escaping in Substitution Strings ⇒ https://docs.oracle.com/en/database/oracle/application-express/20.1/htmdb/understanding-substitution-strings.html#GUID-CA3ABA44-D03D-4396-A527-B160F1FFE933

Das HTML Template

Das HTML Template besteht aus mehreren Bereichen, auf die sich im Advanced Template dann wieder referenzieren läßt.

 APEX Mail Template - Zusammenhang der Verschiedenen Html Template Bereiche

Damit lässt sich das Template besser strukturieren.

Mit der APEX Methode pex_mail.send mit dem Parameter p_placeholders können nun die Platzhalter im Template über einen JSON Record mit allen Ersetzungstexten „ausgefüllt“ werden.

Das funktioniert auch wie beschrieben; wie das aufgerufen werden muss wird im Template sogar generiert:

BEGIN
    apex_mail.send (
        p_to                 => email_address_of_user,
        p_template_static_id => 'MYTEMPLATE',
        p_placeholders       => '{' ||
        '    "TITEL":' || apex_json.stringify( some_value ) ||
        '}' );
END;

Wie das prinzipell funktioniert ist auf folgenden Seiten auch gut beschrieben:


Inline Image in der Mail einbetten

Im nächsten Schritt war die Idee die Bilder in dem HTML Template in die Mail einzubinden, dann ist kein Nachladen von Bildern erforderlich und Datenschutz Einstellungen etc. sind nicht mehr so technisch wichtig.

Seit APEX 21.2 wird es auch etwas von APEX unterstützt, mit einen Einfachen Metoden Aufruf lassen sich Bilder in die E-Mail integrieren, die ID des Bildes wird zuvor mit eine eigenen Directive src=„cid:xxx“ refenziert.

Wie:

<img src="cid:my_company.jpg" alt="Company Logo" >

Nun muss das Bild mit der ID my_company.jpg in der Mail eingebettet werden und dann kann oder kann auch nicht der E-Mail Client das richtig anzeigen.

Ob das wie gewünscht klappt wird leider im Zusammenspiel der Implementierung des ganzen in APEX und der Implementierung im Mail Klient ermittelt.

Die aktuelle Implementierung in APEX triff anscheinend recht gut die Interpretation des Standards dahinter im MS Outlook Client.

Allerdings scheint dies wiederum nicht für den Standard E-Mail Client auf einen Apple Endgerät (Notebook/Smartphone) zutreffen. Die Inline Images werden zwar in der HTML ohne Fehler angezeigt, aber am Ende der Mail nochmals aufgelistet für den Download und erscheinen damit doppelt in der Mail.

Die Protokolle für E-Mail sind schon relativ lange im Einsatz und wurden schon immer sehr variabel ausgelegt. Wer nun hier an welcher Stelle was tun muss das es am Ende geht, ist wohl auch nicht trivial.

Siehe dazu https://stackoverflow.com/questions/3902455/mail-multipart-alternative-vs-multipart-mixed , dort findet sich ein gutes Diagramm bzgl. der unterschiedlichen Reihenfolgen mit denen eine Multipart Message aufgebaut werden kann.

Allerdings zeigen Tests mit Mail aus Apple Quellen, das es besser gehen kann, diese Mails mit Inline Images werden auf beiden System korrekt angezeigt.

Code zum Anbinden der Bilder, in dem Fall werden alle Bilder in einem Static Files Ordner an das Template gehängt:

..
IS
..
   cursor c_news_letter_images(p_app_id NUMBER)
   IS
 	 SELECT REPLACE(filename,'newsletter_images/','') AS filename
		  , blob_content
		  , mime_type
		  , REPLACE(filename,'newsletter_images/','') AS content_id
	 FROM apex_application_files
	WHERE flow_id = p_app_id
	  AND filename LIKE 'newsletter_images/%';
..
BEGIN
 
 
 
    FOR rec IN c_news_letter_images(p_app_id => p_app_id)
	loop	 
		 apex_mail.add_attachment( 
			      p_mail_id      => v_mail_id
 				, p_attachment   => rec.blob_content
				, p_filename     => rec.filename
				, p_mime_type    => rec.mime_type
				, p_content_id   => rec.content_id     
			);
  END loop;
...
END;

siehe dazu ⇒ https://docs.oracle.com/en/database/oracle/apex/22.1/aeapi/ADD_ATTACHMENT-Procedure-Signature-1.html#GUID-5B514926-2C0A-40E3-82BB-7E357CB0C927


Attatchement anbinden

Über den Aufruf von „apex_mail.add_attachment“ können dann Dateien der Mail hinzugefügt werden.

..
IS
  CURSOR c_get_attachment(p_file_id NUMBER)
      IS	  
	  SELECT f.FILENAME
           , f.MIMETYPE
           , f.FILE_CONTENT
	   FROM DOK_FILES f 
          WHERE f.ID = p_file_id ;
 
BEGIN
..
 
     FOR file_rec IN c_get_attachment( p_file_id => p_file_id )
     LOOP
          apex_mail.add_attachment(
                p_mail_id    => v_mail_id,
                p_attachment => file_rec.FILE_CONTENT,
                p_filename   => file_rec.FILENAME,
                p_mime_type  => file_rec.MIMETYPE);      
      END LOOP;         
 
...
 
END;

Siehe dazu ⇒ https://docs.oracle.com/en/database/oracle/apex/22.1/aeapi/ADD_ATTACHMENT-Procedure-Signature-2.html#GUID-0C43024A-D0B5-4358-A43E-8BF8FAD30E08



Ein generelles Beispiel

Hier ein Beispiel am Stück, ohne großes Logging und Exception Handling, in der Praxis würde das entsprechend optimiert in eine Package ausgelagert und mit logger erweitert.

 
DECLARE
   -- get inline Images from Apex Static Resource
   cursor c_news_letter_images(p_app_id NUMBER)
   IS
 	 SELECT REPLACE(filename,'newsletter_gpi/','') AS filename
		  , blob_content
		  , mime_type
		  , REPLACE(filename,'newsletter_gpi/','') AS content_id
	 FROM apex_application_files
	WHERE flow_id = p_app_id
	  AND filename LIKE 'newsletter_gpi/%';
 
    -- get PDF from application document store
    cursor c_get_attachment(p_file_id NUMBER)
      IS	  
	  SELECT f.FILENAME
           , f.MIMETYPE
           , f.FILE_CONTENT
	   FROM GPI.DOK_FILES f 
          WHERE f.ID = p_file_id ;
 
  v_mail_id        NUMBER; 
  v_workspace_id   NUMBER;
  v_placeholders   CLOB;
 
BEGIN
    -- ------------------------------------------- 
    -- Set Apex Workspace ID and Security Group ID
    SELECT workspace_id
      INTO v_workspace_id
      FROM apex_applications
     WHERE apex_applications.application_id = (SELECT nvl(nv('APP_ID'),
                                                          250)
                                                 FROM dual); 
    apex_util.set_security_group_id(p_security_group_id => v_workspace_id);
    IF nv('APP_ID') IS NULL THEN
        apex_session.create_session(p_app_id                   => 250,
                                    p_page_id                  => 1,
                                    p_username                 => 'SYSTEM',
                                    p_call_post_authentication => FALSE);
    END IF;
 
     -- ------------------------------------------- 
    -- Fill the Template Record with values for the template
    apex_json.initialize_clob_output;
    apex_json.open_object;
    apex_json.write('DATUM'        , '03.04.2023',TRUE);        
    apex_json.write('ANREDE'       , 'Anrede',TRUE);
    apex_json.write('MAIL_SUBJECT' , 'Ein Mail Subject mit ÄÖÜ und öäü',TRUE);
    apex_json.write('MAIL_BODY'    , 'Ein Mail Text    mit ÄÖÜ und öäü',TRUE);
    apex_json.close_object; 
    v_placeholders:=apex_json.get_clob_output;   
    apex_json.free_output;
 
     -- ------------------------------------------- 
     -- prepare the mail
     v_mail_id:=apex_mail.send (
          p_to                 => 'info@pipperr.de'
        , p_template_static_id => 'NEWSLETTER'
        , p_application_id     => 250
        , p_from               => 'info@pipperr.de'
	, p_placeholders       => v_placeholders		
	);
 
     -- ------------------------------------------- 
     -- add inline images
     -- Rec.content_id value must match cid:content_id in HTML Template
     --
    FOR rec IN c_news_letter_images(p_app_id => 250)
	loop	 
		 apex_mail.add_attachment( 
			      p_mail_id      => v_mail_id
				, p_attachment   => rec.blob_content
				, p_filename     => rec.filename
				, p_mime_type    => rec.mime_type
				, p_content_id   => rec.content_id     
			);
	END loop;
 
 
    -- ------------------------------------------
	-- add Attachment
	FOR file_rec IN c_get_attachment( p_file_id => 56)
	loop
          apex_mail.add_attachment(
                p_mail_id    => v_mail_id,
                p_attachment => file_rec.FILE_CONTENT,
                p_filename   => file_rec.FILENAME,
                p_mime_type  => file_rec.MIMETYPE);      
      END loop;         
 
 
    --
    commit;
 
    -- ------------------------------------------
	-- sent mail now
    apex_mail.push_queue;
END;
/

APEX Mail überwachen

 SELECT * FROM apex_mail_queue;

Mail Log löschen

Mit der Zeit häufen sich die Logs, das Log Handling wiederum erfolgt auf Instance Ebene. Allerdings finde ich dort keine Möglichkeit für das Mail Log.

Auch in der Doku https://docs.oracle.com/en/database/oracle/apex/22.1/aeadm/configuring-and-deleting-logs-and-log-entries.html#GUID-FAD74DA3-B34A-4E69-B18B-48E6023B1F3D wird das nicht erwähnt.

Praktisch kann aber auch einfach die Tabelle direkt gelöscht werden.

SET serveroutput ON
 
SET serveroutput ON
 
DECLARE
 -- ========================================
 -- APEX Mail Log Handling
 -- ========================================
 
 v_username   VARCHAR2(32);
 v_sql        VARCHAR2(4000); 
 v_delete_count PLS_INTEGER:=0;
 
 -- ========================================
 -- setze auf 0 um die Tabelle abzuschneiden
 -- x Tage aufbewahren 
 v_keep_days    PLS_INTEGER:=31;
 -- ========================================
 
BEGIN
 
   v_sql:='delete from #USER#.WWV_FLOW_MAIL_LOG where MAIL_MESSAGE_SEND_END < (sysdate -#KEEP_DAYS#)';
 
  -- hole den aktuellen aPEX Owner aus der DB
  SELECT MAX(username) 
   INTO v_username
   FROM dba_users 
  WHERE regexp_like(username,'^APEX_[[:digit:]].*');
 
 
 
  IF v_keep_days > 0 THEN
 
      DBMS_OUTPUT.put_line('-- Info :: Lösche Mail Logs aus WWV_FLOW_MAIL_LOG vom User '|| v_username ||' älter als '|| v_keep_days ||' Tage');   
 
      v_sql:=REPLACE(v_sql,'#USER#',v_username);
      v_sql:=REPLACE(v_sql,'#KEEP_DAYS#',TO_CHAR(v_keep_days));
 
      DBMS_OUTPUT.put_line('-- Info :: Lösche mit '||v_sql);
 
      EXECUTE IMMEDIATE v_sql;
 
      v_delete_count:=SQL%ROWCOUNT;
 
     COMMIT;
 
     DBMS_OUTPUT.put_line('-- Info :: Anzahl gelöschter Zeilen '|| v_delete_count);  
 
  ELSE
     DBMS_OUTPUT.put_line('-- Info :: Tabelle WWV_FLOW_MAIL_LOG wird mit Truncate gelöscht');
 
	 v_sql:='truncate table '|| v_username ||'.WWV_FLOW_MAIL_LOG drop storage';  
     EXECUTE IMMEDIATE v_sql;
 
	 DBMS_OUTPUT.put_line('-- Info :: Tabelle WWV_FLOW_MAIL_LOG komplett mit Truncate gelöscht');
  END IF;
 
EXCEPTION 
 WHEN OTHERS THEN 
   DBMS_OUTPUT.put_line('-- Error :: Fehler mit '|| v_sql || ' SQLERR Meldung '||SQLERRM);
END;
/

Quellen

Diese Website verwendet Cookies. Durch die Nutzung der Website stimmen Sie dem Speichern von Cookies auf Ihrem Computer zu. Außerdem bestätigen Sie, dass Sie unsere Datenschutzbestimmungen gelesen und verstanden haben. Wenn Sie nicht einverstanden sind, verlassen Sie die Website.Weitere Information
prog/apex_mail_template_22_2.txt · Zuletzt geändert: 2023/03/29 16:58 von gpipperr