Notification Templates

Templates control the format of notifications. Separate templates exist for each delivery channel (Email, Slack, Teams) and for each delivery type (single and batch). The default templates for each channel integrate Guardrails Quick Actions and serve as a great jumping off point for your own customization.

Slack Notification Example

Email Notification Example

Batch Email Notification Example

The default templates can be overridden by setting the following policies:

Turbot > Notifications > Email > Action Template > Subject
Turbot > Notifications > Email > Action Template > Body
Turbot > Notifications > Email > Action Template > Batch Subject
Turbot > Notifications > Email > Action Template > Batch Body
Turbot > Notifications > Email > Control Template > Subject
Turbot > Notifications > Email > Control Template > Body
Turbot > Notifications > Email > Control Template > Batch Subject
Turbot > Notifications > Email > Control Template > Batch Body
Turbot > Notifications > Slack > Action Template > Body
Turbot > Notifications > Slack > Action Template > Batch Body
Turbot > Notifications > Slack > Control Template > Body
Turbot > Notifications > Slack > Control Template > Batch Body
Turbot > Notifications > Microsoft Teams > Action Template > Body
Turbot > Notifications > Microsoft Teams > Action Template > Batch Body
Turbot > Notifications > Microsoft Teams > Control Template > Body
Turbot > Notifications > Microsoft Teams > Control Template > Batch Body

Template Definition

Template syntax should be familiar for anyone that has written a Guardrails calculated policy. Each template consists of a GraphQL query and a Nunjucks template definition. Here is a simple example for the email subject template:

{% input %}
query notificationDetails($filter: [String!], $resourceId: ID!) {
workspaceUrl: policyValue(
uri: "tmod:@turbot/turbot#/policy/types/workspaceUrl"
resourceId: $resourceId
) {
value
}
notifications(filter: $filter) {
items {
notificationType
message
resource {
turbot {
id
}
trunk {
title
}
}
turbot {
createTimestamp
}
}
}
}
{% endinput %}
{%- if domain %}
{% set workspace = domain.split('/')[2].split('.')[0] %}
[{{ workspace }}] {{ $.notifications.items.length }} actions by Turbot
{%- else %}
{{ $.notifications.items.length }} actions by Turbot
{%- endif %}

The GraphQL query is contained between the {% input %} and {% endinput %} tags. GraphQL is not case sensitive.

All content after {% endinput %} is the template. Keep in mind that Nunjucks flow logic (e.g. {% if domain %}) can generate newlines or whitespace. For template sections where extra newlines would be problematic (e.g. the email subject) make sure that you use whitespace control features of Nunjucks (i.e. {%- if domain -%}) does not generate newlines or excess whitespace, or keep all logic on a single line.

Example Email Body Template

{% input %}
query controlGet($id: ID!, $resourceId: ID!, $filter: [String!]) {
workspaceUrl: policyValue(uri: "tmod:@turbot/turbot#/policy/types/workspaceUrl", resourceId:$resourceId){
value
}
oldControl: control(id: $id) {
actor {
identity {
picture
turbot {
title
id
}
}
}
state
reason
details
type {
trunk {
title
}
}
turbot {
createTimestamp
updateTimestamp
id
}
resource {
turbot {
id
}
trunk {
title
}
type {
title
}
}
}
quickActions: controlTypes(filter: $filter) {
items {
actionTypes{
items{
title
icon
description
uri
confirmationType
defaultActionPermissionLevels
turbot {
id
}
}
}
}
}
}
{% endinput %}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Email Content</title>
</head>
<body>
<p style="color: #999999; font-size: 10px; font-family: Arial, Helvetica, sans-serif; margin-bottom: 0;">RESOURCE</p>
<p style="font-size: small; font-family: Arial, Helvetica, sans-serif; margin-top: 0;">
<a style="color: #0000FF; text-decoration: none;" href="{{ domain }}/resources/{{$.oldControl.resource.turbot.id }}">{{ $.oldControl.resource.trunk.title | replace('>', '&gt;')}}</a>
</p>
<p style="color: #999999; font-size: 10px; font-family: Arial, Helvetica, sans-serif; margin-bottom: 0; margin-top: 20px;">CONTROL</p>
<p style="font-size: small; font-family: Arial, Helvetica, sans-serif; margin-top: 0;">
<a style="color: #0000FF; text-decoration: none;" href="{{ domain }}/controls/{{$.oldControl.turbot.id }}">{{ $.oldControl.type.trunk.title | replace('>', '&gt;')}}</a>
</p>
<p style="color: #999999; font-size: 10px; font-family: Arial, Helvetica, sans-serif; margin-bottom: 0; margin-top: 20px;">STATUS</p>
<p style="font-size: small; font-family: Arial, Helvetica, sans-serif; margin-top: 0;">{% if $.oldControl.state == 'ok' %}OK{% elif $.oldControl.state == 'tbd'%}TBD{% else %}{{ $.oldControl.state | capitalize }}{% endif %} → <span style="font-weight: bold; {% if newControl.state == 'alarm' or newControl.state == 'error' %}color: #CC0000;{% elif newControl.state == 'ok' %}color: #36a64f;{% else %}color: #d3d3d3;{% endif %}">{% if newControl.state == 'ok' %}OK{% elif newControl.state == 'tbd'%}TBD{% else %}{{ newControl.state | capitalize }}{% endif %}</span></p>
<p style="color: #999999; font-size: 10px; font-family: Arial, Helvetica, sans-serif; margin-bottom: 0; margin-top: 20px;">REASON</p>
<p style="font-size: small; font-family: Arial, Helvetica, sans-serif; margin-top: 0;">{{ newControl.reason }}</p>
{%- if $.quickActions.items and $.quickActions.items[0].actionTypes and $.quickActions.items[0].actionTypes.items.length > 0 %}
<p style="color: #999999; font-size: 10px; font-family: Arial, Helvetica, sans-serif; margin-bottom: 0; margin-top: 20px;">QUICK ACTIONS</p>
<p style="font-size: small; font-family: Arial, Helvetica, sans-serif; margin-top: 0;">
{% for item in $.quickActions.items[0].actionTypes.items -%}
&rarr; <a style="color: #0000FF; text-decoration: none;" href="{{ domain }}/resources/{{ $.oldControl.resource.turbot.id }}?executeActionType={{ item.uri | replace('#', '%23')}}">{{ item.title }}</a><br>
{% endfor -%}
</p>
{% endif -%}
<p style="color: #999999; font-size: 10px; font-family: Arial, Helvetica, sans-serif; margin-bottom: 0; margin-top: 20px;">TIMESTAMP</p>
<p style="font-size: small; font-family: Arial, Helvetica, sans-serif; margin-top: 0;">{{ newControl.turbot.updateTimestamp }} UTC <a style="color: #0000FF; text-decoration: none;" href="{{ domain }}/processes/{{ process.id }}/logs?filter=logLevel%3A>%3Dinfo">[Log]</a></p>
<div style="font-size: 11px; color: #848884; margin-top: 20px;">
You have been subscribed to these email alerts by the system administrator of <a style="color: #0000FF; text-decoration: none;" href="{{ domain }}">{{ domain }}</a>. Please contact them directly for changes.
</div>
</body>
</html>

