Send Email Workflow Operation

ID: send-email

Description

The send-email operation invokes the SMTP Service to send an email with the parameters provided. It is useful to send email notifications when some operation(s) have been completed or some error(s) have occurred in a workflow.

The email body, if not specified by body or body-template-file, will consist of a single line of the form: <Recording Title> (<Mediapackage ID>).

Freemarker templates can be used in the following fields to allow replacement with values obtained from the workflow or media package: to, cc, bcc, subject, and body. If body-template-file is specified, the operation will use a Freemarker template file located in <config_dir>/etc/email to generate the email body. A more in deep explanation of how to use Freemarker variables can be found in the Appendix.

Usernames can be provided in to, cc, or bcc in lieu of email addresses so that the user directory is searched and that user's email address is used (see Example 5).

Parameter Table

configuration keys description default value example
body Email body content.
Takes precedence over body-template-file.
<Recording Title> (<Mediapackage ID>) Lecture 1 (4bf316fc-ea78-4903-b00e-9976b0912e4d)
body-template-file Name of file that will be used as a template for the content of the email body. EMPTY templateName
body-html Email body content send as HTML.
Takes precedence over body-html-template-file.
EMPTY <h1>${mediapackage.identifier}</h1>
body-html-template-file Name of file that will be used as a template for the HTML content of the email body. EMPTY templateNameHTML
subject Email subject. EMPTY Operation has been completed
to The field to of the email
i.e. the comma separated list of email accounts the email will be sent to.
EMPTY email-account@email-domain.org,second-account@second-domain.org
cc The field cc of the email
i.e. the comma separated list of email accounts that will receive a carbon copy of the email.
EMPTY email-account@email-domain.org,second-account@second-domain.org
bcc The field bcc of the email
i.e. the comma separated list of email accounts that will receive a blind carbon copy of the email.
EMPTY email-account@email-domain.org,second-account@second-domain.org
address-separator Separator to use for splitting a string into separate email addresses , <tab> ,
skip-invalid-address If the operation should skip invalid addresses instead of failing false true/false

Some other email parameters can be customized in the SMTP Service configuration

If both a text and HTML body is set, a multipart email body is sent.

Variable Substitution

The template will have access to the media package, workflow instance (including its configuration properties and last failed operation), catalogs, and any incidents. Fields should be tested for null/empty values before being used.

Media Package Information

Use ${mediaPackage.FIELD}

Examples

Field How To Get It
media package id ${mediaPackage.identifier}
recording title ${mediaPackage.title}
recording date and time ${mediaPackage.date?datetime?iso_utc} - See Freemarker manual for date manipulation
(extract date only, time only, format, etc)
series title ${mediaPackage.seriesTitle}
series id ${mediaPackage.series}

Workflow Information

Use ${workflow.FIELD}

Field How To Get It
workflow id ${workflow.id}
workflow template id ${workflow.template}
workflow state ${workflow.state}
workflow title ${workflow.title}
workflow description ${workflow.description}
id of parent workflow ${workflow.parentId}
name of workflow creator ${workflow.creatorName}
workflow organization ${workflow.organizationId}

Note that some of these values may not exist depending on the workflow.

Failed Operation Information

Field How To Get It
Operation id ${failedOperation.getId()}
Template ${failedOperation.template}
Date started ${failedOperation.getDateStarted?string("yyyy-MM-dd HH:mm:ss")}
Date completed ${failedOperation.getDateCompleted?string("yyyy-MM-dd HH:mm:ss")}
Execution handling workflow ${failedOperation.getExceptionHandlingWorkflow()}
Execution host ${failedOperation.executionHost}
Failed attempts ${failedOperation.failedAttempts}
Max attempts ${failedOperation.maxAttempts}

Workflow Configuration Properties

Use ${workflowConfig['PROPERTY']}

Last Failed Operation

Operation that caused the workflow to fail. Should be tested before accessing any of its fields:

<#if failedOperation?has_content>Workflow failed in operation: ${failedOperation.template}<!--#if-->

Incidents

In your email template:

<#if incident?has_content>
  <#list incident as inc>
    <#list inc.details as dets>${dets.b}</#list>
  </#list>
</#if>

Catalog fields

Use ${catalogs['FLAVOR']['FIELD']}

Deprecation notice: Opencast versions before 13 used ${catalogs['SUBFLAVOR']['FIELD']} for dublincore/SUBFLAVOR catalogs. This syntax is still accepted but deprecation. Future versions might remove support.

Examples

Field How To Get It
episode creator ${catalogs['dublincore/episode']['creator']}
episode title ${catalogs['dublincore/episode']['title']}
series creator ${catalogs['dublincore/series']['creator']}
series title ${catalogs['dublincore/series']['title']}

Organization properties

Use ${organization['PROPERTY']}

Examples

