Braze Liquid personalization: defaults, templates and DKIM
Braze Liquid prints a blank for a missing first name. How to set a default, write if/else logic, format dates, use abort_message and set up DKIM.
400 KB
On this page
- How do Braze Liquid tags work?
- How to set a default value in Braze Liquid
- Braze Liquid logic: if, else and the operators
- Which Liquid filters does Braze support?
- How to stop a send with abort_message
- Where does Liquid work in a Braze email?
- How to create a Braze email template
- How to set up a Braze sending domain and DKIM
- Sources
In Braze, the first-name tag is {{${first_name}}}, and Liquid's default filter gives it a fallback: {{${first_name} | default: 'there'}}. Without a default, a user whose first name is missing or not set gets a blank in the message. The default covers an empty value but not one made of spaces, so check for blank too, and use abort_message when an email must not go out without the value.
first_name: "Ada"Never setfirst_name: nilSaved as a spacefirst_name: " "Hi {{${first_name}}},Hi Ada,Hi (blank),A blankHi (blank),A spaceHi {{${first_name} | default: 'there'}},Hi Ada,Hi there,Hi (blank),A space: the default does not apply to whitespace{% if ${first_name} == blank %}
Hi there,
{% else %}
Hi {{${first_name}}},
{% endif %}Hi Ada,Hi there,Hi there,{% if ${first_name} == blank %}
{% abort_message('no first name') %}
{% endif %}
Hi {{${first_name}}},Hi Ada,Not sentLogged as a Liquid abortNot sentLogged as a Liquid abortOrange: a greeting that goes out with no name in it
How do Braze Liquid tags work?
Liquid is Shopify's template language, and Braze supports it up to and including Liquid 5. Default attributes are written {{${first_name}}}. A custom attribute needs the custom_attribute namespace, as in {{custom_attribute.${zip code}}}. The Add Personalization modal inserts a tag with its default written in.
The tags an email uses most, from Braze's supported personalization tags and its custom footer page:
| Tag | What it inserts |
|---|---|
{{${first_name}}}, {{${last_name}}} | First and last name from the profile |
{{${email_address}}} | The user's email address |
{{custom_attribute.${your_attribute}}} | A custom attribute |
{{event_properties.${your_property}}} | A property of the event that triggered the message |
{{api_trigger_properties.${your_property}}} | A property sent with an API trigger |
{{campaign.${name}}} | The campaign name, or the step name in a Canvas |
{{${set_user_to_unsubscribed_url}}} | The unsubscribe URL |
{{${email_footer}}} | The workspace's custom footer |
Three of Braze's syntax rules prevent most broken tags: use straight quotes, not curly ones pasted from a text editor; close every if with endif; and keep Liquid inside <body> and out of HTML comments, which Braze strips first. Write it in the HTML editor, since the classic editor may print it as plain text.
How to set a default value in Braze Liquid
Add the filter after a pipe, inside the braces, as in Braze's example on setting default values:
Hi {{ ${first_name} | default: 'Valued User' }}, thanks for using the App!The same page draws the line that matters: the default shows for empty values, not blank ones. An empty value contains nothing, like "". A blank one holds only whitespace, like " ", and goes out as a space. Numbers work too: {{custom_attribute.${reward_points} | default: 0}}.
Braze supports strip and runs filters left to right, so {{${first_name} | strip | default: 'there'}} should empty a name of spaces before the default applies. Braze does not show that combination, so preview it on a profile whose first name is a space.
Braze Liquid logic: if, else and the operators
Logic tags go in {% %}. An if takes elsif and else as needed and must close with endif, or Braze cannot parse the message. That page separates null, a value never set, from blank, which also catches whitespace and false. Braze's example of a different line in place of a default:
{% if ${first_name} != blank and ${first_name} != null %}
Hello {{${first_name} | default: 'there'}}, thanks for downloading!
{% else %}
Thanks for downloading!
{% endif %}The supported operators are ==, !=, >, <, >=, <=, or, and and contains. Liquid has no parentheses, so group conditions with nested if tags. A filter cannot sit inside an if or a for loop: assign the result first, then test the variable.
Which Liquid filters does Braze support?
Most of Shopify's, not all. The filter reference marks lstrip, rstrip, pluralize and money_with_currency unsupported, among others, and says its list may be incomplete. Braze adds advanced filters of its own, such as url_encode and number_with_delimiter.
How to format a date in Braze Liquid
The date filter takes strftime formats, and %s gives Unix time. Dates render in UTC unless you add time_zone, as Braze's use case library does with the profile's own zone:
{{custom_attribute.${date_time_attribute} | time_zone: {{${time_zone}}} | date: '%a, %b %e, %Y'}}The filters page shows '%b %d' printing "03 June", which strftime would write as Jun 03, so preview dates rather than trust the samples. Comparing a time attribute to blank fails with "Comparison of Time with String Failed"; the Liquid FAQ assigns it with | default: "" first.
How do for loops work in Braze Liquid?
A for loop repeats a block for each item in an array attribute, a catalog selection or a Connected Content response. The loop cannot take a filter, so assign the filtered array first, as in Braze's example:
{% assign reversed = my_array | reverse %}
{% for item in reversed %}
{{ item }}
{% endfor %}How to stop a send with abort_message
An abort_message tag inside a conditional cancels the send for that user, and the message does not appear on the profile or count toward deliveries or frequency capping. In a Canvas, the user moves on to the next step. The reason must be a plain string in quotes.
{% if ${first_name} == blank %}
{% abort_message('first_name was blank') %}
{% endif %}Aborts appear in Messaging Observability as Liquid abort. A test send to a user who meets the condition never arrives, so preview as a user who qualifies and turn on Override recipients' attributes with current preview user's attributes.
Where does Liquid work in a Braze email?
- Subject, preheader and headers take Liquid, but each part is rendered separately, so a variable assigned in the body does not reach the subject. Braze's styling guide says the preheader shares the body's Liquid; that FAQ says it does not. Repeat the
assignin each field. - Link URLs take Liquid, but link templates expand first, so hardcode the base URL and the
?, and write a complete anchor tag in eachifbranch. - Not in HTML comments, an
abort_messagereason or the Add from URL field for attachments.
To test, open Preview and Test and choose Preview as User. In a test send, the unsubscribe tag renders as a URL only for a recipient with a profile and an external ID.
How to create a Braze email template
As of October 2026, go to Content, Email, then Create email template; some Braze pages call the same list Templates, Email Templates. Choose the HTML code editor, or upload a ZIP with one HTML file and a folder of images: fewer than 50 images and under 5 MB. Later edits to a template do not reach campaigns built from it.
Can you save a Braze email as a template?
Not a drag-and-drop email built in a campaign or Canvas. Braze's FAQ says to rebuild it, or select Download file and paste the HTML into an HTML template. Its pages describe no save step for an HTML email in a campaign either, so copy the code across.
The Braze email template API
The create endpoint takes template_name, subject and a body of up to 400 KB, plus optional plaintext_body, preheader, tags and should_inline_css. The key needs the templates.email.create permission, and the response returns an email_template_id. The host depends on your Braze instance.
curl --request POST 'https://rest.iad-01.braze.com/templates/email/create' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_REST_API_KEY' \
--data-raw '{
"template_name": "welcome",
"subject": "Welcome to Example",
"body": "<p>Hello</p>"
}'What does Braze change in pasted HTML?
Braze removes HTML event handler attributes, so check the email after pasting. Its parser also rewrites tags that open in one Liquid branch and close in another, which can move head content into the preheader or break links. CSS inlining is a toggle on each email, with a default under Settings, Email Preferences (CSS inlining). And there is no view in browser link.
Before send, the editor flags Liquid errors, a blank body or subject, a missing unsubscribe link and a body over 400 KB. The styling guide recommends under 102 KB to avoid Gmail's clipping and 600 pixels wide at most, and notes that Braze typically adds up to 5 KB at send time.
Which unsubscribe tag does Braze need?
{{${set_user_to_unsubscribed_url}}}, which replaces {{${unsubscribe_url}}}. Or build a footer under Settings, Email Preferences, Subscription Pages and Footers and place it with {{${email_footer}}}; Braze will not save one without an unsubscribe link. The pages disagree on the check itself: the template guide calls a missing link an error, the footer page a warning you may send past.
How do Braze email link templates work?
A link template adds a string before every URL in an email, or query parameters such as UTM tags after it. Your account manager turns the feature on, and you add one to an email under Link Management. It skips the plaintext version, HTML comments, links in {{${email_footer}}} and any URL that Liquid renders invalid.
How to set up a Braze sending domain and DKIM
There is no SPF or DKIM settings page: Braze generates the records during domain setup and you publish them. Since 2026, new setups send through Amazon SES by default, with SendGrid or SparkPost through Braze Support, and the records differ by provider. Both routes start with Braze's onboarding team adding your IP pools:
- Email Self Serve, for new customers with the Edit Domain Settings permission. As of October 2026: Settings, Email Self Serve, Start setup. Enter the sending domain, publish the TXT and CNAME records shown, select Verify, then repeat for a tracking domain.
- Verified domains, on SES only. You delegate a subdomain with four NS records and a TXT record, within 30 days, and Braze maintains every record under it. Sending domains must then be at least three levels deep, such as e.mail.example.com.
Braze requires a dedicated subdomain: bounce MX records on the root would collide with company mail, and its SPF includes would spend your 10-lookup limit.
Amazon SES
The default for new setups from 2026. 6 records.
DKIM key 1CNAME
Host
sel1._domainkey.mail.example.comValuesel1.dkim.amazonses.comDKIM key 2CNAME
Host
sel2._domainkey.mail.example.comValuesel2.dkim.amazonses.comDKIM key 3CNAME
Host
sel3._domainkey.mail.example.comValuesel3.dkim.amazonses.comBouncesMX
Host
bounce.mail.example.comValue10 feedback-smtp.us-east-1.amazonses.comSPFTXT
Host
bounce.mail.example.comValuev=spf1 include:amazonses.com ~allClick trackingCNAME
Host
track.mail.example.comValuer.us-east-1.awstrack.me
SendGrid
Through Braze Support, when SES does not fit. 4 records.
Return-Path and SPFCNAME
Host
em.mail.example.comValueu123456.wl.sendgrid.netDKIM key 1CNAME
Host
s1._domainkey.mail.example.comValues1.domainkey.u123456.wl.sendgrid.netDKIM key 2CNAME
Host
s2._domainkey.mail.example.comValues2.domainkey.u123456.wl.sendgrid.netClick trackingCNAME
Host
email.mail.example.comValuesendgrid.net
SparkPost
Through Braze Support, when SES does not fit. 3 records.
Return-Path and SPFCNAME
Host
mail.example.comValuesmtp.sparkpostmail.comDKIM keyTXT
Host
scph1226._domainkey.mail.example.comValuev=DKIM1; k=rsa; p=...Click trackingCNAME
Host
click.mail.example.comValuespgo.io
On every provider
DMARCTXT
Host_dmarc.example.comValuev=DMARC1; p=none
Yours to add, at the root domain. Braze says p=none meets the minimum inbox requirements.
SPF on example.com
Not asked for. SPF is checked on the Return-Path subdomain above.
Underlined in orange: the DKIM selector
Does Braze need an SPF record?
Not on your root domain. Braze leaves that record alone and has SPF checked on a Return-Path subdomain: a CNAME to the provider on SendGrid and SparkPost, an MX and a TXT with include:amazonses.com on Amazon SES.
Which DKIM selectors does Braze use?
Whatever your provider issues, shown on your setup page. Braze's examples: SendGrid "often" uses s1 and s2, SparkPost a selector such as scph1226, and SES three CNAMEs written sel1 to sel3. AWS says each SES record is a generated token followed by a hosted zone, so expect long random selectors there. In public DNS on 31 October 2026, Braze's own e.braze.com points s1._domainkey and s2._domainkey at SendGrid, with a 2048-bit key.
Does Braze DKIM align with your From domain for DMARC?
Braze says its setups align through both SPF and DKIM, since the keys and the Return-Path sit under your sending subdomain. DMARC is yours to add: a TXT at _dmarc on the root domain, where p=none meets the minimum and the policy covers every subdomain. The pages we read show no sample DKIM-Signature, so confirm d= on a received message.
How to check it once it is live
Braze's own check is to open a message from your domain, choose Show original in Gmail, and look for a DMARC PASS. From outside, query the records with your own selector:
dig +short CNAME s1._domainkey.mail.example.com @1.1.1.1
dig +short TXT _dmarc.example.com @1.1.1.1Then send yourself a test from the new domain and confirm its links use your tracking domain (Email self-serve). HTTPS click tracking needs an SSL certificate through a CDN, unless Braze manages it on a verified domain.
The free mail-merge preview flags every empty merge field in a list exported from Braze, and the DKIM checker takes the selector from your setup page. The editor exports Braze HTML with default fallbacks and {{${set_user_to_unsubscribed_url}}} written in. Draftship also builds and runs outbound inside a client's own platform.
Sources
- braze.coma blank in the message
- braze.comsupports it up to and including Liquid 5
- braze.comThe tags an email uses most, from Braze's supported personalization tags and its
- braze.comThe tags an email uses most, from Braze's supported personalization tags and its custom footer page
- braze.comBraze supports strip and runs filters left to right, so {{${first_name} | strip | default: 'there'}} should empty a name of spaces before the default applies
- braze.comcannot parse the message
- braze.comThe supported operators are ==, !=, >, <, >=, <=, or, and and contains
- braze.comBraze adds advanced filters of its own, such as url_encode and number_with_delimiter
- braze.comDates render in UTC unless you add time_zone, as Braze's use case library does with the profile's own zone
- braze.comComparing a time attribute to blank fails with "Comparison of Time with String Failed"; the Liquid FAQ assigns it with | default: "" first
- braze.comdoes not appear on the profile or count toward deliveries or frequency capping
- braze.comBraze's styling guide says the preheader shares the body's Liquid; that FAQ says it does not
- braze.comlink templates expand first
- braze.comNot in HTML comments, an abort_message reason or the Add from URL field for attachments
- braze.comonly for a recipient with a profile and an external ID
- braze.comChoose the HTML code editor
- braze.comChoose the HTML code editor, or upload a ZIP with one HTML file and a folder of images: fewer than 50 images and under 5 MB
- braze.comBraze's FAQ says to rebuild it, or select Download file and paste the HTML into an HTML template
- braze.comup to 400 KB
- braze.commove head content into the preheader or break links
- braze.comCSS inlining is a toggle on each email, with a default under Settings, Email Preferences
- braze.comno view in browser link
- braze.comA link template adds a string before every URL in an email, or query parameters such as UTM tags after it
- braze.comAmazon SES by default
- braze.comYou delegate a subdomain with four NS records and a TXT record, within 30 days, and Braze maintains every record under it
- braze.comrequires a dedicated subdomain
- docs.aws.amazon.coma generated token followed by a hosted zone
- dns.googles1._domainkey and s2._domainkey at SendGrid
- braze.comDMARC is yours to add: a TXT at _dmarc on the root domain, where p=none meets the minimum and the policy covers every subdomain
- braze.comThen send yourself a test from the new domain and confirm its links use your tracking domain
- braze.coman SSL certificate through a CDN