DocsUser guides

How to add extra filtering in automations triggered by Events.

Campaigns

When using Events to trigger automations, we rely on the Event generation to happen when all the right circumstances and criteria are met: Simply define the conditions and only send the Event to Custobar when ready, which in turn fires the right message via the right channel with the right content.

However, this is not always possible, right? For example, you might want to send a different Order confirmation message whether the recipient is or isn't a member of your loyalty program, and that information comes from a different system than your webstore. This is where Custobar really shines: We gather all the possible touchpoints, and you can use the unified profile and all the data it includes in your strategy. Let's see how to do that!

A bit of context

Technically, this language is a purely functional programming language, with JSON-compatible syntax. In general, an expression written in this language describes the result of a transformation, based on some parameters in the context where the expression is evaluated. When transforming data from one format to another, i.e., incoming Shopify data to the format used by Custobar, or vice versa, the source data is given in a variable called SRC.

In email automation filter expression, the result is a boolean value (true or false), and the context contains variables EVENT and CUSTOMER, carrying the data of the event triggering the automation, and of the customer associated with the event, respectively. Here is an example of a filter expression that checks if the customer associated with the event is tagged as a bonus-member:

["contains", ["CUSTOMER.tags"], "bonus-member"]

Because the syntax is limited to be compatible with JSON, there are some quirks in how the expression is formed. JSON's list notation is bent to signify if a string is a literal string ("bonus-member" in the example), or a reference to a variable ("CUSTOMER.tags") or to one of the prebuilt functions ("contains"). The rule is: if a string is the first member of a JSON list, it refers to a variable or a function. In other positions, a string is taken as a literal string. So, in a conventional programming language, the above expression would be written as:

contains(CUSTOMER.tags, "bonus-member")

This would be the preferable notation for sure, but the language was developed to be compatible with JSON in order to be easily embedded in JSON configuration without requiring a parser, and that's the explanation for the unusual syntax.

As a convention, the context variables (input to the expressions) are written in UPPERCASE. Please note: in some places, the older convention of passing context with dot-prefixed names is supported: ".customer.tags". However, the more recent "CUSTOMER.tags" style should be used instead. Dot-notation is supported inside the strings for accessing the properties of the objects referenced by the variables: ["CUSTOMER.tags"] accesses the "tags" property of the object in CUSTOMER variable. The language supports most of the basic programming constructs for composing expressions out of other expressions, and a number of built-in functions for manipulating data.

And, or, not

Basic boolean constructs are supported: Not inverts a boolean value. For example, only match customers that don't have email permission:

["not", ["CUSTOMER.can_email"]]

And results in true only if all of the subexpressions result in a truthy value. Only match a customer with both email and sms permission on:

["and", ["CUSTOMER.can_email"], ["CUSTOMER.can_sms"]]

Or gives true if any of the subexpressions is true:

["or", ["CUSTOMER.can_email"], ["CUSTOMER.can_sms"]]

There can be any number of subexpressions in and and or:

["and", 
     ["CUSTOMER.can_email"],
     ["contains", ["CUSTOMER.tags"], "bonus-member"],
     ["equals", ["EVENT.subject"], "bonus"]
]

This expression refers to both the event and the associated customer. It also uses another built-in function "equals" that compares its two arguments for equality.

If-then-else

Here's an example of an if expression that selects the filter based on the language setting of the customer:

["equals", 
    ["EVENT.subject"],
    ["if", 
        ["equals", ["CUSTOMER.language"], "fi"],
        "tervetuloa",
        "welcome"
    ]
]

The expression compares the subject of the events to a string that is based on customer's language: "tervetuloa" for Finnish customers and "welcome" for everyone else. The JSON syntax of the if-then-else expression is a four element list, with the first element being "if", the second being the condition to match, third one being the then value that is chosen if the condition evaluates to true, and the fourth one being the else value that is chose otherwise:

["if", CONDITION, THEN, ELSE]

It's possible to omit the else expression, in which case the if expression will evaluate to a null value if there is no match.

JSON objects

JSON objects can be built simply by defining an object in JSON. For example, this object describing an event evaluates to itself, as the values are simple strings:

{
    "type": "BROWSE",
    "customer_id": "123901",
    "product_id": "DD1209EF"
}

JSON objects are not really used in filters, but the data transformations usually are described using them. For example, this simplified Loyaltylion-to-Custobar transformation picks some fields from the incoming Loyaltylion data into a Custobar customer update:

{
     "email": ["SRC.email"],
     "birth_date": ["SRC.birthday"],
	"loyaltylion_id": ["SRC.id"],
     "loyaltylion_merchant_id": ["SRC.merchant_id"]    
 }

