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 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.

Note: If you are using Oracle APEX 21.2 or later, then please refer Inline Attachments for Oracle APEX Emails blogpost. 

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 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 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

Jc said…
This is great Hari, thanks so much.
Question please, how do you work with CSS positioning?
E.g. I'm trying to add some text on top of an image using style="position: absolute; top: 20px; left: 50px;"

But it does nothing.
On the same topic, adding text positioning (not on an image) doesn't seem to have any effect either.
Do you know why? or how to fix it?

Thanks

Popular posts from this blog

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 - 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 - Process Filtered Data on Server Side

Recently one of the APEX developers has reached out to me and asked if it's possible to capture filtered rows data of the Interactive Grid on the server-side and do some processing. In APEX 20.1, there is a new API APEX_IG , using which we can achieve this. Photo by Jakub Kapusnak on Unsplash The approach is very simple and straightforward. Get the internal region id based on the Static ID given for the IG region Get the last viewed report id based on region id Open query context for the region and report using region id and report id Fetch and loop through the rows using the query context Do something with fetched rows And finally, close the query context If you have already done this for interactive reports, then you should be already aware of these steps. The only difference here is, we use APEX_IG APIs instead of APEX_IR APIs. For the demo purpose, let's Build an Interactive Grid on EMP table and let's give it a Static ID as emp Create a Textarea page item with...