Goal
New in AEM 6 is Projects http://localhost:4502/projects.html. Projects console can be used to add members, assign work, track progress etc. This post is on emailing team members when a project is created
Thank you ACS Commons for email code snippets
Demo | Package Install | Source Code
Project creation success modal
Email sent to a member
Solution
For this feature to work, CQ mailing service - com.day.cq.mailer.DefaultMailService should be configured with SMTP details. In the following steps we use Gmail for SMTP and a test account experience.aem@gmail.com as both sender and recipient (when project is created)
Create Gmail Account
1) Create a new gmail account for testing purposes, say experience.aem@gmail.com . Use this account as both sender and recipient
2) Make sure you tone down the security of test gmail account a bit, by clicking Turn on of Access for less secure apps (https://www.google.com/settings/security/lesssecureapps)
Set up CQ Mail Service
1) Configure the CQ Mail Service with Gmail SMTP credentials by accessing Felix config manager (http://localhost:4502/system/console/configMgr/com.day.cq.mailer.DefaultMailService) or create a sling:osgiConfig for com.day.cq.mailer.DefaultMailService with these settings
2) If emails are NOT sent by CQ Mailing service, make sure CQ can reach smtp.gmail.com. Check ports etc. In my case the problem was company vpn; if connected to vpn, CQ running on the machine could not reach smtp.gmail.com
3) Configure test users with email addresses
Download Projects Api Jar
As of this writing, the api jar for Projects is not available in Adobe nexus repo (https://repo.adobe.com/nexus). For build purposes, copy jar com.adobe.cq.projects.api-0.0.14.jar from CQ install (search, find in author\crx-quickstart\launchpad\felix\bundle392\data\install) and copy it to local maven repo (windows - C:\Users\<user>\.m2\repository\com\adobe\cq\projects\com.adobe.cq.projects.api\0.0.14)
The Extension
1) Create a servlet apps.experienceaem.projects.SendProjectCreateEmail in CRX folder /apps/touchui-create-project-send-email for send email call when Email Team button is clicked (added in next steps), after project create
package apps.experienceaem.projects; import com.adobe.cq.projects.api.Project; import com.adobe.cq.projects.api.ProjectMember; import com.day.cq.commons.mail.MailTemplate; import com.day.cq.mailer.MessageGateway; import com.day.cq.mailer.MessageGatewayService; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.text.StrLookup; import org.apache.commons.mail.Email; import org.apache.commons.mail.HtmlEmail; import org.apache.commons.mail.SimpleEmail; import org.apache.felix.scr.annotations.*; import org.apache.felix.scr.annotations.Properties; import org.apache.jackrabbit.api.security.user.Authorizable; import org.apache.jackrabbit.api.security.user.Group; 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.ResourceResolverFactory; import org.apache.sling.api.resource.ValueMap; import org.apache.sling.api.servlets.SlingAllMethodsServlet; import org.apache.sling.commons.json.JSONArray; import org.apache.sling.jcr.base.util.AccessControlUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.jcr.Session; import javax.mail.internet.InternetAddress; import javax.servlet.ServletException; import java.io.IOException; import java.util.*; @Component(metatype = false, label = "Experience AEM Project Create Email Servlet", description = "") @Service @Properties({ @Property(name = "sling.servlet.methods", value = {"GET"}, propertyPrivate = true), @Property(name = "sling.servlet.paths", value = "/bin/experience-aem/send-project-create-email", propertyPrivate = true), @Property(name = "sling.servlet.extensions", value = "json", propertyPrivate = true) }) public class SendProjectCreateEmail extends SlingAllMethodsServlet { private static final Logger log = LoggerFactory.getLogger(SendProjectCreateEmail.class); @Reference private ResourceResolverFactory rrFactory; @Reference private MessageGatewayService messageGatewayService; private static String TEMPLATE_PATH = "/apps/touchui-create-project-send-email/mail/template.html"; private static String SENDER_EMAIL = "experience.aem@gmail.com"; private static String SENDER_NAME = "Experience AEM"; private static String SENDER_EMAIL_ADDRESS = "senderEmailAddress"; public String sendMail(ResourceResolver resolver, Resource projectRes, String recipientEmail, String recipientName){ if(StringUtils.isEmpty(recipientEmail)){ throw new RuntimeException("Empty email"); } if(StringUtils.isEmpty(recipientName)){ recipientName = recipientEmail; } try{ Project project = projectRes.adaptTo(Project.class); Map<String, String> emailParams = new HashMap<String,String>(); emailParams.put(SENDER_EMAIL_ADDRESS, SENDER_EMAIL); emailParams.put("senderName", SENDER_NAME); emailParams.put("projectName", project.getTitle()); emailParams.put("recipientName", recipientName); emailParams.put("body","Project Created - <a href='http://localhost:4502/projects/details.html" + projectRes.getPath() + "'>" + project.getTitle() + "</a>"); emailParams.put("projectCreator", projectRes.adaptTo(ValueMap.class).get("jcr:createdBy", "")); send(resolver, emailParams, recipientEmail); }catch(Exception e){ log.error("Error sending email to " + recipientEmail, e); recipientEmail = ""; } return recipientEmail; } public Map<String, String> getMemberEmails(ResourceResolver resolver, Project project) throws Exception{ Map<String, String> members = new LinkedHashMap<String, String>(); String name = null, email = null; UserManager um = AccessControlUtil.getUserManager(resolver.adaptTo(Session.class)); ValueMap profile = null; Iterator<Authorizable> itr = null; List<Authorizable> users = new ArrayList<Authorizable>(); for(ProjectMember member : project.getMembers()) { Authorizable user = um.getAuthorizable(member.getId()); if(user instanceof Group){ itr = ((Group)user).getMembers(); while(itr.hasNext()) { users.add(itr.next()); } }else{ users.add(user); } } for(Authorizable user : users){ profile = resolver.getResource(user.getPath() + "/profile").adaptTo(ValueMap.class); email = profile.get("email", ""); if(StringUtils.isEmpty(email)){ continue; } name = profile.get("familyName", "") + " " + profile.get("givenName", ""); if(StringUtils.isEmpty(name.trim())){ name = user.getID(); } members.put(name, email); } return members; } private Email send(ResourceResolver resolver, Map<String, String> emailParams, String recipientEmail) throws Exception{ MailTemplate mailTemplate = MailTemplate.create(TEMPLATE_PATH, resolver.adaptTo(Session.class)); if (mailTemplate == null) { throw new Exception("Template missing - " + TEMPLATE_PATH); } Email email = mailTemplate.getEmail(StrLookup.mapLookup(emailParams), HtmlEmail.class); email.setTo(Collections.singleton(new InternetAddress(recipientEmail))); email.setFrom(SENDER_EMAIL); MessageGateway<Email> messageGateway = messageGatewayService.getGateway(email.getClass()); messageGateway.send(email); return email; } public void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException{ ResourceResolver resolver = request.getResourceResolver(); String projectPath = request.getParameter("projectPath"); try{ if(StringUtils.isEmpty(projectPath)){ throw new RuntimeException("Empty projectPath"); } Resource res = resolver.getResource(projectPath); if(res == null){ throw new Exception("Project not found - " + projectPath); } Project project = res.adaptTo(Project.class); Map<String, String> members = getMemberEmails(resolver, project); String recipientEmail = null; JSONArray output = new JSONArray(); for(Map.Entry<String, String> member : members.entrySet()){ recipientEmail = sendMail(resolver, res, member.getValue(), member.getKey()); if(StringUtils.isEmpty(recipientEmail)){ continue; } output.put(recipientEmail); } response.getWriter().print("{ success : " + output.toString() + " }"); }catch(Exception e){ log.error("Error sending email for project create - " + projectPath, e); response.getWriter().print("{ error : 'error sending email' }"); } } }
2) #72 to #78 parameters are needed for email template, created in next step
3) #172 getMemberEmails() call uses Projects api to get a list of members, members in groups, addresses for sending emails
4) Create a template html /apps/touchui-create-project-send-email/mail/template.html for email body with following code. The ${} placeholders are replaced with parameter values
From: ${senderName} <${senderEmailAddress}> Subject: ${projectName} project created Hello ${recipientName} ${body} From ${projectCreator}
5) Create clientlib (cq:ClientLibraryFolder) /apps/touchui-create-project-send-email/clientlib with categories cq.projects.admin.createprojectwizard
6) Create clientlib js file /apps/touchui-create-project-send-email/clientlib/send-email.js, add the following code
(function(window, $) { var MODAL = "modal", OPEN_PROJECT_TEXT = "Open project", EMAIL_SERVLET = "/bin/experience-aem/send-project-create-email?projectPath="; var modalPlugin = $.fn[MODAL], ui = $(window).adaptTo("foundation-ui"); function emailTeam(path){ if(path.indexOf("/content") < 0){ return; } var projectPath = path.substring(path.indexOf("/content")); $.ajax( EMAIL_SERVLET + projectPath).done(handler); function handler(data){ if(data.success){ document.location = path; return; } ui.alert("Error", "Error emailing team", "error"); } } //there could be many ways to intercept project creation ajax, i just thought the following is cleaner function modalOverride(optionsIn){ modalPlugin.call(this, optionsIn); var $element = $(this); if($element.length == 0){ return; } var $openProject = $element.find(".coral-Button--primary"); if($openProject.html() != OPEN_PROJECT_TEXT){ return; } var path = $openProject.attr("href"); $openProject.attr("href", "").html("Email Team").click( function(){ emailTeam(path); } ) ; } $.fn[MODAL] = modalOverride; })(window, Granite.$);
7) AEM's create project submit function was added inside a closure in /libs/cq/gui/components/projects/admin/createprojectwizard/clientlibs/js/createprojectwizard.js and not available for extension. #51 intercepts the result of create project call, by extending modal plugin and making necessary changes to button, for sending email
Are you finding for Adobe Customer Service Number? If yes then check out the Adobe Usa,Canada helpline number @+1877-339-8403 and get most reliable Adobe technical support services.
ReplyDeletevery nice article works fine for me
ReplyDeletehow can we make it workable in AEM 6.4 Please help if anybody has any information .
ReplyDelete