How can I configure or customize the look and content of email notifications sent after a form entry has been submitted?

Issue

  • I am using Liferay Forms' native email notification
  • I would like to change the content and appearance of this email.

 

not.png
Image: Liferay native email notification

 

Environment

  • Liferay DXP 7.0+

Resolution

  • Currently, there's an open feature request for allowing easier customization of the email notification. Make sure to vote for it if you want this feature in future Liferay versions.
  • It is possible to create your own email template and prioritize it over the native one. Please see the following guide on how to effectively do it:

First, you will need to create a Liferay module fragment, using the com.liferay.dynamic.data.mapping.service OSGi bundle as host. The purpose of this fragment is to export the internal package com.liferay.dynamic.data.mapping.internal.notification, so you can extend the class responsible to send the email notification, and to store your new custom email template (you can store your template on the next paragraph's module too, just be mindful of which class loader you should use to retrieve that resource and if that OSGi bundle is providing that capability).

Bundle-Name: ddm-service-fragment
Bundle-SymbolicName: ddm.service.fragment
Bundle-Version: 1.0.0
Export-Package: com.liferay.dynamic.data.mapping.internal.notification
Fragment-Host: com.liferay.dynamic.data.mapping.service;bundle-version="5.3.12"
Portal-Bundle-Version: 7.3.10

Code: example of a fragment's bnb.bnd file, using Liferay DXP 7.3 GA1

Once you have that created, go ahead and put your custom email template inside of your fragment's src/main/resources/META-INF/ folder (make sure to not use the same qualified path as the host's template. OSGi is designed in a way that it first looks for resources inside the host before going through its fragments).

Screen_Shot_2020-11-17_at_16.58.30.png

Image: example of custom email body template

With that fragment completed, you can now create a new Liferay service module. Inside this module, you'll have a new class that should extend DDMFormEmailNotificationSender. Set your new extension as an OSGi Component for the service DDMFormEmailNotificationSender.class and bump the service ranking, so, whenever you startup your server, you guarantee that your custom service will have priority over Liferay's (remember that hot deploying the module may cause references to point to the default service).

@Component(
immediate = true, property = "service.ranking:Integer=" + Integer.MAX_VALUE,
service = DDMFormEmailNotificationSender.class
)
public class DDMCustomNotificationSender extends DDMFormEmailNotificationSender {
...
}

Code: example of a custom DDMFormEmailNotificationSender service configuration

Now it's just a matter of customizing what you need in your custom service (i.e. changing the template path). However, you might notice that just overriding the method getTemplateResource(String templatePath) from the super class to hardcode the template path and change the getClass() call to DDMFormEmailNotificationSender.class, in order for your customization to find the template from the correct class loader, won't work.

@Override
protected TemplateResource getTemplateResource(String templatePath) {
Class<?> clazz = DDMFormEmailNotificationSender.class;

ClassLoader classLoader = clazz.getClassLoader();

URL templateURL = classLoader.getResource("/META-INF/resources/email/notification.soy");

return new URLTemplateResource(templateURL.getPath(), templateURL);
}

Code: this won't work on its own

You'll encounter many NullPointerExceptions during execution if you try to fix one after the other. This happens because the super class methods use several Liferay services that were not yet referenced in your custom extension by the time you first deployed it. Therefore, you will need to reference those services and override all the methods that use them beforehand.

private DDMFormFieldTypeServicesTracker _ddmFormFieldTypeServicesTracker;

@Reference
private GroupLocalService _groupLocalService;

private MailService _mailService;

@Reference
private Portal _portal;

@Reference
private SoyDataFactory _soyDataFactory;

private UserLocalService _userLocalService;

Code: services used in DDMFormEmailNotificationSender that need to be referenced by your service

After that, you should be able to receive your custom email notifications.

Tips

You might want to speed up your development time, so a good tip is to copy the entire super class and paste it in your extension. Just keep an eye on getClass() calls that need to be pointing to the super class and on the private constant _TEMPLATE_PATH that needs to be pointing to your custom template path.

ResourceBundle resourceBundle = ResourceBundleUtil.getBundle("content.Language", locale, getClass());

Code: example of getClass() call in the original class that need to be replaced with DDMFormEmailNotificationSender.class when copied to your custom one

private static final String _TEMPLATE_PATH = "/META-INF/resources/notification/form_entry_add_body.soy";

Code: original value of _TEMPLATE_PATH constant

Using Soy template language and Liferay's built-in template processing API, you don't need to compile your .soy file, since it's done during runtime. Therefore, you can leave that out of your compiling script.

transpileJS {
enabled = false
}

Code: if you use Gradle, you can add this to your fragment's build.gradle file

If you're using Liferay Workspace to develop your project, only set your service module bnd.bnd's Import-Package header if you intend to manually declare all the necessary packages in your bundle.

Bundle-Name: ddm-custom-notification-sender
Bundle-SymbolicName: ddm.custom.notification.sender
Bundle-Version: 1.0.0

Code: example of a service module's bnb.bnd file that should work. Bndtools automatically sets the Import-Package header in the final MANIFEST.MF when you don't declare it.

Specific notes for DXP 7.4

  • The Liferay module fragment just needs to expose the internal package com.liferay.dynamic.data.mapping.internal.notification. No need to include the custom template: it will be inside the custom service.
  • The custom service needs to include:
    • The custom template place in a path like src/main/resources/META-INF/resources/notification.
    • A custom class extending DDMFormEmailNotificationSender. The easiest way is to copy the whole original class and modify the attribute _TEMPLATE_PATH and make it point to the custom template path as above.
  • To make sure the custom service is used after redeploying it, make sure to blacklist the original component com.liferay.dynamic.data.mapping.internal.notification.DDMFormEmailNotificationSender following the documentation Blacklisting OSGi Components.
    • This can happen because the reference to the original component _ddmFormEmailNotificationSender in DDMFormInstanceRecordLocalServiceImpl has a reluctant policy-option by default so it will tend to stick to the same implementation. 

Additional Information

¿Fue útil este artículo?
Usuarios a los que les pareció útil: 2 de 11