AEM 6550 - Compose Emails in AEM Assets Console

Goal

Provide Send Mail feature in Assets Console - http://localhost:4502/assets.html, so users can send free form emails about selected assets without leaving the AEM context...

FakeSMTP configured as mail server, for more info check this


Demo | Package Install | Github


Email Modal



Fake SMTP Server

                              Fake SMTP Server intercepting emails sent to localhost on port 25


 

Configure FakeSMTP properties in AEM

                              http://localhost:4502/system/console/configMgr/com.day.cq.mailer.DefaultMailService



Solution

1) Create email template nt:file /apps/eaem-assets-send-mail/mail-templates/send-assets.html with the following code...

Subject: ${subject}

<table style="width:100%" width="100%" bgcolor="#ffffff" style="background-color:#ffffff;" border="0" cellpadding="0" cellspacing="0">
    <tr>
        <td style="width:100%">${body}</td>
    </tr>
</table>


2) Create a servlet apps.experienceaem.assets.SendMailServlet for sending emails...

package apps.experienceaem.assets;


import javax.jcr.Session;
import javax.servlet.Servlet;
import javax.servlet.ServletException;

import com.day.cq.commons.jcr.JcrConstants;
import com.day.cq.mailer.MessageGatewayService;
import org.apache.commons.lang3.StringUtils;
import org.apache.jackrabbit.api.security.user.Authorizable;
import org.apache.jackrabbit.api.security.user.UserManager;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.apache.sling.jcr.base.util.AccessControlUtil;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.framework.Constants;

import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.day.cq.commons.mail.MailTemplate;
import com.day.cq.mailer.MessageGateway;
import org.apache.commons.lang.text.StrLookup;
import org.apache.commons.mail.Email;
import org.apache.commons.mail.HtmlEmail;

import javax.jcr.Session;
import javax.mail.internet.InternetAddress;
import java.util.*;

import java.io.IOException;

@Component(
        name = "Experience AEM Send Mail Servlet",
        immediate = true,
        service = Servlet.class,
        property = {
                "sling.servlet.methods=POST",
                "sling.servlet.paths=/bin/experience-aem/send-mail"
        }
)
public class SendMailServlet extends SlingAllMethodsServlet {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    private static String EMAIL_TEMPLATE_PATH = "/apps/eaem-assets-send-mail/mail-templates/send-assets.html";

    @Reference
    private MessageGatewayService messageGatewayService;

    @Override
    protected void doPost(final SlingHttpServletRequest req,
                         final SlingHttpServletResponse resp) throws ServletException, IOException {
        ResourceResolver resourceResolver = req.getResourceResolver();

        try{
            String to = req.getParameter("./to");
            String subject = req.getParameter("./subject");
            String body = req.getParameter("./body");

            Map<String, String> emailParams = new HashMap<String,String>();

            emailParams.put("subject", subject);
            emailParams.put("body", body.replaceAll("\r\n", ""));

            sendMail(resourceResolver, emailParams, to);
        }catch(Exception e){
            logger.error("Error sending email", e);
        }
    }

    private Email sendMail(ResourceResolver resolver, Map<String, String> emailParams, String recipientEmail) throws Exception{
        MailTemplate mailTemplate = MailTemplate.create(EMAIL_TEMPLATE_PATH, resolver.adaptTo(Session.class));

        if (mailTemplate == null) {
            throw new Exception("Template missing - " + EMAIL_TEMPLATE_PATH);
        }

        Email email = mailTemplate.getEmail(StrLookup.mapLookup(emailParams), HtmlEmail.class);

        email.setTo(Collections.singleton(new InternetAddress(recipientEmail)));

        MessageGateway<Email> messageGateway = messageGatewayService.getGateway(email.getClass());

        messageGateway.send(email);

        return email;
    }
}


