Using Sendinblue’s New Template Language to create email templates and campaigns [NEW]

Our template language includes predefined placeholders and logic that may be used to insert dynamic content into your email, and modify how this content is displayed to the recipient.

The template language provides a standardized, common structure for creating efficient templates and powerful design options.

Good to know: The New Template Language replaces an older version of the Sendinblue template language which used different syntax and was less robust. In this guide, we’ll simply refer to the New Template Language as “the template language”.

Before getting started

When sending a single email to many recipients at once, create your email as a Campaign.

When creating a template to send as a transactional email or from an automation workflow, build and save it as a template.

Emails using the template language can be triggered to send via their respective Sendinblue API v3 calls (for campaigns) or (for transactional emails) while providing the campaign ID or template ID. They may also be sent via SMTP relay while providing the header X-SIB-API with the correct parameters.

Good to know: If you have not used our Transactional (SMTP) platform before, you may need to request its activation. To check, log in to your account and visit this page. If you do not see a prompt asking you to activate your SMTP account and your quota is higher than 0, you’re ready to start sending.


The template language makes use of variables and filters which get replaced with specific values when the email is triggered to be sent, as well as tags which add logic to control how and if the content is displayed.

There are two kinds of delimiters available:

{{ ... }} - “prints” or outputs the result of a variable or filter into the email
{% ... %} - executes the logic of a tag, such as a for-loop.

Good to know: The template language may be applied to modify several aspects of your email including the subject line, the preheader, within HTML <body> tags, and the recipient “to” field. HTML is automatically escaped by default inside templates, but this behavior may be changed using the autoescape tag described in detail below.



There are three types of variables available and each is described below.

1. Predefined variables  

These are available for all Sendinblue users:

{{ mirror }}

inserts a link to view your email as a web page

This variable is not mandatory.

Typically placed at the top of an email and formatted as a link like this:

<a href="{{ mirror }}">Click here to view this message in your browser.</a>

{{ unsubscribe }}

inserts a link to unsubscribe from your emails

Any non-transactional emails sent via Sendinblue should include an unsubscribe link.

Typically placed in the footer and formatted as a link like this:

<a href="{{ unsubscribe }}">Click here to unsubscribe.</a>


{{ update_profile }}

inserts a link to update profile form pre-filled with your contact's information

You can choose to include an update profile form in your campaign settings and collect your subscribers' preferences.

Typically placed in the footer and formatted as a link like this:

<a href="{{ update_profile }}">Update your preferences</a>


{{ time_now }}

displays the current timestamp in your timezone

If you have not set the timezone in your account, then the variable will return the current timestamp in UTC.

Place it anywhere in your email in the format {{time_now}} 


2. Contact attributes   

This variable type contains two elements and is structured as:

{{ contact.attributeNAME }}

Attribute = your Sendinblue contact attribute name as it appears on your Contacts page in Sendinblue. Attributes are always in CAPS and never contain spaces.

e.g., to insert a contact’s phone number where the attribute is named PHONE, use: {{ contact.PHONE }}

If the contact attribute name contains a dash -, such as CELL-PHONE, format your variable like this instead: {{ contact|key:"ATTRIBUTE" }}.  

e.g. {{ contact|key:"CELL-PHONE" }}

3. Transactional parameters

Good to know: When using the "Send a test" feature to send a test version of your email template, transactional parameters will not be replaced with recipient-specific content in the test email. Instead, you will see the transactional parameters exactly as they are coded in your template. 

Transactional parameter variables also contain two elements and are structured as:

{{ params.parameterNAME }}

parameterNAME = your transactional parameter name, formatted and cased exactly as you pass it via the API.

e.g., to insert a list of products where the parameter is named PRODUCTS, use: {{ params.PRODUCTS }}.

Now, let’s consider a common use case: generating a list of products purchased by a customer. Since the number of products purchased varies per customer, we can combine a for-loop with your PRODUCTS transactional parameter, like this example:  

{% for product in params.products %}

   <li><strong>{{ product.NAME }}</strong> - {{ product.PRICE }}</li>
{% endfor %}

