Back in the days, when Microsoft has introduced Dynamics 365 for Finance and Operations, one of the missing functionalities were alerts. It took some time for Microsoft to re-implement alerts in D365FO, which happened in 2018 with version 8.0.
However, the new standard alert functionality did not turn out to be too flexible:
- Email message body became hard-coded, and it was not possible to use email templates, neither system nor organizational.
- Initially, alerts and workflows were part of the same framework and it was possible to use Batch email sending status form and Email distributor batch to send alert emails from D365FO. Later, that also changed, so it was not possible to use these options to send alert emails.
What has Docentric improved?
We have supported the above-mentioned functionalities, and even more:
- Previously, all alert emails used the same Email ID (Email template) and now you can select from different email templates on an alert rule.
- Previously, you first had to use Batch email sending status and Email distributor batch, but later you could not use them. And now with Docentric you can choose between Email provider and Email distributor batch.
- A bonus: We have improved Email distributor batch and it is now possible to select arbitrary records for different batch jobs. Also, we have introduced Email distributor clean-up.
- The Batch email sending status form is greatly improved by adding a user-friendly form to preview and update outgoing email messages. We have added email fields (Cc, Bcc, Reply-to, etc.) and metadata fields (Company ID, Context information, Account ID, Document ID, Alert rule, Notification ID, etc.) to allow better monitoring and tracking of outgoing emails, but also scheduling different Email distributor batch jobs for different emails.
- Custom placeholders can be used, including Image and HTML placeholders.
- We introduced an improved WYSIWYG Email template body editor with list of available placeholders.
- The email recipient field may contain placeholders.
- Cc and Bcc fields are added and may also contain placeholders.
- Alert email attachments can be added through settings on the email template using Tags.
A use case: Notify vendors that certificates are about to expire
We will demonstrate the alert rules and alert emails improvements done by Docentric on an example of notifying vendors that their certificates will expire soon.
Since the vendors are external companies, an alert pop-up notification is not useful, but the vendor needs to be notified by email. We also want to specify the body of the email message with the dynamic and relevant data, so we will use an email template for that purpose. Finally, we do not want to send alert emails for certificate expiration along with other alert emails, that’s why we will schedule a separate series of batch jobs.
First, we need to configure an email template, at the organization or system level, depending on the business need. In our example, we will use Organization email templates (Organization administration > Setup > Organization email templates):
We see the Docentric settings fast tab with the necessary settings. Note a class that implements the placeholders for our use case, and in this example it is named DocVendorCertificateAlertEmailHandler_CCBA.
We configure the email message body using a combination of static text and placeholders, which will provide the dynamic content.
Once the email template is ready, we can create an alert rule that will raise an alert and send an email to the vendor, warning him that his certificate is about to expire. In our example, the field to be monitored is Expiration date, and our goal is to send notification to the vendor 7 days before the certificate expires.
We configured to send an email when an alert occurs and to send that email using Docentric. We also selected an email template we have previously created for this purpose, and we chose to send an alert email using Email distributor batch (the Email processing option instead of the default, Synchronous mode).
And finally, let’s see what our email looks like when the alert we set on the vendor certificates occurs. We specified sending the alert email using Email distributor batch, that’s why we will find the prepared email message in System administration > Periodic tasks > Email processing > Batch email sending status (how convenient!):
(1) Show message -> Show the message in a new dialog, where we can review it,
(2) Download message -> Download the email message, which you can open, e.g. in MS Outlook,
(3) Update message -> Modify furthermore before Email distributor batch processes it.
Here is the example of the generated email message:
class DocVendorCertificateAlertEmailHandler_CCBA extends DocEmailTemplateAdvancedHandlerWFAL
// Define custom placeholder name
public const str VendorPurpose = 'PURPOSE_';
public const str VendorPrimaryEmail = 'VendorPrimaryEmail';
public const str VendorName = 'VendorName';
/// Gets the class description.
/// <returns>Class description</returns>
public ClassDescription description()
return "Vendor certificate alert handler for CCBA";
/// Fills the given custom placeholder map with placeholder names and their display names.
/// <param name = "_customPlaceholderDefinitionMap">Map of custom placeholders and their display names: Map(DocPlaceholderNameEx -> DocPlaceholderDescription)</param>
protected void defineCustomPlaceholders(Map _customPlaceholderDefinitionMap)
_customPlaceholderDefinitionMap.insert(DocVendorCertificateAlertEmailHandler_CCBA::VendorPrimaryEmail, 'CCBA - Vendor primary email');
_customPlaceholderDefinitionMap.insert(DocVendorCertificateAlertEmailHandler_CCBA::VendorName, 'CCBA - Vendor name');
/// Fills the mappings with custom placeholder values.
/// <param name = "_mappings">Map of custom placeholders and their values: Map(DocPlaceholderNameEx -> DocPlaceholderValue)</param>
/// <param name = "_contextInfo">Context info that can be provided for filling _mappings with placeholders' values</param>
public void fillMappingsWithCustomPlaceholderValues(Map _mappings, container _contextInfo = conNull())
// Check if the source of the notification event is Alert.
if (this.getEventNotificationSource() != DocEmailTemplateEventNotificationSource::Alert)
DocEmailTemplateAlertContextInfoParams alertContextInfo_CCBA = this.getAlertContextInfo();
// Check if the alert context object is not null
if (alertContextInfo_CCBA == null)
// Check if the alert is created for Vendor certification table
if (alertContextInfo_CCBA.parmAlertTable().TableId != tableNum(VendCertification))
VendCertification vendCertification = alertContextInfo_CCBA.parmAlertTable();
VendTable vendTable = VendTable::findByPartyRecId(vendCertification.VendParty);
VendName vendName = vendTable.name();
str vendPrimaryEmailAddress = DocDirPartyHelper::getVendorPrimaryEmailAddress(vendTable);
// Find the alert rule
EventRule eventAlertRule = alertContextInfo_CCBA.parmEventRule();
// Resolve recepients placeholders in TO
DocVendorCertificateAlertEmailHandler_CCBA::resolveEmailRecipients(eventAlertRule.EmailRecipients, _mappings, vendTable);
// Resolve recepients placeholders in CC
DocVendorCertificateAlertEmailHandler_CCBA::resolveEmailRecipients(eventAlertRule.EmailCc_DC, _mappings, vendTable);
// Resolve recepients placeholders in BCC
DocVendorCertificateAlertEmailHandler_CCBA::resolveEmailRecipients(eventAlertRule.EmailBcc_DC, _mappings, vendTable);
/// Subscriber for changing the alert language which affects which email body of the email template will be used
/// as well as how alert standard placeholders will be formatted.
/// <param name = "_alertContextInfo">Alert context info containg the EventRule and EventInbox records, alert table buffer, EventType class instance, Url to alert entity</param>
/// <param name = "_currentAlertLanguageId">Current alert language ID</param>
/// <param name = "_eventHandlerResult">
/// Set _eventHandlerResult.hasResult(true) and _eventHandlerResult.result(newAlertLanguageId) if you need to change the _currentAlertLanguageId parameter.
/*[SubscribesTo(classStr(DocEmailTemplateDelegates), delegateStr(DocEmailTemplateDelegates, changeLanguageForAlertEmailDelegate))]
public static void DocEmailTemplateHelper_changeLanguageForAlertEmailDelegate(DocEmailTemplateAlertContextInfoParams _alertContextInfo, LanguageId _currentAlertLanguageId, EventHandlerResult _eventHandlerResult)
str newLanguage = 'de';
/// Resolves placeholders in the string of email recipients. Resolves vendor purpose placeholders (%PURPOSE_%)
/// first, if present, and then other placeholders.
/// <param name = "_emailRecipients">Email recipients string with placeholders</param>
/// <param name = "_mappings">Map(DocPlaceholderNameEx (str) -> DocPlaceholderValue (str))</param>
/// <param name = "_vendTable">vendor table buffer</param>
/// <param name = "_htmlEncode">Should the placeholder values from _mappings be HTML encoded</param>
/// <returns>Email recipients string with the replaced placeholders</returns>
public static void resolveEmailRecipients(str _emailRecipients, Map _mappings, VendTable _vendTable, boolean _htmlEncode = true)
// Email address delimiters
System.String delimiters = new System.String();
str emailRecipientAddress = '';
if (_emailRecipients == '')
System.String emailRecipients = _emailRecipients;
// Split email recipient string into the string array
System.String emailRecipientsArray = emailRecipients.Split(delimiters, System.StringSplitOptions::RemoveEmptyEntries);
System.Collections.IEnumerator emailRecipientsArrayEnumerator = emailRecipientsArray.GetEnumerator();
System.String emailRecipient = emailRecipientsArrayEnumerator.Current;
// Check if an email recipient is a placeholder
int len = strLen(emailRecipient);
int position = strScan(emailRecipient, DocConstantPlaceholderET::PlaceholderStartSymbol, 1, len);
if (position > 0)
int positionEnd = strScan(emailRecipient, DocConstantPlaceholderET::PlaceholderEndSymbol, position + 1, len);
str placeholderName = subStr(emailRecipient, position + 1, positionEnd - position - 1);
// Check if a placeholder is a vendor purpose placeholder (%PURPOSE_%)
if (strScan(placeholderName, DocVendorCertificateAlertEmailHandler_CCBA::VendorPurpose, 1, strLen(placeholderName)) > 0)
// Get a vendor purpose
str vendPurpose = strDel(placeholderName, 1, strLen(DocVendorCertificateAlertEmailHandler_CCBA::VendorPurpose));
if (vendPurpose != '')
emailRecipientAddress = DocDirPartyHelper::getVendorEmailAddressesFromPurpose(_vendTable, vendPurpose);
//replace "," wiht ";"
emailRecipientAddress = strReplace(emailRecipientAddress, ',', ';');
_mappings.insert(DocVendorCertificateAlertEmailHandler_CCBA::VendorPurpose + vendPurpose, emailRecipientAddress);