Property How To Get It
Engage URL ${organization["org.opencastproject.engage.ui.url"]}

Examples

Example 1

Media package title in subject field, default email body.

<operation
  id="send-email"
  fail-on-error="false"
  exception-handler-workflow="email-error"
  description="Sending email to user after media package is published">
  <configurations>
    <configuration key="to">email-account@email-domain.org</configuration>
    <!-- This is going to be replaced with the media package title -->
    <configuration key="subject">${mediaPackage.title} has been published</configuration>
    <!-- Neither body nor body-template-file specified so default body <Recording Title> (<Mediapackage ID>)<br>is sent -->
  </configurations>
</operation>

Example 2

To and subject are inline templates; the email body uses a template file named sample stored in…/etc/email:

<operation
  id="send-email"
  fail-on-error="false"
  exception-handler-workflow="email-error"
  description="Sending email to user before holding for edit">
  <configurations>
    <!-- This is going to be replaced with the episode catalog publisher field, which in this example it is assumed
    it contains a notification email address -->
    <configuration key="to">${catalogs['dublincore/episode']['publisher']}</configuration>
    <!-- This is going to be replaced with the episode catalog title field -->
    <configuration key="subject">${catalogs['dublincore/episode']['title']} is ready for EDIT</configuration>
    <!-- Email body is going to be built using the sample template found in <config_dir>/etc/email -->
    <configuration key="body-template-file">sample</configuration>
  </configurations>
</operation>

Template: sample

The contents of the …/etc/email/sample email template:

Event Details
<#if catalogs['dublincore/series']?has_content>
Series Title: ${catalogs['dublincore/series']['title']}
Instructor: ${catalogs['dublincore/series']['contributor']}
</#if>
Media Package Id: ${mediaPackage.identifier}
Title: ${mediaPackage.title}
Workflow Id: ${workflow.id}
Event Date: ${mediaPackage.date?datetime?iso_local}

Example 3

Email address entered via admin UI as a workflow configuration parameter:

<operation
  id="send-email"
  fail-on-error="false"
  exception-handler-workflow="email-error"
  description="Sends email">
  <configurations>
    <configuration key="to">${workflowConfig['emailAddress']}</configuration>
    <configuration key="subject">Media package has been published</configuration>
    <configuration key="body-template-file">sample</configuration>
  </configurations>
</operation>

Workflow Configuration Panel:

<configuration_panel>
<![CDATA[
   <!-- Add after the other configuration fields (Holds, Archive, etc) -->
   <fieldset>
      <legend>Notification</legend>
      <ul class="oc-ui-form-list">
        <li class="ui-helper-clearfix">
          <label class="scheduler-label">
            <span class="color-red">* </span><span id="i18n_email_label">Email Address</span>:
          </label>
          <span id="emailconfig">
            <input id="emailAddress" name="emailAddress" type="text" class="configField"
                   value="my-email-account@my-email-domain.org"/>
          </span>
        </li>
      </ul>
    </fieldset>

    <script type="text/javascript">

      // Add email variable
      var emailAddress = $('input#emailAddress');

      // Register email configuration property
      ocWorkflowPanel.registerComponents = function(components){
        /* components with keys that begin with 'org.opencastproject.workflow.config' will be passed
         * into the workflow. The component's nodeKey must match the components array key.
         *
         * Example:'org.opencastproject.workflow.config.myProperty' will be available at ${my.property}
         */
        // After the other components (Hold, Archive, etc), add:
        components['org.opencastproject.workflow.config.emailAddress'] = new ocAdmin.Component(
          ['emailAddress'],
          {key: 'org.opencastproject.workflow.config.emailAddress'},
          {getValue: function(){ return this.fields.emailAddress.value;}
          });

          //etc...
      }
      ocWorkflowPanel.setComponentValues = function(values, components){
        // After the other components (Hold, Archive, etc), add:
        components['org.opencastproject.workflow.config.emailAddress'].setValue(
          values['org.opencastproject.workflow.config.emailAddress']);
      }
    </script>
]]>
</configuration_panel>

Example 4

In error handling workflow (email-error):

