> For the complete documentation index, see [llms.txt](https://docs.neonblue.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.neonblue.ai/email-integration/klaviyo-flow-experiments.md).

# Klaviyo Flow Experiments

Neon Blue integrates with [Klaviyo Flows](https://help.klaviyo.com/hc/en-us/articles/115002774932) to run experiments on flow-triggered emails.&#x20;

A Klaviyo flow experiment consists of two parts:

1. **Custom Action** — fetches the variant assignment from Neon Blue and writes it to the user's profile
2. **Email Template** — renders the assigned variant content using Klaviyo's template language

Neon Blue assigns each user a variant at send time and stores it on their Klaviyo profile. The email template then reads and renders that variant.

***

### Custom Action Setup

Add a [Custom Action](https://developers.klaviyo.com/en/docs/add_a_custom_action_to_a_flow) node to your flow, where you want users to receive Neon Blue variants and name it `nb_assignment`. This node calls the Neon Blue assignment API, retrieves the variant content for the user, and stores it on their Klaviyo profile.\
\
Set-up the following details for your custom action:

#### Code

```javascript
import { Profiles } from 'klaviyo'

const EXPERIMENT_ID = process.env.EXPERIMENT_ID;
const API_KEY = process.env.API_KEY;

export default async (event, profile, context) => {
  // construct assignment payload
  const payload = {
    profile: {
      type: profile.data.type,
      id: profile.data.id,
      attributes: profile.data.attributes,
      properties: profile.data.properties
    },
    event: {
      type: event.data.type,
      id: event.data.id,
      attributes: event.data.attributes
    },
    context: {
      trigger: context.trigger,
      function_id: context.function_id
    }
  };

  // fetch assignment
  const res = await fetch(
    `https://api.aws.neonblue.ai/v1/render/${EXPERIMENT_ID}?user_id=${profile.data.id}`,
    {
      method: 'POST',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
        'X-Partner': 'coterie',
        'X-Api-Key': API_KEY,
        'X-Preview': context.trigger.type == "MANUAL_RUN"
      },
      body: JSON.stringify(payload)
    }
  );
  const assignment = await res.json();
  const assignment_id =
    assignment.$metadata.assignment_id ||
    '00000000-0000-0000-0000-000000000000';

  // update user profile with variant content
  const $neonblue = profile.data.attributes.$neonblue || {};
  $neonblue[assignment_id] = assignment.value;
  await Profiles.updateProfile(profile.data.id, {
    data: {
      type: "profile",
      id: profile.data.id,
      attributes: {
        properties: {
          $neonblue: $neonblue
        }
      }
    }
  });

  return {
    assignment_id: assignment_id
  };
}
```

#### Environment Variables

| Variable        | Description                                                                                |
| --------------- | ------------------------------------------------------------------------------------------ |
| `API_KEY`       | Your Neon Blue API key (found in the Neon Blue app on the bottom left of the left sidebar) |
| `EXPERIMENT_ID` | The UUID or slug identifying your experiment                                               |

To ensure a valid experiment id, make sure the experiment has been promoted from draft experiments. This is where you will find the Experiment ID:<br>

<figure><img src="https://3940235945-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0In3UCjZvAgYWvb8eTzO%2Fuploads%2FXsAQtL84tvbFaDt52ALF%2FScreenshot%202026-04-29%20at%2016.46.38.png?alt=media&amp;token=56f2d867-0021-4292-9d3c-e399366723ef" alt=""><figcaption></figcaption></figure>

#### Outputs

| Output          | Type     | Default                                | Description                                              |
| --------------- | -------- | -------------------------------------- | -------------------------------------------------------- |
| `assignment_id` | `string` | `00000000-0000-0000-0000-000000000000` | The unique identifier for this user's variant assignment |

#### Testing Your Custom Action

To verify your Custom Action is set up correctly:

1. **Identify a test profile**
   * Go to your flow and open the **Preview** mode
   * Find a user who would naturally enter the path where the `nb_assignment` action runs by clicking on the different user profiles
2. **Run a test execution**
   * Go back to the Custom Action node
   * Click **Test**
   * Use the selected profile as the input
3. **Validate the result**
   * The test should return the default `assignment_id`  and 'success'.

#### How It Works

When the flow triggers, the Custom Action sends the user's profile and event context to the Neon Blue assignment API. The API returns a response in the following shape:

```json
{
  "value": {
    "subject": "...",
    "preview": "...",
    "{FIELD_0}": "{VALUE_0}"
  },
  "$metadata": {
    "action": "render",
    "assignment_id": "{ASSIGNMENT_UUID}"
  }
}
```

The `value` object contains the variant content fields configured in your experiment. The Custom Action writes this content to the user's [custom profile properties](https://help.klaviyo.com/hc/en-us/articles/115000250912) under the `$neonblue` key, indexed by `assignment_id`.

> **Why write to the profile?** Klaviyo Custom Action nodes are limited to 5 return values. Storing variant content on the profile allows experiments with any number of content fields.

The `X-Preview` header is automatically set to `true` when the flow is triggered via a manual test run, preventing test sends from being recorded as exposures.

***

### Template Setup

Email templates retrieve variant content from the user’s profile using the `assignment_id` returned by the `nb_assignment` Custom Action. This content is stored under the `$neonblue` property and is used to dynamically render the email.

A single Neon Blue variant can include content for multiple emails in a flow, allowing for consistent messaging across the user journey.\
\
Emails after the `nb_assignment` node must be configured to use Neon Blue variant content by setting up the following:

#### Subject and Preview Text

In your flow, select the email node and on the sidebar, set the email **Subject** and **Preview text** to reference variant content via the user's profile:

**Subject:**

{% @neonblue-snippet/snippet-block template="{%with assignment\_id=outputs.nb\_assignment.assignment\_id%}{{person|lookup:"$neonblue"|lookup:assignment\_id|lookup:"{{SLOT\_KEY}}"|lookup:"subject"}}{%endwith%}" %}

**Preview text:**

{% @neonblue-snippet/snippet-block template="{%with assignment\_id=outputs.nb\_assignment.assignment\_id%}{{person|lookup:"$neonblue"|lookup:assignment\_id|lookup:"{{SLOT\_KEY}}"|lookup:"preview"}}{%endwith%}" %}

Replace the 'SLOT KEY' placeholder with it's value in Neon Blue and replace `"subject"` and `"preview"` with the field names configured in your experiment if they differ.

#### Email Body

In the Klaviyo Flow, select the template used by your email and ensure it matches the template configured in Neon Blue.

Use the images below as a reference for where to select and edit the template in Klaviyo.<br>

<figure><img src="https://3940235945-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0In3UCjZvAgYWvb8eTzO%2Fuploads%2FWzuvT0p0qjZGSxktRcco%2FScreenshot%202026-04-29%20at%2017.14.22.png?alt=media&amp;token=74648d70-b14f-462a-bec3-780da0418cb4" alt=""><figcaption></figcaption></figure>

<figure><img src="https://3940235945-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0In3UCjZvAgYWvb8eTzO%2Fuploads%2FcykIAV6kCT0akeQfL3Jh%2FScreenshot%202026-04-29%20at%2017.14.51.png?alt=media&amp;token=d031bcc8-30f3-431b-a4be-1f1782ee9a86" alt=""><figcaption></figcaption></figure>

#### Required Header and Footer Blocks

In the Klaviyo template:

The **first** content block must be the Universal Content Block `[Neon Blue] Experiment Header`:

{% @neonblue-snippet/snippet-block template="<!--{%with nb=person|lookup:"$neonblue"|lookup:outputs.nb_assignment.assignment_id|lookup:"{{SLOT_KEY}}"%}-->" %}

The **last** content block must be the  Universal Content Block `[Neon Blue] Experiment Footer`:

```django
<!--{%endwith%}-->
```

These blocks populate the `nb` template variable with the full variant content object. You can then reference any experiment field using `{{nb.<field_name>}}` anywhere in the email body between the header and footer.

For example, if your experiment is configured with a field called `h1`:

```django
<h1>{{nb.h1}}</h1>
```

#### Template Checklist

* [ ] Subject line references variant content via `outputs.nb_assignment.assignment_id`
* [ ] Preview text references variant content via `outputs.nb_assignment.assignment_id`
* [ ] First content block is `[Neon Blue] Experiment Header`
* [ ] Last content block is `[Neon Blue] Experiment Footer`
* [ ] All dynamic content fields use the `{{nb.<field_name>}}` syntax between the header and footer blocks