3) Add the action bar button Send Mail at path /apps/eaem-assets-send-mail/content/send-mail-but with following configuration...

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:granite="http://www.adobe.com/jcr/granite/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
    granite:rel="cq-damadmin-admin-actions-send-mail-activator"
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/collection/action"
    icon="email"
    target=".cq-damadmin-admin-childpages"
    text="Send Mail"
    variant="actionBar">
    <data
        jcr:primaryType="nt:unstructured"
        text="Email sent..."/>
</jcr:root>


4) Add the send mail modal /apps/eaem-assets-send-mail/send-mail-dialog, a form with To, Subject and Body. When user selects assets and clicks on Send Mail button the modal opens with the assets paths added to content field (email body). User can add additional content before clicking Send....

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:granite="http://www.adobe.com/jcr/granite/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
    jcr:primaryType="cq:Page">
    <jcr:content
        jcr:mixinTypes="[sling:VanityPath]"
        jcr:primaryType="nt:unstructured"
        jcr:title="Experience AEM Send Mail"
        sling:resourceType="granite/ui/components/coral/foundation/page">
        <head jcr:primaryType="nt:unstructured">
            <favicon
                jcr:primaryType="nt:unstructured"
                sling:resourceType="granite/ui/components/coral/foundation/page/favicon"/>
            <viewport
                jcr:primaryType="nt:unstructured"
                sling:resourceType="granite/ui/components/coral/foundation/admin/page/viewport"/>
            <clientlibs
                jcr:primaryType="nt:unstructured"
                sling:resourceType="granite/ui/components/coral/foundation/includeclientlibs"
                categories="[coralui3,granite.ui.coral.foundation,granite.ui.shell,dam.gui.admin.coral,eaem.assets.send.mail]"/>
        </head>
        <body
            jcr:primaryType="nt:unstructured"
            sling:resourceType="granite/ui/components/coral/foundation/page/body">
            <items jcr:primaryType="nt:unstructured">
                <content
                    jcr:primaryType="nt:unstructured"
                    sling:resourceType="granite/ui/components/coral/foundation/form"
                    action="/bin/experience-aem/send-mail"
                    foundationForm="{Boolean}true"
                    maximized="{Boolean}true"
                    method="post"
                    novalidate="{Boolean}true"
                    style="vertical">
                    <successresponse
                        jcr:primaryType="nt:unstructured"
                        jcr:title="Success"
                        sling:resourceType="granite/ui/components/coral/foundation/form/responses/openprompt"
                        open="/assets.html"
                        redirect="/apps/eaem-assets-send-mail/send-mail-dialog.html"
                        text="Email sent"/>
                    <items jcr:primaryType="nt:unstructured">
                        <wizard
                            jcr:primaryType="nt:unstructured"
                            jcr:title="Compose"
                            sling:resourceType="granite/ui/components/coral/foundation/wizard">
                            <items jcr:primaryType="nt:unstructured">
                                <container
                                    granite:class="eaem-send-mail-form"
                                    jcr:primaryType="nt:unstructured"
                                    jcr:title="Compose"
                                    sling:resourceType="granite/ui/components/coral/foundation/container">
                                    <items jcr:primaryType="nt:unstructured">
                                        <actionbar
                                            jcr:primaryType="nt:unstructured"
                                            sling:resourceType="granite/ui/components/coral/foundation/container">
                                            <items jcr:primaryType="nt:unstructured">
                                                <to
                                                    jcr:primaryType="nt:unstructured"
                                                    sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
                                                    fieldDescription="Comma separated 'To' list..."
                                                    fieldLabel="To"
                                                    name="./to"/>
                                                <subject
                                                    jcr:primaryType="nt:unstructured"
                                                    sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
                                                    fieldDescription="Enter Subject..."
                                                    fieldLabel="Subject"
                                                    name="./subject"/>
                                                <body
                                                    jcr:primaryType="nt:unstructured"
                                                    sling:resourceType="granite/ui/components/coral/foundation/form/textarea"
                                                    cols="15"
                                                    fieldDescription="Email body content"
                                                    fieldLabel="Content"
                                                    name="./body"
                                                    rows="22"/>
                                            </items>
                                        </actionbar>
                                    </items>
                                    <parentConfig jcr:primaryType="nt:unstructured">
                                        <prev
                                            granite:class="foundation-wizard-control"
                                            jcr:primaryType="nt:unstructured"
                                            sling:resourceType="granite/ui/components/coral/foundation/anchorbutton"
                                            href="/aem/start.html"
                                            text="Cancel">
                                            <granite:data
                                                jcr:primaryType="nt:unstructured"
                                                foundation-wizard-control-action="cancel"/>
                                        </prev>
                                        <next
                                            granite:class="foundation-wizard-control"
                                            jcr:primaryType="nt:unstructured"
                                            sling:resourceType="granite/ui/components/coral/foundation/button"
                                            text="Send"
                                            type="submit"
                                            variant="primary">
                                            <granite:data
                                                jcr:primaryType="nt:unstructured"
                                                foundation-wizard-control-action="next"/>
                                        </next>
                                    </parentConfig>
                                </container>
                            </items>
                        </wizard>
                    </items>
                </content>
            </items>
        </body>
    </jcr:content>
