Skip to main content

Including Images in Oracle APEX Emails

 In this blogpost, I am going to explain different ways using which we can include images as part of the email content.

CID Attachments

In this approach images are sent as attachment to the email and then images are referred in email content using attached filename. For e.g. if you want to include image "ironman.jpg", then you need to attach this image file to the email. Then you can refer the attached image using below syntax in "img" tag "src" attribute.

<img src="cid:ironman.jpg" alt="ironman"/>

Sample PL/SQL code to use this approach

DECLARE
    l_body        CLOB;
    l_body_html   CLOB;
    l_img_html    CLOB;
    l_id          NUMBER;
BEGIN
-- build html email body
    l_body_html := '<!DOCTYPE HTML>'
                   || '<html>'
                   || '<head>'
                   || '<meta http-equiv="Content-Type" content="text/html; charset=utf-8">'
                   || '<meta name="viewport" content="width=device-width">'
                   || '</head>'
                   || '<body yahoo="fix" style="background-color: #efefef; line-height: 1.5;">';

-- sending image as CID attachment
-- observe image src attribute src="cid:ironman.jpg", here we are saying email client to get image from email attachments and look for file name ironman.jpg
    l_img_html := '<h1>Below image is sent as CID attachment.</h1><img src="cid:ironman.jpg" alt="ironman"/>';
    l_body_html := l_body_html || l_img_html;
    l_body_html := l_body_html
                   || '</body>'
                   || '</html>';
    l_body := 'Please use HTML enabled email client to view this message.';
    l_id := apex_mail.send(
        p_to          => :P30_EMAIL_TO
        ,p_from        => 'noreply@oracle.com'
        ,p_body        => l_body
        ,p_body_html   => l_body_html
        ,p_subj        => 'Including images as CID attachment'
    );

-- attach image file for CID attachment
-- here I am taking one image from "Static Application Files" uploaded in shared components 
-- filename we are attaching should match with filename specified at img src
-- if filename does not match, then below image will be displayed as normal attachment
    FOR img IN(
        SELECT
            filename
            ,blob_content
            ,mime_type
        FROM
            apex_application_files
        WHERE
            filename = 'ironman.jpg'
            AND flow_id = :APP_ID
    )LOOP
        apex_mail.add_attachment(
            p_mail_id      => l_id
            ,p_attachment   => img.blob_content
            ,p_filename     => img.filename
            ,p_mime_type    => img.mime_type
        );
    END LOOP;
-- optionally push email
    apex_mail.push_queue;
END;

Since images are included as attachments, your email size will grow based on size of images used. This approach works well in most of the desktop clients but may not work well in web-based email clients. In my testing, this approach is working fine with Microsoft Outlook client, but not working as expected with Gmail web client. In Gmail, I can see it as normal attachment, but not as inline image.

Inline embedding using base64 encoding

In this approach images are converted to text format using base64 encoding and then they are directly used in "img" tag "src" attribute.

<img src="data:image/png;base64,<base64_image_text>"  alt="ironman"/>

Sample PL/SQL code to use this approach

DECLARE
    l_body         CLOB;
    l_body_html    CLOB;
    l_img_html     CLOB;
    l_img_base64   CLOB;
    l_mime_type    VARCHAR2(255);
    l_img_blob     BLOB;
BEGIN
-- build html email body
    l_body_html := '<!DOCTYPE HTML>'
                   || '<html>'
                   || '<head>'
                   || '<meta http-equiv="Content-Type" content="text/html; charset=utf-8">'
                   || '<meta name="viewport" content="width=device-width">'
                   || '</head>'
                   || '<body yahoo="fix" style="background-color: #efefef; line-height: 1.5;">';

-- here I am taking one image from "Static Application Files" uploaded in shared components 
-- convert image to base64 encoding
    SELECT
        blob_content
        ,mime_type
    INTO
        l_img_blob
    ,l_mime_type
    FROM
        apex_application_files
    WHERE
        filename = 'ironman.jpg'
        AND flow_id = :APP_ID;

    l_img_base64 := apex_web_service.blob2clobbase64(l_img_blob);

-- sending image as text using base64 encoding   
-- observe image src attribute src="data:image/png;base64,<base64_image_text>" 
    l_img_html := '<h1>Below image is sent as inline image by converting image to text using base64 encoding.</h1><img src="data:'
                  || l_mime_type
                  || ';base64,'
                  || l_img_base64
                  || '" alt="ironman"/>';
    l_body_html := l_body_html || l_img_html;
    l_body_html := l_body_html
                   || '</body>'
                   || '</html>';
    l_body := 'Please use HTML enabled email client to view this message.';
    apex_mail.send(
        p_to          => :P30_EMAIL_TO
        ,p_from        => 'noreply@oracle.com'
        ,p_body        => l_body
        ,p_body_html   => l_body_html
        ,p_subj        => 'Embedding images inline using base64 encoding'
    );

-- optionally push email
    apex_mail.push_queue;
END;

Again, since images are included as part of email content, your email size will grow based on size of images used.  In my testing, this approach is working fine with Microsoft Outlook client, but not working at all with Gmail web client. I can only see image alt text in Gmail.

Hosted Images