However, by default, propeties whose values evaluate to null are omitted from the resulting object. So if the SRC object for the transformation above is:

{
    "id": 12345,
    "merchant_id": null,
    "email": "boss@custobar.com"  
}

the result of the transformation will be:

{
    "email": "boss@custobar.com",
    "loyaltylion_id": 12345
}

Merchant id is null and birth_date is missing altogether, so neither is included in the result. For the cases where the null values should in fact be included, there is a special "keep_nulls" form that the JSON object can be wrapped in:

["keep_nulls", {
     "email": ["SRC.email"],
     "birth_date": ["SRC.birthday"],
     "loyaltylion_id": ["SRC.id"],
     "loyaltylion_merchant_id": ["SRC.merchant_id"]
}]

Transforming the same data with this transform results in:

{
    "email": "test@example.com",
    "birth_date": null,
    "loyaltylion_id": 12345,
    "loyaltylion_merchant_id": null
}

It's often useful to merge data from several sources to a single object. This can be done with the combine function. It combines all the argument objects it is given. A common use case is to augment the data to be transformed with some extra fields:

["combine", ["SRC"], {"shop_id": "kouvola"}]

This transforms the source data by keeping everything, but adding a shop_id (or overriding the existing shop_id!)

Lists and for expressions

Because the JSON list syntax is given the special meaning, if you want to construct a list, you need to use the list function instead:

{
     "customer_id": ["SRC.id"],
     "tags": ["list", "ONE", "TWO", "THREE"]
}

The tags property of the result will be ["ONE", "TWO", "THREE"]. Often, lists are built by iterating (mapping or filtering) over some source data. This can be done by using a for expression. The first argument to the for expression is a variable name, the second argument is an expression that produces a list, and the last argument is an expression that is evaluated for each element of the argument list, binding the element to the named variable. The result is the list of the results of evaluating that expression for each element, filtering out any null values. Example: prefixing each tag with "PFX-" in the incoming data:

{
    "external_id": ["SRC.id"],
    "tags": ["for", "TAG", ["SRC.tags"], ["catenate", "PFX-", ["TAG"]]]
}

(catenate function concatenates it's argument strings into a single string) Example: filter for an automation that only triggers if a customer has at least one tag prefixes with "PFX-":

["any", ["for", "TAG", ["CUSTOMER.tags"], ["startswith", ["TAG"], "PFX-"]]]

This one uses the any function to combine the resulting list. any results in true if at least one of the elements in the argument list is true.

Let expressions

The result of an expression can be given a name using a let expression. This is useful for avoiding repetiton if the result is needed several times, and sometimes naming the subexpressions just makes the code easier to understand. The let expression has similar form to the for expression, but the variable is evaluated just once. Here's an example of binding "SRC.customer.tags" to "TAGS" in order to avoid repeating it in a filter:

["let", "TAGS", ["SRC.customer.tags"], 
     ["or", 
        ["contains", ["TAGS"], "gold"],
        ["contains", ["TAGS"], "silver"],
        ["contains", ["TAGS"], "bronze"]  
     ]
]

Here's a more complex example, taken from the Shopify product transformations:

{
     "shopify_options": 
         ["for", "POS", ["list", 1, 2, 3],
              ["let", 
                  "OPT", ["first", 
                      ["for", "OPT", ["SRC.options"],
                          ["if", ["equal", ["OPT.position"], ["POS"]], ["OPT"]]
                      ]
                  ],
                  ["if", ["OPT"], {
                       "value": ["get", ["catenate", "option", ["POS"]], ["VARIANT"]],
                       "name": ["OPT.name"]
                  }]
             ]
         ] 
}

This transformation picks the "option values" from the product variants. It loops over the list of positions [1, 2, 3] and for each position, searches the "options" list in the incoming Shopify data for the option with that position, and then binds that option to OPT variable, before continuing with the transformation. If the incoming product data contains the option definitions like this:

{
    "options": [
        {"position": 1, "name": "weight"},
        {"position": 2, "name": "color"}
    ]
}

And the VARIANT has option values defined:

{
    "option1": "13.12kg",
    "option2": "red"
}

The result of the transformation is:

{
    "shopify_options": [
         {"name": "weight", "value": "13.12kg"},
         {"name": "color", "value": "red}
    ]
}

(first function picks the first element of a list. get function extracts a property from an object. ["get", "name", ["SRC"]] is equivalent to "SRC.name".

So how do I add this filtering?

Well that's quite simple once you know where to look!

  • Open the "activation" page for your automation, where have inserted the trigger Event type
  • Click on the "..." button

  • Toggle the "Show field for additional precondition query" option

  • Scroll back up to see the field is now available to use!

And that's all there is to it! If you have any questions, please reach out to support@custobar.com and we will assist you.