Skip to content

Braze

Set up Braze → JustAI data ingestion so JustAI can measure performance (send/open/click/conversion) and power downstream workflows. This is one-time setup - all templates will share this configuration.

JustAI ingests Braze reporting events via Braze Currents. This provides the core metrics needed for analysis (opens, clicks, sends, conversions), as well as custom metrics that are important to you.

  • Confirm which Braze workspace you’re connecting.
  • Identify your JustAI org slug (usually your company name in all lower case - feel free to ask us).
  • Make sure you have permissions to create API credentials and Connected Content calls in Braze.

JustAI integrates with Braze to fetch canvases and messages when creating new templates. This requires a REST API key from your Braze account.

  1. Navigate to Settings → APIs and Identifiers.

  2. Click Create API Key.

  3. Set the name to “JustAI”.

  4. Leave the IP Allowlist empty.

  5. Enable the following permissions:

    • Campaigns, Canvas, Catalogs, Content Blocks
    • Email, Messages, Segments, SMS, Templates
    • User Data → users.export.ids

    Braze API Key Permissions

  6. Click Save.

  7. Copy the API key from the table by clicking the copy icon:

    Copy Braze API Key

  1. Navigate to Settings → Integrations.

  2. Choose Braze for the Integration.

  3. Select the cluster instance (region) where your Braze app is hosted (e.g., US-01, EU-01):

    Braze Cluster Selection

  4. Paste the API key from Step 1. Click Verify before saving:

    Save Braze API Key

  5. Set the App ID as well.

  6. Click Save changes.

Braze uses Connected Content to fetch JustAI content at send time. The connected_content snippet must be placed in your email subject, preheader, and body.

The copy-pasteable snippet can be found in the Template Configuration page in the JustAI platform. The snippet will look something like this:

