> ## Knowledge Base Index
> Fetch the complete knowledge base index at: https://help.devit.software/sitemap.xml
> Use this file to discover available pages before exploring further.
> Pure-Markdown content can be obtained by appending a '.md' suffix to the content URLs listed in the sitemap (without the trailing slash).

# React Flow recipes: Liquid in emails, HTTP request bodies, tags from metafields and win-back

# ${color}[#204a87](React Flow recipes: Liquid in emails, HTTP request bodies, tags from metafields and win-back)

${color}[#3465a4](**In this article:**)
* [Before you start: test Liquid with Log output](#1-before-you-start-test-liquid-with-log-output)
* [Recipe 1: Liquid in an email template](#1-recipe-1-liquid-in-an-email-template)
* [Recipe 2: JSON body for an HTTP request](#1-recipe-2-json-body-for-an-http-request)
* [Recipe 3: tag products from a metafield](#1-recipe-3-tag-products-from-a-metafield)
* [Recipe 4: win-back campaign for inactive customers](#1-recipe-4-win-back-campaign-for-inactive-customers)
* [Liquid cheat sheet](#1-liquid-cheat-sheet)

These recipes show how to combine React Flow steps and Liquid variables to solve common tasks. Each recipe lists the trigger, the steps and the exact values to enter, so you can rebuild it in a few minutes and adjust it to your store.

# ${color}[#204a87](Before you start: test Liquid with Log output)

Almost every text field in React Flow accepts Liquid: email subjects and bodies, tags, HTTP request bodies, file names and more. Variables use the Shopify field names, for example `{{ ``order.name`` }}`, `{{ product.title }}` or `{{ customer.firstName }}`. Click **Add variables** next to a field to find the right name.

To check what a piece of Liquid returns before you use it:

1. Add the **Log output** action ("Log and inspect Liquid variable values for debugging").
2. Paste your Liquid into **Output**.
3. Click **Run Test** (or **Run flow** in a One-Shot workflow) and open **Log output** in the result to see the rendered text.

![](https://storage.crisp.chat/users/helpdesk/website/-/3/2/7/1/3271b421d88bd400/log-output-liquid_m9fzyn.png =800x382)

|| **React Flow uses** [LiquidJS](https://liquidjs.com/tutorials/intro-to-liquid.html)**, not Shopify's native Liquid implementation.**
As a result, some Shopify-specific Liquid tags and filters may not be fully supported or may behave differently.
# ${color}[#204a87](Recipe 1: Liquid in an email template)

**Goal:** a personal order email that greets the customer, lists the purchased products with links, and shows a special line for large orders.

1. Trigger: **Order created** (or **Order fulfilled** for a review request).
2. Action: **Send email**.
3. **Send to:** `{{ ``order.email`` }}`
4. **Subject:** `Thank you for your order {{ ``order.name`` }}, {{ order.customer.firstName | default: "friend" }}!`
5. **Message:** click **<>** in the editor toolbar to switch to HTML and paste:

```
<p>Hi {{ order.customer.firstName | default: "there" }},</p>
<p>Here is what you ordered:</p>
<ul>
{% for line_item in order.lineItems %}
  <li><a href="{{ shop.primaryDomain.url }}/products/{{ line_item.product.handle }}">{{ line_item.name }}</a></li>
{% endfor %}
</ul>
{% assign total = order.totalPriceSet.shopMoney.amount | plus: 0 %}
{% if total > 100 %}
<p>Your order qualifies for free gift wrapping. Just reply to this email!</p>
{% endif %}
<p>{{ shop.name }}</p>
```

6. Click **Preview** above the editor to check the layout, then **Run Test** > **Simulate** with a real order.

Tips:

* Use `| default: "..."` for values that can be empty, such as the first name of a guest customer.
* For a reliable delivery, send emails through your own SMTP or Microsoft connector.

# ${color}[#204a87](Recipe 2: JSON body for an HTTP request)

**Goal:** send product data to an external system (an ERP, a warehouse or a webhook) whenever a product is created.

1. Trigger: **Product added to store**.
2. Action: **Send HTTP request**.
3. **Method:** `POST`
4. **Url:** the address of the receiving system, for example `https://example.com/webhooks/products`
5. **Headers:** `Content-Type` = `application/json`. Add an authorization header if the system requires one, for example `Authorization` = `Bearer <your token>`.
6. **Body:**

```
{
  "id": "{{ product.id }}",
  "title": {{ product.title | json }},
  "tags": {{ product.tags | json }}
}
```

![](https://storage.crisp.chat/users/helpdesk/website/-/3/2/7/1/3271b421d88bd400/send-http-request-body_epdncq.png =800x382)

Why `| json`? It wraps text in quotes and escapes special characters, and it turns lists into JSON arrays. In our test, `{{ product.title | json }}` returned `"Snowboard: Liquid"` and `{{ product.tags | json }}` returned `["Accessory","customize","Sport","Winter"]`. Without the filter, a title with a quote mark would break the JSON.

Optional settings:

* **Handle HTTP error codes**: treat error responses as a normal result and get access to the error in the workflow variables, so you can branch on it with a condition.
* **Output results to use in other action or condition**: save the response. The field below shows the variable to use in the next steps, for example `{{workflow.e021529... | json}}`.

# ${color}[#204a87](Recipe 3: tag products from a metafield)

**Goal:** add a tag based on a product metafield, for example `material-cotton` from the metafield `custom.material`, so you can use the tag in collections and filters.

1. Trigger: **Product added to store**, or **Product updated** with a field to observe.
2. Condition (optional, to skip products without the metafield): **Liquid** > **Liquid result**, with `{{ product.metafields.custom.material }}` and the matcher **is not empty**.
3. Action under **then**: **Add product tags** with the tag:

```
material-{{ product.metafields.custom.material | downcase | replace: " ", "-" }}
```

Replace `custom.material` with the namespace and key of your metafield. You can find them in Shopify **Settings** > **Metafields and metaobjects** > **Products**: open the metafield definition and look at its namespace and key, for example `custom.material`.

In our test, a product with the metafield `test_data.binding_mount` = `met3` got the tag `binding-met3`. Products without the metafield returned an empty value, which is why the condition in step 2 is useful.

To tag the products you already have, build the same steps in a One-Shot workflow: **Fetch resources** > **Products**, then the condition and **Add product tags** under **for each**.

|| To add several tags at once, return them separated by commas, for example with a `for` loop and `{% if forloop.last == false %},{% endif %}`.

# ${color}[#204a87](Recipe 4: win-back campaign for inactive customers)

**Goal:** every day, find customers who haven't ordered for 90 days, send each of them a personal discount code once, and mark them so they don't get it again.

![](https://storage.crisp.chat/users/helpdesk/website/-/3/2/7/1/3271b421d88bd400/win-back-workflow_n916tu.png =800x382)

1. Trigger: **Scheduled time**. Set the start time and click **Add repeat**: every **1** **Days**.
2. Action: **Fetch customers**.
   * Query type: **Segments**, **Advanced**, and in **Edit query**:
`number_of_orders >= 1 AND last_order_date < -90d AND customer_tags NOT CONTAINS 'winback-sent'`
* Optionally add `AND email_subscription_status = 'SUBSCRIBED'` to email only subscribers.
* Check the **Runs counter**: each fetched customer counts as a run.
3. Under **for each**: **Create discount code**.
   * Discount type: **Amount off order**, **Generate random discount code**, **Percentage** `15`.
   * Usage limit `1`. **Customer**: `{{ ``loop.customer.id`` }}`, so only this customer can use the code.
   * **Ends in**: **Within days** `14`.
   * Select **Output results to use in other action or condition**. Copy the variable shown under "Use this code to access as variable", for example `{{loop.workflow.975ce...}}`.

![](https://storage.crisp.chat/users/helpdesk/website/-/3/2/7/1/3271b421d88bd400/discount-output-variable_k8y3er.png =800x382)

4. Next: **Send email**.
   * **Send to:** `{{ loop.customer.defaultEmailAddress.emailAddress }}`
   * **Subject:** `We miss you, {{ loop.customer.firstName }}! Here is 15% off`
   * In the message, insert the code with the copied variable plus `.title`, for example `{{loop.workflow.975ce....title}}`. `.discountValue` returns the discount value.
5. Next: **Add customer tags** with `winback-sent`. Because the query excludes this tag, each customer gets the offer only once.
6. Click **Save**, test with **Run Test** > **Simulate**, then **Turn on**.

There is also a ready-made **win-back** template in **Templates** and [Playbooks](https://help.devit.software/en/article/playbooks-11d0ggg/). It runs monthly, picks customers whose last order was 30–365 days ago, and calculates a bigger discount for customers who have been inactive longer. The **Playbooks** version also provides additional analytics on the revenue generated by the playbook.

# ${color}[#204a87](Liquid cheat sheet)

* Default value: `{{ order.customer.firstName | default: "there" }}`
* Upper or lower case: `{{ product.title | upcase }}`, `{{ product.vendor | downcase }}`
* Replace spaces: `{{ product.title | downcase | replace: " ", "-" }}`
* Format a date: `{{ order.createdAt | date: "%Y-%m-%d" }}`. Use `"now"` to get the current date, for example: `{{ "now" | date: "%Y-%m-%d" }}`.
* Join a list: `{{ product.tags | join: ", " }}`
* Safe JSON value: `{{ product.title | json }}`
* Condition with a number: `{% assign total = order.totalPriceSet.shopMoney.amount | plus: 0 %}{% if total > 100 %}...{% endif %}` (`plus: 0` turns the amount into a number)
* Loop: `{% for line_item in order.lineItems %}{{ line_item.name }}{% endfor %}`
* Metafield: `{{ product.metafields.namespace.key }}`

# ✔️ Next step

Check our articles to learn more about the app's functionality:

* [Components of the workflow](https://help.devit.software/en/article/components-of-the-workflow-1rd4ha2/)
* [Webhooks](https://help.devit.software/en/article/webhooks-1n0gn2z/)
* [React Flow templates](https://help.devit.software/en/article/react-flow-templates-1slouze/)