This is simple approach where images are hosted in a server and those images are referred in email using "img" tag, just like how we refer them in any web-page.

Sample PL/SQL code to use this approach

DECLARE
    l_body        CLOB;
    l_body_html   CLOB;
    l_img_html    CLOB;
BEGIN
-- build html email body
    l_body_html := '<!DOCTYPE HTML>'
                   || '<html>'
                   || '<head>'
                   || '<meta http-equiv="Content-Type" content="text/html; charset=utf-8">'
                   || '<meta name="viewport" content="width=device-width">'
                   || '</head>'
                   || '<body yahoo="fix" style="background-color: #efefef; line-height: 1.5;">';

-- referring to image hosted in server
-- here I am taking one image from "Static Application Files" uploaded in shared components 
-- filename ironman.jpg
    l_img_html := '<h1>Below image is referred from external server. Size of this email should be small compared to other two approaches.</h1><img src="'
                  || apex_mail.get_instance_url
                  || :app_images
                  || 'ironman.jpg" alt="ironman"/>';
    l_body_html := l_body_html || l_img_html;
    l_body_html := l_body_html
                   || '</body>'
                   || '</html>';
    l_body := 'Please use HTML enabled email client to view this message.';
    apex_mail.send(
        p_to          => :P30_EMAIL_TO
        ,p_from        => 'noreply@oracle.com'
        ,p_body        => l_body
        ,p_body_html   => l_body_html
        ,p_subj        => 'Referring hosted images'
    );
    
-- optionally push email
    apex_mail.push_queue;
END;

When you are using this approach, images are not included as part of your email. So, email size should be much smaller compared to other two approaches. In my testing, this approach is working fine with Microsoft Outlook client as-well as with Gmail web client

This is probably most popular approach used for including images in email content. For that matter, APEX uses this approach in all email communications that are being sent from apex.oracle.com. You can also find those images in "\email\img" folder under APEX "images" folder. However, one point to note here is, "images should be accessible to email recipients". For e.g. if images are hosted on a server which is behind corporate firewall and if your recipients are outside the firewall, then images will not be displayed in email. I think, this is one of the reasons why you find recommendation to "Avoid images" in Oracle APEX APIs documentation.

In this blogpost, I have tried to provide example PL/SQL code to include images in emails, using different approaches. However, it is not easy to ensure images are always displayed without any issues for all your users, especially when you don't know which email clients your users may use. But an age-old adage says, "A Picture Is Worth a Thousand Words", so sometimes it's worth to put all that effort. 😀

To understand pros and cons of above approaches and compatibility with different email clients, please refer Embedding Images in HTML Email: Have the Rules Changed? blogpost from mailtrap.

Link for Demo

Thank you.

Comments

Popular posts from this blog

Interactive Grid - Bulk Operation on Selected Rows

What's the Problem? Let's say you have an IG on employee table and you want to update selected employees commission based on user's input. Ideally, it should be very simple, where you can write UPDATE statement in page process and select IG region as "Editable Region" under "Execution Options" of the page process. But, when you select rows and submit page, you can see that, this process won't get executed! The reason is  Selection of 'Row Selector' check-boxes is not considered as row-change. Thus selected rows are not submitted to server. So, is there any work around?  Yes! Luckily there are lot of JavaScript (JS) APIs available to work with IG. If you are not already aware, you can refer "APEX IG Cookbook"  or  JavaScript API Reference documentation. If we continue with above Employee IG example, when user selects IG rows, enters "Commission %" and clicks on "Update Commission" button, then we can writ

Interactive Grid - Conditional Enable/Disable

In this blogpost, I am going to discuss few approaches using which we can conditionally enable/disable Interactive Grid (IG) column(s) based on other column(s) values. Note:    There is a bug  30801170  in APEX 19.2/20.1 with respect to "enable/disable" dynamic actions for IG columns. Workaround for this bug is provided at the end of this blogpost . This bug has been fixed in APEX version 20.2. Client Side Only Conditions If conditions to enable/disable are simple, then we can check those conditions easily on the client side. For e.g. let's consider IG on EMP table with following SQL Query. SELECT EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM, DEPTNO FROM EMP Let's consider the requirement as - Enable "Commission" column only when JOB is equals to 'SALESMAN' and disable "Commission" column in all other cases. This can be done declaratively using dynamic actions (DA) DA "Enable/Disable Commission - 1" Create DA and give it a prope

Interactive Grid - Aggregate Validations

In Oracle APEX, validating Interactive Grid (IG) data at row level, is easy and straight forward. You just need to select "Editable Region" for the validation and then you can use IG column values using :COLUMN_NAME syntax in SQL and PL/SQL code. However, if you want to create a validation at table level or validation which uses multiple rows data, then it becomes tricky. Let's consider a simple example of budget planning, where you can allocate percentage for each category, like 10% for healthcare, 10% for education etc. Here, we need to implement a simple table level validation to ensure total allocation % does not exceed 100. Please refer  Interactive Grid - Client Side Aggregate Validations  for implementing this validation entirely on client side. You can use technique explained below for more complex validations that can't be done on client side (checking data from multiple tables, number of rows in IG are more than 50) Before jumping into the implemen