{%- connected_content https://worker.justwords.ai/api/generate/<org_slug>?template_id=<template_id>&tracking_id={{campaign.${dispatch_id}}}-{{${user_id}}}&user_id={{${user_id}}} :save result :rerender :cache_max_age 5 :headers { "x-api-key": "<JUSTAI_API_KEY>", "Content-Type": "application/json" } -%}

After the connected_content call, use the result with fallback handling. The snippets from the platform will look something like this:

Subject line:

{%- connected_content https://worker.justwords.ai/api/generate/<org_slug>?template_id=<template_id>&tracking_id={{campaign.${dispatch_id}}}-{{${user_id}}}&user_id={{${user_id}}} :save result :rerender :cache_max_age 5 :headers { "x-api-key": "<JUSTAI_API_KEY>", "Content-Type": "application/json" } -%}
{% if result.__http_status_code__ != 200 %}
Your fallback subject line
{% else %}
{{result.copy.vars.subject}}
{% endif %}

Preheader:

{%- connected_content https://worker.justwords.ai/api/generate/<org_slug>?template_id=<template_id>&tracking_id={{campaign.${dispatch_id}}}-{{${user_id}}}&user_id={{${user_id}}} :save result :rerender :cache_max_age 5 :headers { "x-api-key": "<JUSTAI_API_KEY>", "Content-Type": "application/json" } -%}
{% if result.__http_status_code__ != 200 %}
Your fallback preheader
{% else %}
{{result.copy.vars.preheader}}
{% endif %}

Body:

{%- connected_content https://worker.justwords.ai/api/generate/<org_slug>?template_id=<template_id>&tracking_id={{campaign.${dispatch_id}}}-{{${user_id}}}&user_id={{${user_id}}} :save result :rerender :cache_max_age 5 :headers { "x-api-key": "<JUSTAI_API_KEY>", "Content-Type": "application/json" } -%}
{% if result.__http_status_code__ != 200 %}
Your fallback body content
{% else %}
{{result.copy.vars.body}}
{% endif %}

This line is included once per message, after one of the connected_content calls (typically the subject line). It is already included in the copy-pasteable snippet from the Template Configuration page.

{% message_extras :key copy_id :value {{result.copy.id}} %}

This attaches the JustAI variant ID to the message for reporting correlation.

JustAI supports two ways to pass user data: attrs and fields.

ParameterPurposeUse When
attrsAttributes used for ranking and filtering variants (passed as query params)You want JustAI to select different content based on user segments (e.g., persona, plan type).
fieldsPersonalization tokens passed via :body for Braze to prehydrate before rerenderYou need to insert user-specific values (e.g., first name) into JustAI content that contains Liquid variables.

Add attrs as query parameters to select content based on user segments:

&attrs.persona={{custom_attribute.${persona}}}&attrs.plan={{custom_attribute.${plan_type}}}

Full URL example:

{%- connected_content https://worker.justwords.ai/api/generate/<org_slug>?template_id=<template_id>&tracking_id={{campaign.${dispatch_id}}}-{{${user_id}}}&user_id={{${user_id}}}&attrs.persona={{custom_attribute.${persona}}}&attrs.plan={{custom_attribute.${plan_type}}} :save result :rerender :cache_max_age 5 :headers { "x-api-key": "<JUSTAI_API_KEY>", "Content-Type": "application/json" } -%}

Fields are passed as :body params in the connected_content call. This allows Braze to prehydrate the values so they can be injected into JustAI’s response when Braze applies :rerender.

For example, if your JustAI content contains {{first_name}}, you must pass first_name in the :body param. Otherwise, Braze won’t have the value available to substitute during rerender.

:body first_name={{${first_name}}}&city={{custom_attribute.${city}}}
Data TypeSyntaxExample
Standard attribute{{${attribute_name}}}{{${first_name}}}
Custom attribute{{custom_attribute.${attribute_name}}}{{custom_attribute.${persona}}}
Event property{{event_properties.${property_name}}}{{event_properties.${product_category}}}
Canvas entry property{{canvas_entry_properties.${property_name}}}{{canvas_entry_properties.${campaign_source}}}
Context{{context.${attribute_name}}}{{context.${role}}}
{%- connected_content https://worker.justwords.ai/api/generate/<org_slug>?template_id=<template_id>&tracking_id={{campaign.${dispatch_id}}}-{{${user_id}}}&user_id={{${user_id}}}&attrs.persona={{custom_attribute.${persona}}}&attrs.plan={{custom_attribute.${plan_type}}} :body first_name={{${first_name}}}&city={{custom_attribute.${city}}} :save result :rerender :cache_max_age 5 :headers { "x-api-key": "<JUSTAI_API_KEY>", "Content-Type": "application/json" } -%}
{% if result.__http_status_code__ != 200 %}
Fallback content here
{% else %}
{{result.copy.vars.body}}
{% endif %}
{% message_extras :key copy_id :value {{result.copy.id}} %}

If your Braze emails use Content Blocks that contain Liquid logic, you need to prehydrate them before they reach JustAI. Braze’s :rerender only hydrates Liquid one level deep, so if a Content Block contains Liquid variables (e.g., {{${first_name}}}), those variables won’t be hydrated when the block is returned from JustAI.

To handle this, pass the Content Block as a field in the :body parameter. This forces Braze to hydrate the Content Block’s Liquid before calling JustAI, so the fully-rendered content is available during the rerender phase.

  1. In your JustAI copy, reference the content block with the json_escape filter: {{ my_content_block | json_escape }}
  2. In the connected_content :body, pass the prehydrated content block:
:body my_content_block={{content_blocks.${my_content_block}}}

Using json_escape is a best practice for Content Blocks, especially complex ones. If you preview the message in Braze and the email doesn’t render correctly (showing the entire JustAI API response instead), adding json_escape to your JustAI variant will usually fix it.

Full example:

{%- connected_content https://worker.justwords.ai/api/generate/<org_slug>?template_id=<template_id>&tracking_id={{campaign.${dispatch_id}}}-{{${user_id}}}&user_id={{${user_id}}} :body my_content_block={{content_blocks.${my_content_block}}}&first_name={{${first_name}}} :save result :rerender :cache_max_age 5 :headers { "x-api-key": "<JUSTAI_API_KEY>", "Content-Type": "application/json" } -%}
{% if result.__http_status_code__ != 200 %}
Fallback content here
{% else %}
{{result.copy.vars.body}}
{% endif %}
{% message_extras :key copy_id :value {{result.copy.id}} %}

In this example, my_content_block is fully hydrated by Braze before JustAI receives it. When JustAI’s response includes {{ my_content_block | json_escape }}, Braze substitutes the prehydrated content during rerender.

JustAI can ingest engagement and custom event data exported from your Braze instance via Braze Currents and a shared S3 bucket.

If you already have a Braze Current set up, you can forward some or all of the events back to JustAI to avoid setting up a new Braze Current.

  1. Create an IAM role for the export:
    • Recommended Permissions: s3:PutObject, s3:ListBucket, s3:GetBucketLocation
    • Share the ARN role with JustAI. JustAI team will grant read/write permissions to a shared S3 bucket.
  2. Create a daily data export that writes data to this S3 bucket: s3://justwords-metrics-ingest/<org_slug> (replace <org_slug> with your actual org name).

Braze Configuration

You can re-export the Braze Current data as-is (Avro) back to JustAI. As long as data is partitioned by time (hourly) and uses a common serialization format (gzip, parquet, etc), integration will work. Braze Currents data is already partitioned by event type, making it straightforward to sync only a subset of events.

Common export patterns: Spark jobs, Redshift queries, Databricks jobs.

  1. In JustAI, open the template and go to Integration Settings.
  2. Select Braze and set:
    • Template type (Email, Push, In-App, Webhook)
    • Canvas ID
    • Control step/message IDs
    • Treatment step/message IDs
  3. Save changes.
  4. In Settings → Integrations, set your Braze API key and cluster so JustAI can sync metadata.
  • Connected Content returns empty: Verify your API key is correct and the template_id exists in JustAI.
  • Mismatched content across subject/preheader/body: Ensure the connected_content snippet is identical in all locations. Even whitespace differences can cause separate API calls.