Example Template & Tutorial
Introduction
This tutorial is intended to help you understand the template syntax and provide an advanced example of a complete template, which uses many of the available web variables.
As the subject of demonstration, we will walk through the template used for the default email template provided with the device.
At the end of the document, you will find the complete text of the finished template.
For reference, it is handy to keep the template and web variables docs pages open in separate tabs.
The email template
The purpose of this default template is to generate the content of the emails , the device can send as a notification.
Requirements
To avoid having to deal with multiple specialized templates, the idea was to implement a single email template, that
- supports both English and German language, depending on the language setting of the device,
- handle for all relevant types of notifications, including
- digital I/O state changes,
- sensor values leaving and reentering the normal range,
- sensor failures and watchdog test failures.
- generate a subject line that is informative and concise, so that the user can quickly identify the type of notification and the affected device,
- and supports sending the test mail, which is used to verify the email configuration
- uses valid
RFC 5322message andMIMEheaders, so that email clients can display correctly,
General concepts
Lang web vars
What you will see a lot throughout the templates are snippets like:
{%if lang%}German{%else%}English{%endif%}
This is a practical application of the if command, where the expression (the argument to the if directive) is the lang web variable, which returns 1 for German and 0 for English, dependent on the device's language setting.
So the line outputs "German" if the device is set to German and the variable returns 1, otherwise it outputs "English".
Additionally to ifand else, the template syntax also optionally supports elif (else if) and requires endif (end if) keywords.
Templates use the if commands extensively, notably also in nested form, where an if-else construct is the argument expression of a parent if-else construct.
The arg function
The arg function is used to access the arguments passed to the template.
In our case the device automatically passes the arguments input, output, sensor, watchdog, failure_type and value to the template. Depending on the type of notification that triggered the email,
input,output,sensor,watchdogwill contain the index of the input/output/sensor/watchdog that triggered the notification, or an empty string""if the notification was not triggered by that type of peripheral (which we will use shortly), whilevalueprovides the new state of the input/output, or the new value of the sensor, or the result of the watchdog test;failure_typeis only relevant for sensor notifications and specifies the type of sensor failure that occured, if any.
Email headers
To generate a valid email, email clients expect the mail content to include the required headers.
In our case we will use Subject, MIME-Version and Content-Type.
Typical email structure
From: Alice <alice@example.com>
To: Bob <bob@example.com>
Subject: Hello
Hello Bob!
A complete email also includes other headers, such as From and To, for the sender and recipient, but these are automatically prepended by the device and do not need to be included in this template.
Note the empty line between the Subject header and the actual message body, though. This is required by the email protocol and must be included in the template, because it separates the message headers from the message body.
Line endings
The device automatically converts all line endings in the template in the carriage return + line feed, or CR/LF format. This is handy in most situations, as the email protocol requires it.
Walkthrough
The template starts with the Subject header.
Note, that the text Subject: is immeadiately followed by a {#-#}.
This is a 'trick', to avoid the template engine from inserting a line break after the Subject: header, because we want to append the lines that will follow to Subject: . If we would not use the {#-#} trick, a line ending would be inserted by the template engine, and the email client would interpret the next line as not part of the subject header any more.
The {#-#} syntax is in fact an empty comment. Comments are generally not output by the template engine, but the - character tells the template engine to suppress the line break after outputs, commands and comments.
The following lines, up until MIME-Version: 1.0 look wild, but are just a series of nested if-else statements, that check which type of notification triggered the email, and then output the appropriate subject line.
Depending on the peripheral type, it uses relevant web variables to construct the subject.
Note, the extensive usage of {#-#} and {%endif-%} to ensure that everything ends up in the subject line, without any line breaks.
What follows is the boundary between the headers and the message body:
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
{# #}
The first two lines are required MIME headers for a valid email.
The third line is an (empty) comment, that in this case triggers an additional line break, because the email specification requires an empty line between the headers and the message body.
Without the comment, there would be one line break after charset=UTF-8, but an empty line requires a second one.
Everything that comes after this line is part of the message body!
The body starts with a greeting line, that uses the lang and some other web variables to output the vendor, device type, and hostname.
Then follows
{%set details="=== Details ==="%}
This is the set command, which defines a temporary variable details that can be used later in the template. In this case, it is used to output a line === Details ===, which will be needed multiple times, so it is worth defining it once and referring to it.
This is proceeded by the block
{%if arg(input) != ""%}{%if lang%}Der digitale{%else%}The digital{%endif%} Input {{loop.index}} {%if inputtab_name != ""%}"{{inputtab_name}}"{%endif%} {%if lang%}wurde kürzlich{%else%}has been recently{%endif%} {%if arg(value)>0%}{%if lang%}aktiviert{%else%}activated{%endif%}{%else%}{%if lang%}deaktiviert{%else%}deactivated{%endif%}{%endif%}.
{# #}
{{details}}
Input Index: {{loop.index}}
Input Name: {{inputtab_name}}
{%if lang%}Gruppe{%else%}Group{%endif%}: {{inputtab_group}}
{%if lang%}Zustandsänderung{%else%}State transition{%endif%}: {%if arg(value)>0%}{%if lang%}Aus-An{%else%}Off-On{%endif%} 🔴-🟢{%else%}{%if lang%}An-Aus{%else%}On-Off{%endif%} 🟢-🔴{%endif%}
{%if lang%}Aktueller Zustand{%else%}Current State{%endif%}: {%if arg(value)>0%}{%if lang%}An (Aktiv){%else%}On (Active){%endif%} 🟢{%else%}{%if lang%}Aus (Inaktiv){%else%}Off (Inactive){%endif%} 🔴{%endif%}
{%elif arg(output) != ""%}{%if lang%}Der digitale{%else%}The digital{%endif%} Output {{loop.index}} {%if outputtab_name != ""%}"{{outputtab_name}}"{%endif%} {%if lang%}wurde kürzlich{%else%}has been recently{%endif%} {%if arg(value)>0%}{%if lang%}aktiviert{%else%}activated{%endif%}{%else%}{%if lang%}deaktiviert{%else%}deactivated{%endif%}{%endif%}.
The first line checks the input argument, and if it is not empty (which would mean that this is indeed an email for an input notification), it emits a message for an input notification. Of course, the argument can also be passed as "", which would make the control flow jump to the next elif branch, which checks for an output notification, and so on.
Separated by an empty line using the {# #} comment trick, it outputs the details temporary variable, we have defined above.
The next lines output the details of the input in a structured format, using the loop.index web variable to get the index of the input that triggered the notification, and other web variables to get the name, group, and state of the input.
After that,
{%elif arg(output) != ""%}...
does basically the same for
- output notifications, and the following blocks do the same for
- sensor and
- watchdog notifications,
while peripheral-specific details are provided, using the relevant web variables.
As you might have noticed, the body mainly consists of a big if-elif-else construct, that checks the type of peripheral, the notification belogs to, and outputs the relevant information.
Up until:
{%else%}{%if lang%}Die Test-Mail wurde erfolgreich abgesendet{%else%}The Test Mail has been successfully sent{%endif%}!{%endif%}
The last {%endif%} marks the end of the big notification-specific if-elif-else construct, while the else branch handles the case of a test mail, which is sent when the user clicks the "Test" button on the email configuration page.
What follows is the last part of the template, which outputs general device information, that is included in every email, regardless of the type of notification.
Most of this block might be familiar to you by now.
Notable is the System Time line, specifically the use of the strftime() template function {{strftime("%F %T", systimestamp, 1)}}. This function formats the systimestamp web variable, which is a Unix timestamp, into a human-readable date and time format, where %T represents the time in 12:34:56 24-hour format and %F represents the date in 2026-08-11 format. The last argument 1 specifies that the time should be displayed in the local timezone, rather than UTC.
Finally,
{%for netiftab-%}
{%if netiftab_up == 1%}http://{{netiftab_ip_addr}}, https://{{netiftab_ip_addr}} ({{netiftab_type_name}}){%endif%}{#-#}
{%endfor%}
is a for loop command, that iterates over all existing network interfaces and outputs the http and https URLs with the IP addresses, only of those interfaces that are currently up (active), along with their type (Ethernet, Wi-Fi, etc.).
The last line emits the name of the template that was used to generate the email, which is useful for debugging purposes.
This wraps up the walkthrough of the default email template.
If you would like to practice your new knowledge, you can try to customize the template to your liking, maybe by adding a custom greeting, or incorporating additional web variables, that might be interesting for your use case.
To customize this default template you will have to clone it. Please refer to the Files & Templates section of the Firmware & Files docs page for more information on managing default and custom templates.
Finished Template
This is the complete template code for the default email template.
Subject: {#-#}
{%if arg(input) != ""%}Input {{loop.index}} {%if inputtab_name != ""%}"{{inputtab_name}}"{%endif%} {%if arg(value)>0%}{%if lang%}aktiviert{%else%}activated{%endif%} 🔴-🟢{%else%}{%if lang%}deaktiviert{%else%}deactivated{%endif%} 🟢-🔴{%endif-%}
{%elif arg(output) != ""%}Output {{loop.index}} {%if outputtab_name != ""%}"{{outputtab_name}}"{%endif%} {%if arg(value)>0%}{%if lang%}aktiviert{%else%}activated{%endif%} 🔴-🟢{%else%}{%if lang%}deaktiviert{%else%}deactivated{%endif%} 🟢-🔴{%endif-%}
{%elif arg(sensor) != ""%}Sensor {{loop.index}} {%if sensortab_name != ""%}"{{sensortab_name}}"{%endif%} {%if arg(failure_type)&224%}{%if lang%}Fehler{%else%}Failure{%endif%} ⚠️❌{%elif arg(failure_type)&2%}{%if lang%}Wert zu hoch{%else%}Value too high{%endif%} ⚠️⬆️{%elif arg(failure_type)&1%}{%if lang%}Wert zu niedrig{%else%}Value too low{%endif%} ⚠️⬇️{%elif arg(failure_type)==0%}{%if lang%}Wert wieder normal{%else%}Value back to normal{%endif%} ✅{%else%}{%if lang%}Unbekanntes Problem{%else%}Unknown problem{%endif%} ⚠️❌ {%endif-%} {%if sensortab_value%}({{sensortab_value}} {{sensortab_unit}}){%endif%}
{%elif arg(watchdog) != ""%}Watchdog {{loop.index}} {%if sensortab_name != ""%}"{{sensortab_name}}"{%endif%} {%if arg(value)>0%}{%if lang%}ausgelöst{%else%}triggered{%endif%} ⚠️❌{%else%}{%if lang%}wieder normal{%else%}back to normal{%endif%} ✅{%endif-%}
{%elif arg(sysevent)=="start"%}System {%if lang%}(neu)gestartet{%else%}(re)started{%endif%} ⏻{#-#}
{%else%}Test{%if lang == 1%}-{%else%} {%endif%}Mail{%endif%}{#-#}
@ {{hostname}} {%if syslocation != ""%}({{syslocation}}){%endif%}
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
{# #}
{%if lang%}Hallo von Ihrem{%else%}Hello from your{%endif%} {{fw_manufacturer}} {{dev_type}} ({{hostname}}),
{%set details="=== Details ==="%}
{%if arg(input) != ""%}{%if lang%}Der digitale{%else%}The digital{%endif%} Input {{loop.index}} {%if inputtab_name != ""%}"{{inputtab_name}}"{%endif%} {%if lang%}wurde kürzlich{%else%}has been recently{%endif%} {%if arg(value)>0%}{%if lang%}aktiviert{%else%}activated{%endif%}{%else%}{%if lang%}deaktiviert{%else%}deactivated{%endif%}{%endif%}.
{# #}
{{details}}
Input Index: {{loop.index}}
Input Name: {{inputtab_name}}
{%if lang%}Gruppe{%else%}Group{%endif%}: {{inputtab_group}}
{%if lang%}Zustandsänderung{%else%}State transition{%endif%}: {%if arg(value)>0%}{%if lang%}Aus-An{%else%}Off-On{%endif%} 🔴-🟢{%else%}{%if lang%}An-Aus{%else%}On-Off{%endif%} 🟢-🔴{%endif%}
{%if lang%}Aktueller Zustand{%else%}Current State{%endif%}: {%if arg(value)>0%}{%if lang%}An (Aktiv){%else%}On (Active){%endif%} 🟢{%else%}{%if lang%}Aus (Inaktiv){%else%}Off (Inactive){%endif%} 🔴{%endif%}
{%elif arg(output) != ""%}{%if lang%}Der digitale{%else%}The digital{%endif%} Output {{loop.index}} {%if outputtab_name != ""%}"{{outputtab_name}}"{%endif%} {%if lang%}wurde kürzlich{%else%}has been recently{%endif%} {%if arg(value)>0%}{%if lang%}aktiviert{%else%}activated{%endif%}{%else%}{%if lang%}deaktiviert{%else%}deactivated{%endif%}{%endif%}.
{# #}
{{details}}
Output Index: {{loop.index}}
Output Name: {{outputtab_name}}
{%if lang%}Gruppe{%else%}Group{%endif%}: {{outputtab_group}}
{%if lang%}Zustandsänderung{%else%}State transition{%endif%}: {%if arg(value)>0%}{%if lang%}Aus-An{%else%}Off-On{%endif%} 🔴-🟢{%else%}{%if lang%}An-Aus{%else%}On-Off{%endif%} 🟢-🔴{%endif%}
{%if lang%}Aktueller Zustand{%else%}Current State{%endif%}: {%if arg(value)>0%}{%if lang%}An (Aktiv){%else%}On (Active){%endif%} 🟢{%else%}{%if lang%}Aus (Inaktiv){%else%}Off (Inactive){%endif%} 🔴{%endif%}
{%elif arg(sensor) != ""%}{%if lang%}Der{%else%}The{%endif%} Sensor {{loop.index}} {%if sensortab_name != ""%}"{{sensortab_name}}"{%endif%} {%if lang%}hat kürzlich{%else%}has recently{%endif%} {%if arg(failure_type)&224%}{%if lang%}einen Fehler gemeldet{%else%}reported a failure{%endif%}{%elif arg(failure_type)&2%}{%if lang%}einen Wert oberhalb des Normbereichs gemeldet{%else%}reported a value above the normal range{%endif%}{%elif arg(failure_type)&1%}{%if lang%}einen Wert unterhalb des Normbereichs gemeldet{%else%}reported a value below the normal range{%endif%}{%elif arg(failure_type)==0%}{%if lang%}das Zurückkehren seines Wertes in den Normbereich gemeldet{%else%}reported the return of its value to the normal range{%endif%}.{%else%}{%if lang%} ein unbekanntes Problem gemeldet{%else%}reported an unknown problem{%endif%}{%endif%}.
{# #}
{{details}}
Sensor Index: {{loop.index}}
Sensor Name: {{sensortab_name}}
{%if lang%}Gruppe{%else%}Group{%endif%}: {{sensortab_group}}
{%if lang%}Zustand{%else%}Condition{%endif%}: {%if arg(failure_type)&224%}{%if lang%}Fehler{%else%}Failure{%endif%} ⚠️❌{%elif arg(failure_type)&2%}{%if lang%}Wert zu hoch{%else%}Value too high{%endif%} ⚠️⬆️{%elif arg(failure_type)&1%}{%if lang%}Wert zu niedrig{%else%}Value too low{%endif%} ⚠️⬇️{%elif arg(failure_type)==0%}{%if lang%}Wert wieder normal{%else%}Value back to normal{%endif%} ✅{%else%}{%if lang%}Unbekanntes Problem{%else%}Unknown problem{%endif%} ⚠️❌ {%endif%}
{%if lang%}Aktueller Wert{%else%}Current Value{%endif%}: {%if sensortab_value%}{{sensortab_value}}{%else%}--{%endif%}
{%if lang%}Einheit{%else%}Unit{%endif%}: {{sensortab_unit}}
{%if lang%}Normbereich{%else%}Normal Range{%endif%}: Minimum {{sensortab_lim_lo}} {{sensortab_unit}} - Maximum {{sensortab_lim_hi}} {{sensortab_unit}} - Hysteres{%if lang%}e{%else%}is{%endif%} {{sensortab_hyst}} {{sensortab_unit}}
{%elif arg(watchdog) != ""%}{%if lang%}Der{%else%}The{%endif%} Watchdog "{{watchtab_name_def}}" {%if lang%}hat kürzlich{%else%}has recently{%endif%} {%if arg(value)>0%}{%if lang%}eine fehlgeschlagene Prüfung gemeldet{%else%}reported a failed check{%endif%}{%else%}{%if lang%}das Zurückkehren in den Normalzustand gemeldet{%else%}reported the return to the default state{%endif%}{%endif%}.
{# #}
{{details}}
Watchdog Index: {{loop.index}}
Watchdog Name: {{watchtab_name}}
{%if lang%}Gruppe{%else%}Group{%endif%}: {{sensortab_group}}
Typ: {%if watchtab_type==1%}Ping Out{%elif watchtab_type==2%}Ping In{%elif watchtab_type==3%}HTTP Out{%elif watchtab_type==4%}HTTP In{%endif%}
{%if lang%}Ziel{%else%}Target{%endif%}: {{watchtab_target}}
{%if lang%}Nächste Prüfung{%else%}Next check{%endif%}: {{watchtab_time_left}}s
{%if lang%}Zustand{%else%}Condition{%endif%}: {%if arg(value)>0%}{%if lang%}Fehlgeschlagene Prüfung{%else%}Failed check{%endif%} ⚠️❌{%else%}{%if lang%}Wieder im Normalzustand{%else%}Back to normal state{%endif%} ✅{%endif%}
{%else%}{%if lang%}Die Test-Mail wurde erfolgreich abgesendet{%else%}The Test Mail has been successfully sent{%endif%}!{%endif%}
=== {%if lang%}Geräteinfo{%else%}System info{%endif%} ===
Hostname: {{hostname}}
{%if syslocation != ""%}{%if lang%}Standort{%else%}Location{%endif%}: {{syslocation}}{%endif%}
{%if lang%}Systemzeit{%else%}System time{%endif%}: {{strftime("%F %T", systimestamp, 1)}}
{%if lang%}Weboberfläche{%else%}Web interface{%endif%} :
{%for netiftab-%}
{%if netiftab_up == 1%}http://{{netiftab_ip_addr}}, https://{{netiftab_ip_addr}} ({{netiftab_type_name}}){%endif%}{#-#}
{%endfor%}
{%if lang%}Benutztes Template{%else%}Used template{%endif%}: "{{mailtab_tpl}}"