</jcr:root>


5) Create node /apps/eaem-assets-send-mail/send-mail-dialog/clientlib of type cq:ClientLibraryFolder, add String[] property categories with value [dam.gui.actions.coral, eaem.assets.send.mail], String[] property dependencies with value lodash.


6) Create file (nt:file) /apps/eaem-assets-send-mail/send-mail-dialog/js.txt, add

                        dialog-send-mail.css

7) Create file (nt:file) /apps/eaem-assets-send-mail/send-mail-dialog/dialog-send-mail.css, add the following code...

.eaem-send-mail-form{
    margin: 10px 40px 0px 40px;
    height: 87%;
}

.eaem-send-mail-col1{
    width: 30%;
    margin-top: 10px;
    overflow-y: auto;
}

.eaem-send-mail-col2{
    height: 65%;
    padding: 1rem;
    overflow: hidden;
    width: 65%;
    margin-top: 30px;
    margin-left: 50px;
    text-align: center;
    background: white;
}

.eaem-send-mail-apply{
    margin-top: 20px;
    margin-left: 88%;
}

.eaem-send-mail-apply-modal {
    width: 50%;
    margin-left: -50%;
    height: 63%;
    margin-top: -50%;
    box-sizing: content-box;
    z-index: 10100;
}

.eaem-send-mail-apply-modal > iframe {
    width: 100%;
    height: 100%;
    border: 1px solid #888;
}


8) Create file (nt:file) /apps/eaem-assets-send-mail/send-mail-dialog/js.txt, add

                        dialog-send-mail.js

9) Create file (nt:file) /apps/eaem-assets-send-mail/send-mail-dialog/dialog-send-mail.js, add the following code...

