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 > SubjectTurbot > Notifications > Email > Action Template > BodyTurbot > Notifications > Email > Action Template > Batch SubjectTurbot > Notifications > Email > Action Template > Batch Body
Turbot > Notifications > Email > Control Template > SubjectTurbot > Notifications > Email > Control Template > BodyTurbot > Notifications > Email > Control Template > Batch SubjectTurbot > Notifications > Email > Control Template > Batch Body
Turbot > Notifications > Slack > Action Template > BodyTurbot > Notifications > Slack > Action Template > Batch Body
Turbot > Notifications > Slack > Control Template > BodyTurbot > Notifications > Slack > Control Template > Batch Body
Turbot > Notifications > Microsoft Teams > Action Template > BodyTurbot > Notifications > Microsoft Teams > Action Template > Batch Body
Turbot > Notifications > Microsoft Teams > Control Template > BodyTurbot > 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('>', '>')}}</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('>', '>')}}</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 -%} → <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('>', '>')}}]({{ 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('>', '>')}}</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 %} ] }