This would output an unordered, bulleted list of products including the product name and price, like this and each recipient’s list may include a unique number of products:  

  • Product 1 - $10
  • Product 2 - $10
  • Product 3 - $10

Good to know: We recommend avoiding the use of hyphens or any special characters in your variable/parameter names. If you have no other choice, you can use {{ params|key:”parameter-NAME” }}. You may nest the filter multiple times, such as in {{ params|key:”my-product”|key:”my-product-name” }}.


When using an array, you may access all individual elements. Specify their ID like this: params.parameterARRAY.ID.parameterNAME. For example: {{ params.PRODUCTS.1.NAME }}.



The most common tags available to use in templates are iffor and autoescape. Guidelines and examples for each are included below. 


The powerful logic offered by if enables you to add or remove entire content blocks within your template (or modify content within a block). This effectively extends the usefulness of a single template for multiple scenarios.

The tag {% if %} enables you to test if an expression is true (or if an array is empty), and modify the email content based on the result. You can also check for false values, multiple conditions, and multiple branches.

Here are several common ways to use if:


Checks if a value is true or if an array is not empty

{% if contact.ACTIVE %}
Congrats! You achieved your goal this month.
{% endif %}


{% if params.tutors %}

{% for tutor in params.tutors %}
The following tutors are available to help you:
<li>{{ }}</li>
{% endfor %}
{% endif %}


check if an expression is true

{% if coupon == “WELCOME” %}
<p>Welcome to our list! Here’s your first coupon: WELCOME25</p>
{% endif %} 


{% if contact.DONOR == true %}
<b>Thank you for your support!</b>
{% endif %}

if, in

check if a value (substring) exists within a string or if a variable exists within array