(function ($, $document) {
    var BUTTON_URL = "/apps/eaem-assets-send-mail/content/send-mail-but.html",
        SHARE_ACTIVATOR = "cq-damadmin-admin-actions-adhocassetshare-activator",
        SEND_MAIL_SERVLET = "/bin/experience-aem/send-mail",
        SEND_MAIL_URL = "/apps/eaem-assets-send-mail/send-mail-dialog.html",
        CANCEL_CSS = "[data-foundation-wizard-control-action='cancel']",
        SENDER = "experience-aem", REQUESTER = "requester", $mailModal,
        url = document.location.pathname;

    if( url.indexOf("/assets.html") == 0 ){
        $document.on("foundation-selections-change", addSendMail);
    }else if(url.indexOf(SEND_MAIL_URL) == 0){
        handleSendMailDialog();
    }

    function handleSendMailDialog(){
        $document.on("foundation-contentloaded", fillDefaultValues);

        $document.on("click", CANCEL_CSS, sendCancelMessage);

        $document.submit(sendMailSentMessage);
    }

    function sendMailSentMessage(){
        var message = {
            sender: SENDER,
            action: "send"
        };

        getParent().postMessage(JSON.stringify(message), "*");
    }

    function sendCancelMessage(){
        var message = {
            sender: SENDER,
            action: "cancel"
        };

        getParent().postMessage(JSON.stringify(message), "*");
    }

    function getParent() {
        if (window.opener) {
            return window.opener;
        }

        return parent;
    }

    function closeModal(event){
        event = event.originalEvent || {};

        if (_.isEmpty(event.data) || _.isEmpty($mailModal)) {
            return;
        }

        var message, action;

        try{
            message = JSON.parse(event.data);
        }catch(err){
            return;
        }

        if (!message || message.sender !== SENDER) {
            return;
        }

        var modal = $mailModal.data('modal');
        modal.hide();
        modal.$element.remove();

        if(message.action == "send"){
            showAlert("Email sent...", $mailModal.mailSentMessage);
        }
    }

    function showAlert(message, title, callback){
        var fui = $(window).adaptTo("foundation-ui"),
            options = [{
                id: "ok",
                text: "OK",
                primary: true
            }];

        message = message || "Unknown Error";
        title = title || "Error";

        fui.prompt(title, message, "default", options, callback);
    }

    function fillDefaultValues(){
        var queryParams = queryParameters(),
            form = $("form")[0];

        setWidgetValue(form, "[name='./subject']", queryParams.subject);

        setWidgetValue(form, "[name='./body']", queryParams.body);
    }

    function setWidgetValue(form, selector, value){
        Coral.commons.ready(form.querySelector(selector), function (field) {
            field.value = _.isEmpty(value) ? "" : decodeURIComponent(value);
        });
    }

    function queryParameters() {
        var result = {}, param,
            params = document.location.search.split(/\?|\&/);

        params.forEach( function(it) {
            if (_.isEmpty(it)) {
                return;
            }

            param = it.split("=");
            result[param[0]] = param[1];
        });

        return result;
    }

    function addSendMail(){
        $.ajax(BUTTON_URL).done(addButton);
    }

    function addButton(html) {
        var $eActivator = $("." + SHARE_ACTIVATOR);

        if ($eActivator.length == 0) {
            return;
        }

        var $mail = $(html).css("margin-left", "20px").insertBefore($eActivator);

        $mail.click(openModal);

        $(window).off('message', closeModal).on('message', closeModal);
    }

    function openModal(){
        var actionConfig = ($(this)).data("foundationCollectionAction");

        var $items = $(".foundation-selections-item"),
            assetPaths = [];

        $items.each(function () {
            assetPaths.push($(this).data("foundationCollectionItemId"));
        });

        var body = "Please review the following assets... \n\n" + assetPaths.join("\n");

        showMailModal(getModalIFrameUrl("Experience AEM: Review Assets...", body), actionConfig.data.text);
    }

    function showMailModal(url, mailSentMessage){
        var $iframe = $('<iframe>'),
            $modal = $('<div>').addClass('eaem-send-mail-apply-modal coral-Modal');

        $iframe.attr('src', url).appendTo($modal);

        $modal.appendTo('body').modal({
            type: 'default',
            buttons: [],
            visible: true
        });

        $mailModal = $modal;

        $mailModal.mailSentMessage = mailSentMessage;
    }

    function getModalIFrameUrl(subject, body){
        var url = Granite.HTTP.externalize(SEND_MAIL_URL) + "?" + REQUESTER + "=" + SENDER;

        url = url + "&subject=" + encodeURIComponent(subject) + "&body=" + encodeURIComponent(body);

        return url;
    }
}(jQuery, jQuery(document)));


1 comment:

  1. Hi,
    Thanks for the great tutorial.

    I am trying to add a button in sites.html action bar.
    What should I put as property in clientlibs folder instead of "dam.gui.actions.coral" for assets?

    ReplyDelete