Example Slack Template

{% input %}
query controlGet($id: ID!, $resourceId: ID!, $filter: [String!]) {
workspaceUrl: policyValue(
uri: "tmod:@turbot/turbot#/policy/types/workspaceUrl"
resourceId: $resourceId
) {
value
}
oldControl: control(id: $id) {
actor {
identity {
picture
turbot {
title
id
}
}
}
state
reason
details
type {
trunk {
title
}
}
turbot {
createTimestamp
updateTimestamp
id
}
resource {
turbot {
id
title
}
trunk {
title
}
type {
title
}
}
}
quickActions: controlTypes(filter: $filter) {
items {
actionTypes{
items{
title
icon
description
uri
confirmationType
defaultActionPermissionLevels
turbot {
id
}
}
}
}
}
}
{% endinput %}
{
"attachments": [
{
"color": "{% if newControl.state == 'alarm' or newControl.state == 'error' %}#cb1119{% elif newControl.state == 'ok' %}#36a64f{% else %}#d3d3d3{% endif %}",
"author_name": "{{ $.oldControl.resource.trunk.title }}",
"author_link": "{{ domain }}/resources/{{ $.oldControl.resources.turbot.id }}",
"title": "{{ $.oldControl.type.trunk.title }}",
"title_link": "{{ domain }}/controls/{{ $.oldControl.turbot.id }}",
"mrkdwn_in": [
"text",
"footer"
],
"text": "{% if $.oldControl.state == 'ok' %}OK{% elif $.oldControl.state == 'tbd'%}TBD{% else %}{{ $.oldControl.state | capitalize }}{% endif %} → *{% if newControl.state == 'ok' %}OK{% elif newControl.state == 'tbd'%}TBD{% else %}{{ newControl.state | capitalize }}{% endif %}*\n_{{ newControl.reason }}_\n{%- if domain %}{%- for item in $.quickActions.items[0].actionTypes.items %}⭢ <{{domain}}/resources/{{$.oldControl.resource.turbot.id}}?executeActionType={{ item.uri | replace('#', '%23')}}|{{ item.title }}>\n{%- endfor %}{%- endif %}"
}
]
}

Example MS Teams Template

{% input %}
query notificationDetails($filter: [String!], $resourceId: ID!) {
workspaceUrl: policyValue(
uri: "tmod:@turbot/turbot#/policy/types/workspaceUrl"
resourceId: $resourceId
) {
value
}
turbotId: resource(id: "tmod:@turbot/turbot#/") {
turbot {
id
}
}
accountableResource: resource(id: $resourceId) {
turbot {
id
}
trunk {
title
}
}
notifications(filter: $filter) {
items {
notificationType
message
resource {
turbot {
id
}
trunk {
title
}
}
}
}
}
{% endinput %}
{
"summary": "[{{ $.accountableResource.trunk.title | replace('>', '&gt;')}}]({{ domain }}/resources/{{$.accountableResource.turbot.id}})",
"sections": [
{%- for i in range(0, 3) -%}
{%- if $.notifications.items[i] %}
{
"activityTitle": "<a href='{{ domain }}/resources/{{$.notifications.items[i].resource.turbot.id }}' style='font-weight:bold; color:black'>{{ $.notifications.items[i].resource.trunk.title | replace('>', '&gt;')}}</a>",
"activitySubtitle": "<span style='font-size: 14px'>_{{ $.notifications.items[i].message }}_</span>"
}
{%- if $.notifications.items.length <= 3 and $.notifications.items.length - 1 > i %},{% endif %}
{%- if $.notifications.items.length > 3 %},{% endif %}
{%- endif %}
{%- endfor %}
{%- if $.notifications.items.length > 3 %}
{
"activityTitle": "[View all {{ $.notifications.items.length }} notifications →]({{ domain }}/resources/{{$.accountableResource.turbot.id}}/activity?search=id:{{notificationIds}}&level=self%2Cdescendant)",
"markdown": true }
{%- endif %}
]
}