{% if “" in "" %}
This appears since "" is a substring of ""
{% endif %}


{% if "Piano" in params.types %}
Be careful! There are heavy items in your order. You must be present to receive this delivery.
{% endif %}


check for values that are false

{% if not user.subscribed %}
   <p>You are not subscribed to our secret sale alerts. Sign up here.</p>
{% endif %}

and / or

evaluate multiple conditions

{% if temperature > 10 and temperature < 55 %}
   <p>Brr. It’s cold! Here’s a coupon for 20% off of any hot beverage, today only.</p>
{% endif %}


{% if contact.LANG == “FR” and contact.COUNTRY == “Canada” %}
{% endif %}


{% if contact.COUNTRY == “United States” or contact.COUNTRY == “Canada” %}
{% endif %}

elif, else

evaluate multiple branches

 Hello {% if contact.GENDER == "Male" %} Mr. {{ contact.LASTNAME }}, 
{% elif contact.GENDER == "Female" %} Ms. {{ contact.LASTNAME }}, 
{% else %} there, {% endif %} 


{% if event.paid %}
   Your spot is reserved. Thanks for submitting your payment.
{% elif event.registered %}
   Your spot is reserved but will be released without payment by January 2.
{% else %}
   There’s still time to sign up! Click here.
{% endif %}


Good to know: We do not recommend applying if tag comparisons to variables that contain “float” values as they may not produce accurate results. (Float values are those that contain a fractional number instead of an integer/whole number.) However, these may be applied if the value is passed as a string (contained in quotation marks “like this”).



As we will explore later in this guide, for is quite powerful when combined with your transactional parameters. It enables you to easily do complex work, such as inserting dynamic lists of products.

The tag {% for %} enables you to loop, or iterate, over each item in a sequence. This is particularly helpful when the number of items in the sequence is unknown when creating a template.

Here are several common ways to use for:

for, in

display a list

   {% for user in params.USERS %}
       <li>{{ user.username }}</li>
   {% endfor %}


render a replacement block if your sequence is empty

   {% for user in params.USERS %}
       <li>{{ user.username }}</li>
   {% else %}
       <li><em>no user found</em></li>
   {% endfor %}

Would display “no user found”.


reverses the print order of items in your sequence

{% for country in params.countries reversed %} {% end for %}

Would display a list of countries, but in reverse order compared to the original list.


Loop variables

Inside of a for-loop block you can also access these special variables:


Always set to an integer representing the number of times the loop has been entered. This is one-indexed, so the first time through the loop, forloop.Counter will be set to 1.

This may be easily used to number items in a loop:

{% for product in params.products %}
{{ forloop.Counter }}. {{ }} {% endfor %}

will output a list like this:

1. Product a
2. Product b
3. Product c, etc.


The current iteration of the loop. (0 indexed)


Always set to an integer representing the number of remaining items in the loop. The first time through the loop, forloop.Revcounter will be set to the total number of items in the sequence you’re traversing. The last time through the loop, forloop.revcounter will be set to 1.


The number of iterations from the end of the loop (0 indexed)


Boolean value set to True if this is the first time through the loop.

This is convenient for special-casing the first item in a list, such as adding the “upper” filter (described in the “Filters” section below) to only the first item in this sample list of products.

{% for product in params.products %}
{{ forloop.Counter }}. {% if forloop.First %} {{|upper }} {% else %} {{ }} {% endif %} - {{ product.price }}
{% endfor %}

Could output a list of products like this:

1. FIRST PRODUCT - $5.00
2. Second product - $2.00
3. Third product - $4.00


Boolean value set to True if this is the last time through the loop. A common use for this is to put separating characters (like , or |) in a list of items.


References the forloop object for the parent loop, in case of nested loops.

Here’s an example:

{% for country in countries %}
{% for city in country.city_list %}
<td>Country #{{ forloop.Parentloop.counter }}</td>
<td>City #{{ forloop.Counter }}</td>
<td>{{ city }}</td>
{% endfor %}
{% endfor %}


Good to know: Loop variable names are case sensitive.

e.g. to assign a numeric value (counting from 1) to each item printed from your iteration, use {{ forloop.Counter }}:

{% for user in users %}
   {{ forloop.Counter }} - {{ user.username }}
{% endfor %}



Good to know: By default Sendinblue escapes the HTML (and JS) content of all variables. This means any HTML passed within a variable will be escaped and printed as text. For example, if your variable content is <h1>My title</h1>, then your template would output exactly this text within your email: <h1>My title</h1> rather than outputting the phrase “My title” formatted as an H1 title.

The autoescape tag controls the current auto-escaping behavior. Since Sendinblue templates autoescape HTML by default, you will typically need to apply this tag to disable auto-escaping within a particular block. The block should be closed with an endautoescape ending tag.

We’ll review a common example when disabling autoescape may be useful. Let’s say that you pass products in a variable, but each product requires unique formatting.

If autoescape is used like this:

{% autoescape off %}{{ params.my_html }}{% endautoescape %}

Then, auto-escaping will be disabled for the variable {{ params.my_html }}. Now, if your variable content is <h1>My title</h1>, it will display as an H1 title. 



The verbatim tag allows you to use double braces {{ like this }} in your email without them being recognized as a template language element. To escape {{ }} and print these symbols directly in your email, wrap them in this tag:

{% verbatim %}
{{ Print variable }}
{% endverbatim %}

This text will appear fully in your email as:

{{ Print variable }}


Variables can be modified by filters. Filters are separated from the variable by a pipe symbol (|) and may have optional arguments in parentheses. Multiple filters can be chained. The output of one filter is applied to the next.

Most commonly used filters


Inserts a default value as a back-up when the original value evaluates to "false"

If the value evaluates as "true" then the value will be inserted instead of the default

Hello {{contact.NAME|default:“there”}}, 

If the attribute for NAME is empty, the default text will display instead as: 

Hello there,

If the attribute for NAME contains "Tom", then the text displayed will be: 

Hello Tom,

Modifying and formatting word case


capitalizes a value

The first character will be uppercase, all others lowercase.

{{ contact.NAME|capfirst }}

john doe will output John doe.


returns a titlecased version of the value

Words will start with uppercase letters, all remaining characters are lowercase.

{{ contact.NAME|title }}

john doe will output John Doe.


converts a value to uppercase

{{ contact.NAME|upper }}

john doe will output JOHN DOE.


converts a value to lowercase

{{ contact.NAME|lower }}

JOHN DOE will output john doe.


Truncates a string if it is longer than the specified number of characters. Truncated strings will end with a translatable ellipsis sequence (…).

{{ value|truncatechars:9 }}

If value = congratulations, it would output as congratul…

Formatting numbers


Rounds the output of your contact attribute or parameter’s float values to the specified decimal place.
To function correctly, the passed values should be passed as a number, rather than as a string.
(Values should not be passed in quotes as “value”.)

If your template contains
{{ contact.BALANCE }} and the output value would be 40.320000, the float may be rounded to “n” decimal place by using:
{{ contact.BALANCE | floatformat: n}}

{{ contact.BALANCE | floatformat: 2}}
Will output as 40.32 instead of 40.320000

{{ contact.BALANCE | floatformat: 0}}
Will output as 40 instead of 40.320000

Formatting date and time


Converts your date format (passed as a string) to a standard date format that may be used with other filters

Pass your current date format as an argument: format the exact date/time of how Monday January 2 15:04:05 -0700 MST 2006 would be displayed if it were the value.

If your string is formatted using RFC3339 you may use the dedicated parser time_parse_rfc3339 without an argument.

{{ params.my_date|time_parse:"15:04 02/01/2006" }}

{{ params.my_date|time_parse:"Monday 02 January 2006" }}

Note: Only English words for day and month are recognized.


Prints the date in the specified format
To define your own format, write down what the reference time, defined to be Mon Jan 2 15:04:05 -0700 MST 2006, would look like formatted your way.

Please note that you must pass a date to this filter.

You’ll get a date from a string using the time_parse filter.

{{ "14:01 01/06/2018"|time_parse:"15:04 02/01/2006"|date:"Mon Jan 2 15:04:05 2006"}}

Will output as:

Fri Jun 1 14:01:00 2018

Note: Only English words for day and month are recognized.

{{ time_now|date:"Monday, Jan 2, 2006"}}

Will print the current day and date in the specified format

{{ time_now|date:"01"}}

Will print the current month

{{ time_now|date:"02"}}

Will print the current day

{{ time_now|date:"2006"}}

Will print the current year


Adds number of days to a date string or timestamp

Please note that it is mandatory to pass all values (year, month and day) to this filter.

{{ params.my_date|time_parse:"15:04 02/01/2006”|time_add_date:"year, month, date"}}

{{"14:01 01/06/2018"|time_parse:"15:04 02/01/2006"|time_add_date:"1,2,3"}}

Will output as:

2019-08-04 14:01:00 +0000 UTC

{{time_now|time_add_date:"year, month, date"}}

{{time_now|time_add_date:"1,2,3" }}

Will print timestamp after adding 1 year, 2 months and 3 days to the current timestamp


Converts date string or timestamp according to the timezone passed to the filter 

Please note that it is mandatory to pass timezone to the filter and should be defined in the format “location/timezone”

{{ params.my_date|time_parse:"15:04 02/01/2006”|time_in_location:"location/timezone"}}

{{"14:01 01/06/2018"|time_parse:"15:04 02/01/2006"|time_in_location:"Europe/Berlin"}}

Will output as:

2018-06-01 16:01:00 +0200 CEST


{{ time_now|time_in_location:"Europe/Berlin" }}

Will print current timestamp in CEST


Adds time duration to a date string or timestamp.

Please note that it is mandatory to pass at least one value (hour, minute or second) in the filter.

{{time_now|time_add_duration:"1h2m3s" }}

Will print timestamp after adding 1 hour, 2 minutes  and 3 seconds to the time value in the current timestamp

{{time_now|time_add_duration:"30m" }}

Will print timestamp after adding 30 minutes to the time value


Converts a time value to Unix timestamp in seconds

 {{ time_now | time_unix}}

Will print current Unix timestamp in seconds


Converts a time value to Unix timestamp in nanoseconds

{{ time_now | time_unix_nano}} 

Will print current Unix timestamp in nanoseconds

Please note:

  • Day and month abbreviations and full names are supported in English only
  • If a value is not passed, it’s considered to be 0
  • The default timezone (if not passed) is UTC
  • You may use the am/pm keywords
  • The most common ways to define the timezone are: the dedicated keywords (MST, CET, UTC, Asia/Kolkata etc.), or as numeric, +0100 or -0100, to UTC.

Other filters


returns the first "element" of a sequence, a mapping, or a string

If your template contains {{params.array|first}}

and your API request contains:

“params” :
“array” : [ 1,2,3,4 ]

Will output as 1


returns a string which is the concatenation of the items of a sequence

The separator between elements is an empty string by default. You may define it with the optional first parameter.

{{ [1, 2, 3]|join }}

Will output as 123  

{{ [1, 2, 3]|join: '|' }}

Will output as 1|2|3


returns the last "element" of a sequence, a mapping, or a string

{{ [1, 2, 3, 4]|last }}

Will output as 4


returns the number of items of a sequence or mapping, or the length of a string

{% if users|length > 10 %}
{% endif %}


marks a string as not requiring further HTML escaping prior to output

{{ params.htmltest | safe }}

"params" :{HtmlTest: "<p>This is my test sentence.</p>"}

Will output a paragraph formatted in the recipient’s mail client.

{{ params.html }}
"params" :{HtmlTest: "<p>html para</p>"}

Without the safe filter, this will display the HTML tag as normal string in the recipient’s mail client:

<p>This is my test sentence</p>


extracts a slice of a sequence, a mapping, or a string

This filter may be configured to:

A. Loop through only the first ‘n’ number of elements in an array

B. Loop through elements between two specified positions (‘n’ and ‘m’) in the array

In both of these cases, remember that elements in an array are assigned a unique position number starting with ‘0’ as the first position (they are “zero-indexed”).

C. The filter may also be configured to loop through elements between two specified elements in an array.

In each example, consider that your array contains the following:


{% for product in params.products|slice:’:5’ %}
{{ - {{ product.price }}
{% endfor %}
Will loop over the first 5 positions (0-4) which are: ["a","b","c","d","e"}

{% for product in params.products|slice:’2:3’ %}
{{ - {{ product.price }}
{% endfor %}
Will loop over positions (2-3) which are: ["c","d"]

{% for product in params.products|slice:’b:d’ %}
{{ - {{ product.price }}
{% endfor %}
Will loop over the specified elements, ["b","c","d"]


Testing & Troubleshooting your template

To test your template and troubleshoot basic issues, please refer to this guide or contact our customer care team.

Good to know: When using the "Send a test" feature to send a test version of your email template, contact attribute variables will be replaced with recipient-specific content in the test email while transactional parameter variables will appear blank.


Additional documentation resources

The template language used by Sendinblue is based on the Django Template Language.

This guide covers the most relevant and frequently used template language structure and elements. Below is an exhaustive list of all supported tags and filters. Reference this Django documentation to view more details.

All supported tags

autoescape, comment, cycle, filter, firstof, for, if, ifchanged, now, set, spaceless, templatetag, with

All supported filters

escape, safe, escapejs, add, addslashes, capfirst, center, cut, date, default, default_if_none, divisibleby, first, floatformat, get_digit, iriencode, join, last, length, length_is, linebreaks, linebreaksbr, linenumbers, ljust, lower, make_list, phone2numeric, pluralize, random, removetags, rjust, slice, stringformat, striptags, time, title, truncatechars, truncatechars_html, truncatewords, truncatewords_html, upper, urlencode, urlize, urlizetrunc, wordcount, wordwrap, yesno (Reference:

All supported filters for hash algorithms

hash_md5, hash_sha256, hash_sha256_224, hash_sha512, hash_sha512_224, hash_sha512_256, hash_sha512_384, hash_sha3_224, hash_sha3_256, hash_sha3_384, hash_sha3_512

Please note: If you identify a desirable element that is not yet supported, please reach out to our customer care team at to request it.