<operation
  id="send-email"
  fail-on-error="true"
  exception-handler-workflow="error"
  description="Sends email">
    <configurations>
    <!-- Note that you can use variable substitution in to, subject, body
         e.g. ${(catalogs['dublincore/episode']['FIELD']!'root@localhost'}  -->
    <configuration key="to">root@localhost</configuration>
    <configuration key="subject">Failure processing a mediapackage</configuration>
    <configuration key="body-template-file">errorDetails</configuration>
  </configurations>
</operation>

Template: errorDetails

The contents of the /etc/email/errorDetails email template:

Error Details

<#if catalogs['dublincore/series']?has_content>
Course: ${catalogs['dublincore/series']['subject']!'series subject missing'} - ${catalogs['dublincore/series']['title']!'series title missing'}
Instructor: ${catalogs['dublincore/series']['contributor']!'instructor missing'}
</#if>
Title: ${catalogs['dublincore/episode']['title']!'title missing'}
Event Date: ${mediaPackage.date?datetime?iso_local}

<#if failedOperation?has_content>
  Workflow failed in operation: ${failedOperation.getId()}-${failedOperation.template}
  Started: ${failedOperation.dateStarted?string("yyyy-MM-dd HH:mm:ss")}
  Ended: ${failedOperation.dateCompleted?string("yyyy-MM-dd HH:mm:ss")}
  Execution Host: ${failedOperation.executionHost}
</#if>

Logged incident of the error looks like this:

<#if incident?has_content>
  <#list incident as inc>
    <#list inc.details as dets>${dets.b} </#list>
  </#list>
</#if>

Example 5

The username is stored in the episode dublin core contributor field. There's a user jharvard with email jharvard@harvard.edu defined in the system. The message will be sent to jharvard@harvard.edu:

   <operation
      id="send-email"
      fail-on-error="false"
      description="Notify user associated to this recording that it is ready to be trimmed">
      <configurations>
        <configuration key="to">${(catalogs['dublincore/episode']['contributor'])}</configuration>
        <configuration key="subject">Recording is ready for EDIT</configuration>
        <configuration key="body-template-file">eventDetails</configuration>
      </configurations>
    </operation>

Episode Dublin Core

<?xml version="1.0" encoding="UTF-8"?>
<dublincore xmlns="http://www.opencastproject.org/xsd/1.0/dublincore/"
    xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <dcterms:contributor>jharvard</dcterms:contributor>
    <dcterms:created>2018-05-01T16:14:00Z</dcterms:created>
    <dcterms:extent xsi:type="dcterms:ISO8601">PT17M1.933S</dcterms:extent>
    <dcterms:isPartOf>20180229999</dcterms:isPartOf>
    <dcterms:spatial>classroom-20</dcterms:spatial>
    <dcterms:temporal>start=2018-05-01T16:14:00Z; end=2018-05-01T16:31:00Z;
        scheme=W3C-DTF;</dcterms:temporal>
    <dcterms:title>Test Lecture</dcterms:title>
</dublincore>

Example 6

Email the user which started the workflow. Requires the user account to have a valid email address.

<operation
    id="send-email"
    description="Sending email to user after media package is published">
  <configurations>
    <!-- Lookup email address of the creator -->
    <configuration key="to">${workflow.creatorName}</configuration>
    <configuration key="subject">${mediaPackage.title} has been published</configuration>
  </configurations>
</operation>

Appendix: Freemarker Variable Usage Guide

Freemarker is a powerful template engine that allows you to embed dynamic content in your templates. One of the key features is its ability to handle variables. This guide will walk you through the basics of using variables in Freemarker templates.

Declaring Variables

In Freemarker, you don't explicitly declare variables. You usually pass them from your application code to the template. However, you can also set local variables using the <#assign> directive:

<#assign myVariable = "Hello, World!">

Accessing Variables

To access a variable, you can simply enclose its name in ${}:

${myVariable}

Checking for Null or Empty Values

Freemarker provides the ?has_content built-in to check if a variable has content:

<#if myVariable?has_content>
  ${myVariable}
<#else>
  Variable is empty or null.
</#if>

Default Values

You can provide a default value for a variable using the ! operator:

${myVariable!"Default Value"}

Nested Fields

To access nested fields, you can use the dot notation:

${user.name}

And you can chain checks to avoid null pointer exceptions:

<#if user?has_content && user.name?has_content>
  ${user.name}
<#else>
  Name is not available.
</#if>

Formatting Dates

Freemarker allows you to format date variables using the ?string built-in:

${dateVariable?string("yyyy-MM-dd")}

You can also specify the type of date-like value using ?date, ?time, or ?datetime:

${dateVariable?datetime}

Lists and Maps

You can iterate over lists and maps using the <#list> directive:

<#list myList as item>
  ${item}
</#list>

For maps:

<#list myMap?keys as key>
  ${key}: ${myMap[key]}
</#list>

Arithmetic Operations

Freemarker supports basic arithmetic operations:

${x + y}
${x - y}
${x * y}
${x / y}

Conditional Statements

You can use <#if>, <#elseif>, and <#else> for conditional logic:

<#if x > y>
  X is greater.
<#elseif x == y>
  X and Y are equal.
<#else>
  Y is greater.
</#if>

Functions and Macros

You can define reusable pieces of code using <#macro>:

<#macro greet name>
  Hello, ${name}!
</#macro>

<@greet name="John"/>

This guide provides a quick overview of how to use variables in Freemarker. For more advanced topics and complete documentation, you can refer to the official Freemarker documentation.