=====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. {{ :prog:apex_umlaut_problem_mail_subject_22_2_3.png | 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 => [[prog:apex_mail_acl|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: {{ :prog:apex:apex_22_3_mail_versandt_instance_settings.png?600 | 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. {{ :prog:apex:apex_22_3_mail_versandt_aufbau_advanced_mail_template.png?600 | 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: * https://srihariravva.blogspot.com/2020/09/including-images-in-oracle-apex-emails.html * https://tm-apex.hashnode.dev/summary-apex-mail-apex-mail-10 ---- ==== 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: 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 ==== Oracle: * https://docs.oracle.com/en/database/oracle/apex/22.1/aeapi/APEX_MAIL.html#GUID-14F51C6D-CB82-4B38-AB6E-61C46E75596F * https://docs.oracle.com/en/database/oracle/application-express/20.1/htmdb/understanding-substitution-strings.html#GUID-CA3ABA44-D03D-4396-A527-B160F1FFE933 Web: * https://stackoverflow.com/questions/3902455/mail-multipart-alternative-vs-multipart-mixed * https://srihariravva.blogspot.com/2020/09/including-images-in-oracle-apex-emails.html * https://tm-apex.hashnode.dev/summary-apex-mail-apex-mail-10 * http://www.oneoracledeveloper.com/2021/11/creating-apex-session-from-plsql.html