Skip to content

Easy Email templating

With Orchestra Easy Email (orchestra_easy_email) every notification is an Easy Email email. This page shows how to word and lay out those emails with as few templates as possible: one layout for the whole site, one Easy Email template for most notifications, and the words that change from step to step set on the step itself.

For how a notification is triggered and who receives it, see Notifications; for the channels and when an email leaves, see Notification delivery.

Three layers

An Orchestra email is put together from three places, each owning one thing:

Layer Where it lives What it owns
Layout a Twig file in the theme (easy-email-body-html.html.twig) the look shared by every email: header, footer, colors, and the zones an Orchestra email fills in
Template an Easy Email template (configuration) the subject, and the body of one kind of email
Values the Values of a workflow node the words of one step: its title, its text, its link

The layout wraps the template's body ({{ body }}), and both read the node's values: the template through tokens ([orchestra:node-value:title]), the layout through a Twig variable ({{ node_values.title }}). Put each thing in the lowest layer that owns it and nothing is written twice.

flowchart LR
  N[Notify or task node<br/>Values: title, body, link] -->|tokens| T[Easy Email template<br/>subject + body]
  N -->|node_values| L[Theme layout<br/>easy-email-body-html.html.twig]
  T -->|"{{ body }}"| L
  L --> M[Email]

Which template an email uses

Every notification Orchestra sends carries a type: a machine name (lowercase letters, digits and underscores) that says what kind of message it is. The type is not the template; it is what the template is looked up by.

Where the type comes from

The email is sent by Its type Where the type is set
a Notify node the node's Notification type, generic unless changed the Notify node's own settings in the modeler, beside its Recipients
Notify on arrival, on a node that parks the feature's Notification type, interaction_link when left empty the node's Notify on arrival section in the modeler
a task being assigned or reassigned task_assigned fixed by Orchestra Inbox, not configurable
an escalation timer reminding a task task_reminder fixed by Orchestra Inbox, not configurable
a task being canceled task_canceled fixed by Orchestra Inbox, not configurable
your own code whatever it passes the $type argument of OrchestraNotification::notify()

So the types you choose are those of your Notify nodes and arrival notifications. Giving two Notify nodes the same type sends them through the same template; giving one a type of its own, request_rejected say, lets it have a template of its own, orchestra_request_rejected. With node values, most nodes can keep generic and still say different things.

How the type finds a template

The channel turns the type into a list of template ids, from the most specific to the most general, and sends the email with the first one that exists in Structure > Email templates. The ids combine the type with the instance's tenant (its tenant's machine name, when the instance belongs to one), its workflow (the workflow's machine name) and the workflow's application (its machine name, when the workflow belongs to one):

  1. orchestra_<tenant>_<workflow>_<type>
  2. orchestra_<tenant>_<application>_<type>
  3. orchestra_<tenant>_<type>
  4. orchestra_<workflow>_<type>
  5. orchestra_<application>_<type>
  6. orchestra_<type>
  7. orchestra_generic, the catch-all

Tenant, application and workflow ids share one name space: give them distinct machine names, or a template named after one also reaches the other.

A level whose part is missing (no tenant, no application) is skipped. For example, an instance of the leave_request workflow, in the hr application and the city_hall tenant, reaching a Notify node whose type is request_rejected tries:

  1. orchestra_city_hall_leave_request_request_rejected
  2. orchestra_city_hall_hr_request_rejected
  3. orchestra_city_hall_request_rejected
  4. orchestra_leave_request_request_rejected
  5. orchestra_hr_request_rejected
  6. orchestra_request_rejected
  7. orchestra_generic

So a template can be made for one workflow, for every workflow of one application, for one tenant, for one type anywhere, or for everything, just by the id it is given, and the most specific one wins.

A process variable named orchestra_notification_template_suffix adds a variant at every level: with the suffix urgent, orchestra_request_rejected_urgent is tried before orchestra_request_rejected, and so on for each of the ids above. The variable is read as it stands when the step sends the notification (a notification sent later, from cron, keeps the value of that moment), so a step that sets it switches the templates of every notification after it.

When none of the ids exists, not even orchestra_generic, the email is not sent and the Orchestra log names the ids that were tried.

The shipped templates

Template Used by
orchestra_generic every type that has no template of its own, Notify nodes left at generic included
orchestra_task_assigned task_assigned
orchestra_task_reminder task_reminder
orchestra_task_canceled task_canceled
orchestra_interaction_link interaction_link, the arrival notifications left at the default type

The fewer types you use, the fewer templates you keep: a Notify node left at generic needs no template of its own, its words come from its values.

Tokens

An Easy Email template, and the text of a node value, can use:

Token What it reads
[orchestra:*] the instance and the step: label, workflow-label, node-label, status-label, correlation-key, created, … (see Tokens)
[orchestra:var:NAME] a process variable, with dot paths and entity chains ([orchestra:var:validator_uid:user:display-name])
[orchestra:node-value:NAME] a value the node sets (see below)
[orchestra-notification:label], […:link], […:message] what the notification itself carries, which depends on who sent it (below)
[user:*] the recipient, when the email goes to one person
[site:*] the site

What each notification carries under [orchestra-notification:*]:

Sent by label link message
Notify node – – –
task_assigned the task's label the inbox –
task_reminder the task's label the inbox the timer's message
task_canceled the task's label – why it was canceled
arrival (interaction_link) the step's label the step itself –

[user:*] reads the person the email is addressed to, so it resolves on any email sent to one person: an email with no Cc, and each blind copy, which goes out on its own. An email shared between a To and a Cc has several readers and leaves [user:*] empty.

What a process variable brings in is data: a link holder who typed [site:mail] into a form field sees [site:mail] in the email, never the site's address. Easy Email renders the text again after Orchestra, and this holds through every pass.

Easy Email renders every email in the site's default language, whatever the recipient's own language. On a multilingual site, values and templates are translated through configuration translation, and the email is still sent in the default language.

Node values

A Notify node, and every node that parks (task, wait, interaction), has a Values section: a list of names, each mapped to a text that may contain tokens.

Name Text
subject Request [orchestra:var:reference] approved
title Your request is approved
body [orchestra:var:validator_uid:user:display-name] approved it on [orchestra:var:approved_on].
link [orchestra-notification:link]

The rules:

  • A name is a machine name: lowercase letters, digits and underscores, unique on the node.
  • [orchestra:node-value:NAME] reads the value of the node the step stands on. A reminder reads its task's node, since its timer stands there.
  • A name the node does not set reads as nothing, so one template serves nodes that set a title and nodes that set none.
  • The tokens inside a value are replaced with the same data as the template: the instance, the step, the notification, the recipient.
  • A value is plain text. Its markup is escaped, so write the markup in the template or the layout and keep only words in the value.
  • A value does not read another value.
  • A value is read when the email is rendered, from the workflow version the instance runs, so a reminder sent days later reads the variables as they are then.

Use the same names on every node and the templates and the layout stay generic. A convention that covers most emails:

Name Used for
subject the subject line
title the heading
intro a first sentence, before the body
body the main text
details a secondary block, shown only when set
link the address of the call to action
link_label the words of the call to action

A value that is only a token

A value whose text is a single token reads as empty when that token does, which makes a block of the layout depend on the data rather than on the node:

Name Text
reason [orchestra:var:rejection_reason]
{% if node_values.reason %}
  <div class="reason"><strong>{{ 'Reason'|t }}</strong> {{ node_values.reason }}</div>
{% endif %}

The block shows only when the variable holds something. Keep the label in the layout: a value Reason: [orchestra:var:rejection_reason] is never empty, and its block would always show.

Layout files

Easy Email renders each body through a theme template. A theme overrides it by placing a file of the same name in its templates/ directory, then clearing the cache.

File Lays out
easy-email-body-html.html.twig the HTML body of every email of the site
easy-email-body-html--TEMPLATE.html.twig the HTML body of one Easy Email template
easy-email-body-plain.html.twig the plain-text body of every email
easy-email-body-plain--TEMPLATE.html.twig the plain-text body of one template

TEMPLATE is the template id with its underscores turned into hyphens: the template orchestra_task_reminder is laid out by easy-email-body-html--orchestra-task-reminder.html.twig, a per-tenant orchestra_city_hall_task_assigned by easy-email-body-html--orchestra-city-hall-task-assigned.html.twig. There is no file for "every Orchestra email": the site-wide file lays them out, and puts their own zones in {% if orchestra %}.

The layout gets:

Variable What it holds
body the template's body, its tokens already replaced
easy_email the Easy Email entity
orchestra Orchestra emails only: the Orchestra tokens, by name
node_values Orchestra emails only: the node's values, by name

orchestra reads [orchestra:*] with an underscore for each hyphen: orchestra.label, orchestra.workflow_label, orchestra.node_label, orchestra.status_label, orchestra.correlation_key. It does not read process variables: a layout that needs one reads a value the node relays. node_values reads the node's values: node_values.title is [orchestra:node-value:title].

Both are plain text Twig escapes, both read as empty for a name nobody sets, and both work with Twig's own fallbacks: {{ node_values.title ?: orchestra.node_label }} shows the node's title, or the step's label where the node sets none, which no token can do. Each name is rendered the first time the layout reads it, so a layout pays only for what it shows.

The file is read from the theme that renders the email, which is not always the front theme: an email leaves before the page, after it or from cron. Which theme that is, and where it is set, depends on the HTML mailer (see step 2).

Step by step

This walks through the most generic setup: one layout for the site, one Easy Email template for most notifications, and the words on the nodes. Each step says where to click, what to write, and how to check it.

Step 1: use the Easy Email channel

  1. Make sure the site sends HTML email. Easy Email needs an HTML mailer, which it does not install itself: Mailer Plus (the symfony_mailer project, with its Mailer override module so that Easy Email's mail goes through it), Drupal Symfony Mailer Lite, or Mime Mail. Drupal's own mailer sends plain text, and the HTML would arrive flattened.
  2. Enable Orchestra Easy Email (orchestra_easy_email). It brings Easy Email and installs the four Orchestra templates.
  3. Uninstall Orchestra Mail (orchestra_mail) if it is enabled: the two channels send independently, so with both every recipient gets two emails. The status report warns while both are on.

Check: Structure > Email templates (/admin/structure/email-templates/templates) lists orchestra_generic, orchestra_task_assigned, orchestra_task_reminder, orchestra_task_canceled and orchestra_interaction_link.

Step 2: know which theme renders the emails

The layout lives in a theme, so it matters which theme an email is rendered in. An email leaves at different moments, before the page, after it, or from cron, and by default it is rendered in whichever theme is active then: the front theme, the administration theme when an operator acts in the back office, the default theme from cron. Which setting decides depends on the HTML mailer:

  • Mailer Plus (symfony_mailer, with its Mailer override and Mailer policy modules): the Theme adjuster of a mailer policy decides, at Configuration > System > Mailer Plus > Policy (/admin/config/system/mailer/policy). The policy that applies to every email, _, ships with Active with fallback: the active theme, except the administration theme, which falls back to the default theme. So with that setting, put the layout in the default (front) theme, or pick one theme outright in the adjuster.
  • Drupal Symfony Mailer Lite or Mime Mail: either is selected through Mail System, whose Theme to render the emails setting decides (Configuration > System > Mail System); set it to the theme that holds the layout rather than "Current".

Step 3: write the site layout

In that theme, create templates/easy-email-body-html.html.twig (the folder may hold sub-folders; Drupal finds the file by name):

<table role="presentation" width="100%" cellpadding="0" cellspacing="0" class="email">
  {% block header %}
    <tr><td class="logo">
      <img src="public://logo.png" height="64" alt="{{ 'My site'|t }}">
    </td></tr>
    <tr><td class="header">{{ 'My site'|t }}</td></tr>
  {% endblock %}

  {% if orchestra %}
    {% block orchestra_heading %}
      <tr><td class="step">{{ orchestra.workflow_label }}: {{ orchestra.label }}</td></tr>
      <tr><td class="title"><h1>{{ node_values.title ?: orchestra.node_label }}</h1></td></tr>
      {% if node_values.intro %}
        <tr><td class="intro"><p>{{ node_values.intro }}</p></td></tr>
      {% endif %}
    {% endblock %}
  {% endif %}

  {% block content %}
    <tr><td class="content">{{ body }}</td></tr>
  {% endblock %}

  {% block action %}
    {% if node_values.details %}
      <tr><td class="details">{{ node_values.details }}</td></tr>
    {% endif %}
    {% if node_values.link %}
      <tr><td class="action">
        <a class="button" href="{{ node_values.link }}">{{ node_values.link_label ?: 'Open'|t }}</a>
      </td></tr>
    {% endif %}
  {% endblock %}

  {% block footer %}
    <tr><td class="footer">{{ 'Sent by My site.'|t }}</td></tr>
  {% endblock %}
</table>

Line by line:

  • The header and footer are outside {% if orchestra %}: every email of the site gets them, Orchestra's and other modules' alike.
  • The logo is a file of the site (public://logo.png, the one uploaded in the theme's settings), written by its own public:// address so that Mailer Plus's Inline images adjuster embeds it in the email: add that adjuster to the mailer policy. To have the logo on every email of the site, put it in Mailer Plus's wrapper instead, not in both. See Logo and images, which also covers linking the image rather than embedding it.
  • {% if orchestra %} holds what only an Orchestra email has: here its workflow and instance, and a heading.
  • node_values.title ?: orchestra.node_label shows the node's title value, or the step's own label when the node sets none, so every Orchestra email has a heading.
  • {{ body }} is the Easy Email template's body, its tokens already replaced.
  • The intro, details and link zones show only when the node sets them.
  • Each zone is a {% block %}, so a template that needs another layout extends this one and replaces only the blocks that differ (step 8).
  • Email clients ignore most CSS: keep styles inline (style="...") in a real layout; the classes here only name the zones.

Then create templates/easy-email-body-plain.html.twig, the same for the plain-text alternative:

{% if orchestra %}{{ node_values.title ?: orchestra.node_label }}

{% endif %}{% if node_values.intro %}{{ node_values.intro }}

{% endif %}{{ body }}
{% if node_values.details %}

{{ node_values.details }}{% endif %}{% if node_values.link %}

{{ node_values.link_label ?: 'Open'|t }}: {{ node_values.link }}{% endif %}

Clear the cache (drush cr) so Drupal finds the new files.

Step 4: reduce the template to tokens

Open Structure > Email templates, then Edit on orchestra_generic, and under Content:

  1. Subject: [orchestra:node-value:subject].
  2. HTML body: <p>[orchestra:node-value:body]</p>, in a text format that keeps the markup (Full HTML).
  3. Generate plain text body from HTML body: leave it off, and set Plain text body to [orchestra:node-value:body]; or turn it on and let Easy Email derive the plain text from the HTML.
  4. Inbox preview, the line mail clients show after the subject: [orchestra:node-value:intro], or leave it empty.
  5. Save.

Replacement patterns, on the same form, lists Easy Email's own and the site-wide tokens only; the Orchestra ones are in the Tokens table above, and in the token browser of a node's Values section. The template now holds no words of its own: everything it says comes from the node.

Step 5: set the words on the nodes

Open the workflow in the Complete modeler and, for each Notify node:

  1. Leave Notification type at generic, so the node uses orchestra_generic.
  2. Open its Values section and click Add value for each name:
Name Text
subject [orchestra:label]: approved
title Your request is approved
intro Good news, [user:display-name].
body [orchestra:var:validator_uid:user:display-name] approved it.
link [orchestra:var:request_url]
link_label See the request

The token browser below the list helps find tokens (with the Token module). 3. Save the workflow. If it is versioned, publish a version: an instance reads the values of the version it runs, so the new words reach the instances started on the new version.

A second Notify node, on the "rejected" branch, sets its own subject, title and body, and a details value with [orchestra:var:rejection_reason]. Both send the same template through the same layout.

Step 6: try it

  1. To keep a copy of what is sent, edit the template and tick Save emails of this type by default under Email storage. Sent emails then appear in Reports > Email log (/admin/reports/email).
  2. Run an instance through the Notify node.
  3. Check the email: subject from subject, heading from title (or the step's label), the zones the node sets, nothing for the ones it does not.
  4. Nothing arrived? The Orchestra log names the template ids it tried; see Troubleshooting.

Turn the email storage option off afterwards if the log should not keep every email.

Step 7: the task and arrival emails

task_assigned, task_reminder, task_canceled and the arrival email keep their own templates, because they carry what a Notify node does not: the task's label, the inbox or step link, the reminder's message, why a task was canceled. They already go through the site layout. To make them generic too:

  1. Reduce each one's body to the node's words, as in step 4: for orchestra_task_assigned, HTML body <p>[orchestra:node-value:body]</p>. Keep a subject that always resolves, [orchestra-notification:label], since a task node may set no subject.
  2. On each task (or interaction) node, set title and body in its values.
  3. To show the layout's button on task emails, add a value link with the text [orchestra-notification:link]: it relays the inbox or step link the notification carries, which the layout cannot read otherwise.

A reminder reads the same node's values as the assignment, since its timer stands on the task's node. To word it differently, use [orchestra-notification:message] (the timer's message) in orchestra_task_reminder, or lay that template out on its own (below).

Step 8: one template laid out on its own

To give one template a different layout, create a file named after it, its underscores turned into hyphens, templates/easy-email-body-html--orchestra-task-reminder.html.twig for orchestra_task_reminder, and have it extend the site layout, replacing only the blocks that differ:

{% extends 'easy-email-body-html.html.twig' %}

{% block orchestra_heading %}
  <tr><td class="badge">{{ 'Reminder'|t }}</td></tr>
  {{ parent() }}
{% endblock %}

extends names the site layout by its file name, and Drupal finds it in the theme. The reminder keeps the header, the content, the button and the footer of every other email, and adds a badge above the heading; {{ parent() }} keeps what the site layout had in that block. Clear the cache after adding the file.

See Sharing parts between layouts for the other ways to reuse a layout.

Keep templates working when a node sets nothing

A template cannot fall back: a subject read only from a value is empty on a node that sets none. Either set that value on every node that uses the template, or write the subject from a token that always resolves ([orchestra:label], [orchestra-notification:label]), or both: [orchestra:label] [orchestra:node-value:subject]. The layout can fall back (?:), the template cannot.

Logo and images

A mailbox cannot open a path of the site. An image in an email has to travel inside the email (embedded), or be fetched from a full address when the email is opened, which many mail clients do only once the reader allows images. Embedding is the recommended way: the logo shows at once, everywhere.

Use a small PNG or JPEG, not SVG: Gmail and Outlook do not show it.

  1. Put the logo in the site's files, for example the one uploaded in the theme's settings: public://logo.png.
  2. In the mailer policy that applies to every email, _, at Configuration > System > Mailer Plus > Policy, add the Inline images adjuster.
  3. In the layout, write the image with the file's own address, public://…, not a web address:
<img src="public://logo.png" height="64" alt="{{ 'My site'|t }}">

When the email is sent, Inline images reads that file from the site's files, attaches it to the email, and points the src to the attachment (cid:). The email then carries the logo (here 20 KB), so keep the image small.

It works in Mailer Plus's wrapper, email-wrap.html.twig, for a logo on every email of the site, as well as in Easy Email's layout, easy-email-body-html.html.twig, for the Orchestra emails only: Inline images runs after the email is wrapped. Put the logo in one of them, not both, or it shows twice.

The public:// address is what keeps it simple. A web address in the src (/sites/…/logo.png, or a file of the theme) makes Inline images download the image from the site's own address while sending, which needs the server to reach its public address, and from cron the site's URL; a local Docker setup usually cannot. public:// is read from disk, and needs neither. A public:// address only works where Inline images processes the email: without that adjuster, the src reaches the mail client as it is, and the image does not show.

Fetched

To link the image instead, give it a full address.

  • In Easy Email's layout, file_url() gives the file's address on the site, and the URL to absolute adjuster of the policy completes it:
<img src="{{ file_url('public://logo.png') }}" height="64" alt="{{ 'My site'|t }}">

file_url(directory ~ '/images/email-logo.png') points to a file of the theme that renders the email instead. - In Mailer Plus's wrapper, URL to absolute does not help: it runs before the email is wrapped, so an address written there stays relative and the image does not show. Give the wrapper a full address from the theme:

// THEME.theme
function THEME_preprocess_email_wrap(array &$variables) {
  $logo = theme_get_setting('logo.path', 'THEME');
  if ($logo) {
    $variables['logo_url'] = \Drupal::service('file_url_generator')->generateAbsoluteString($logo);
  }
}
{% if logo_url %}<img src="{{ logo_url }}" height="64" alt="{{ 'My site'|t }}">{% endif %}

A full address uses the address of the request that sends the email. An email sent from cron has no request to take it from: give the cron command the site's URL (drush --uri=https://www.example.com cron), or the image points to http://default.

With another HTML mailer

With Mime Mail, local images are embedded (cid:) unless its Link images only setting is on, in which case they are linked by their full address. With Symfony Mailer Lite, nothing rewrites addresses: write the full address in the layout, or compute it in a preprocess function as above. Embedding the image as data: text in the src is no way out: Gmail and Outlook do not show such images.

With Mailer Plus: the wrapper and the stylesheet

Mailer Plus adds two things of its own that combine with the layout above:

  • Wrap and convert (on in the _ policy) wraps the body of every email the site sends, Easy Email or not, in its template email-wrap.html.twig, and writes the plain-text version from the HTML. A theme overrides that file like any other. Put what every email of the site shares, the logo (see above), the header, the footer, in email-wrap.html.twig, and keep easy-email-body-html.html.twig for what is Easy Email's or Orchestra's: the orchestra and node_values zones. Do not put the header in both, or it shows twice.
  • Inline CSS copies the theme's email library into style attributes, since mail clients ignore most stylesheets. Declare it in the theme's THEME.libraries.yml:
email:
  css:
    theme:
      css/email.css: {}

Then the layouts can use classes (class="header", class="button") and style them in css/email.css, instead of style attributes on every tag.

Sharing parts between layouts

The layout files are ordinary theme templates, rendered by Drupal's Twig, so everything Twig offers to reuse markup works in them, and the Orchestra variables (orchestra, node_values, body) reach every template they pull in.

Extend a layout and replace blocks. A layout made of {% block %} zones is a base the others extend. A per-template file starts with {% extends 'easy-email-body-html.html.twig' %} and redefines only its blocks; {{ parent() }} inside a block keeps the base's content. A file can only extend another, it cannot add markup outside its blocks.

Include a partial. Put a piece used by several layouts in a file of its own, anywhere under the theme's templates/ directory, and include it through the theme's namespace:

{# THEME/templates/email/signature.html.twig #}
<p class="signature">{{ 'The @site team'|t({'@site': 'My site'}) }}</p>
{% block footer %}
  <tr><td class="footer">{% include '@THEME/email/signature.html.twig' %}</td></tr>
{% endblock %}

@THEME is the theme's machine name. The partial sees the layout's variables; to hand it only some, write {% include '@THEME/email/signature.html.twig' with { name: orchestra.label } only %}.

Embed a partial with blocks of its own. {% embed %} includes a file and replaces blocks inside it, for a component used with different contents, such as a call-to-action box:

{# THEME/templates/email/action-box.html.twig #}
<div class="action-box">
  {% block action_text %}{% endblock %}
  <a class="button" href="{{ url }}">{{ label }}</a>
</div>
{% if node_values.link %}
  {% embed '@THEME/email/action-box.html.twig' with { url: node_values.link, label: node_values.link_label ?: 'Open'|t } %}
    {% block action_text %}<p>{{ node_values.intro }}</p>{% endblock %}
  {% endembed %}
{% endif %}

Branch inside one layout. When the difference is small, a condition in the site layout is simpler than a file: {% if easy_email.bundle == 'orchestra_task_reminder' %} tests the template the email uses, {% if orchestra %} whether it is an Orchestra email at all.

All of it is read from the theme that renders the email: keep the base layout, the files that extend it and the partials in the same theme (see step 2).

Complex cases

Several brands on one site

Give each tenant its own templates and layouts. The template lookup tries orchestra_<tenant>_<type> first, so orchestra_city_hall_generic serves the city_hall tenant and every other tenant keeps orchestra_generic. Each template gets its own layout file, easy-email-body-html--orchestra-city-hall-generic.html.twig, which can {% include %} a shared part of the site layout and change only the header and colors.

Variants of one step

A process variable orchestra_notification_template_suffix switches template for the rest of the instance: set it to urgent and every notification first tries orchestra_<type>_urgent. A step that needs different words for the same template is better served by a value that is only a token (see above), or by two Notify nodes on two branches.

A shared child process

When a child process is shared by several parents, its nodes are shared, and so are their values: every parent sends the child's words. Write them so they read well for every parent, with tokens for what differs ([orchestra:label], a variable the parent hands the child). A parent that needs other words can set the orchestra_notification_template_suffix variable: an embedded child runs in the parent's instance and sees it as it is, a child run as an instance of its own needs it in its input mapping. The child's notifications then switch to the templates of that suffix.

Optional blocks

A layout block that depends on data reads a value that is only a token (see A value that is only a token): the block shows when the data is there and disappears when it is not, without a template per case.

Escaping keeps a value from breaking the markup, not from pointing anywhere: a value placed in an href links wherever its text says. Build a link from an address the site makes, [orchestra-notification:link] or a URL your own step stores, never from a variable a link holder filled in.

Troubleshooting

Symptom Look at
No email at all the Orchestra log: "no Easy Email template for the … notification" names the ids tried. Check the node's type and the template ids.
The layout is not applied the theme that renders the email (Mailer Plus's policy Theme, or Mail System's Theme to render the emails), the file name (hyphens, not underscores), and clear the cache.
The header shows twice with Mailer Plus, a header in both email-wrap.html.twig and the Easy Email layout.
The logo does not show embedded: the policy must have Inline images, and the src must be the file's public:// address; linked: an address written in email-wrap.html.twig is not made absolute, and from cron the site's URL must be given to the cron command. See Logo and images.
A value is empty the name on the node and in the template must match exactly; the email must be about a step (an email sent for the whole instance has no node to read values from); a token in the value may resolve to nothing.
Tags show as text a value is plain text: put the markup in the template or the layout.
[user:*] is empty the email has several readers (To and Cc), or the recipient is an address with no account.
The wrong language Easy Email sends in the site's default language.