Warning!

External Content and External Lexpad are complementary features but they serve very different purposes. Please read both documentations very carefully and then choose the right solution for your task. Otherwise you may fall into inefficient design antipatterns that will affect sending speed and may generate excessive network traffic.

You should use External Lexpad when you want to populate message content using complex data structures that are specific for every recipient.

Availability

Currently this feature is only available for messages defined through API (excluding RSSes).

Synopsis

Let's assume that you have online book store.

Every customer in this store:

  • Has corresponding contact in GetResponse (for example you synchronize them using API).
  • Can choose favorite book categories.

Now you want to send message to your contacts containing section with new books in their categories.

At this point you should notice few things:

  • You will apply the same formatting for many elements (books) in array.
  • Every element (book) will have many attributes (author, title, cover type, tags, and so on).
  • Set of those elements (books) will be quite unique for every customer. For example if there are 16 categories in your store then there are 65536 sets of favorite categories possible.

That is the sign, that you should use External Lexpad, not External Content

WebService

First you have to provide endpoint, that will return data structure with new books for every GR contact.

Let's assume it is located at: https://example.com/new_books.json

Communication in both ways uses JSON and your service will get following request:

    {
  "campaign" : { "id" : "ABC123" },
"message" : { "id" : "QWE123" },
"contact" : { "id" : "RTY456" }
}

(IDs in query are the same as returned by API)

Now your webservice should find corresponding customer in book store, check which categories are marked by this customer as favorite, find list of new books in those categories and return them in JSON data structure of your choice:

    {
"new_books" : {
"Science-fiction" : [
{ "title" : "Attack from Mars", "author" : "John Foo"},
{ "title" : "Robots", "author" : "Anna Bar"}
],
"Horror" : [
{ "title" : "Dark forest", "author" : "Peter Baz"}
]
}
}

This structure is called "Lexpad" and serves the same purpose as lexpads in programming languages - provides data for code execution.

Message

Message content can use special mergewords that allows to iterate over provided data structure.

    Hi {{CONTACT "subscriber_first_name"}}

Check out new books that may interest you:

{{LOOP "new_books" "category" "books"}}
In category {{TOPIC "category"}} we have
{{LOOP "books" "book"}}
- {{TOPIC "book" "title"}} written by {{TOPIC "book" "author"}}
{{ENDLOOP}}
{{ENDLOOP}}

Happy reading!

Result

Before message is sent to subscriber GetResponse will send Lexpad request to your webservice and will use received data to execute mergeword logic, producing following message:

    Hi Tom

Check out new books that may interest you:

In category Science-fiction we have
- Attack from Mars written by John Foo
- Robots written by Anna Bar
In category Horror we have
- Dark forest written by Peter Baz

Happy reading!

As you can see External Lexpad feature allows to use more sophisticated template logic. And while most Dynamic Content tags operate on data stored in GetResponse (for example contact name) for LOOP tags you provide data yourself through your webservice. Simple? OK, let's dig into details :)

General rules

  • WebService must respond within 4 seconds.
  • Response cannot be larger than 4KB.
  • Lexpad must be in JSON format using UTF-8 character set.
  • Top level structure must be JSON object. Arrays or bare values are not allowed.

If any of rules above are broken then message won't be sent to contact.

  • It is strictly forbidden to use any formatting directives in Lexpad. Values within must never contain any HTML tags, CSS styles, newlines, etc. Whole formatting must be done on message content level exclusively to speed up sending process and minimize lexpad size and network traffic. It may be tempting to return whole piece of text preformated on webservice side without learning those LOOP and TOPIC constructs, but any detected attempt to do so will result in immediate block of this feature. Preformatting on server side would mean pushing over network the same formatting directives again and again, which is very inefficient and therefore we do not allow it.
  • It is recommended to return JSON in compact form, without pretty formatting.
  • We do not enforce how data structure inside top JSON object should look like. Dynamic Content mergeword execution is fast. Like really, really, really fast. So if your data has deep strucure by its nature do not be afraid to use many levels of nested LOOPs to format the output.

TOPIC tag

This tag prints value available in current scope under given name (first param). It understands Scalars, Arrays and Hashes.

Lexpad:

    {
"greeting" : "Hello",
"subject" : [ "Dog", "World", "Kitty" ],
"punctuation" : { "dot" : ".", "exclamation" : "!", "question" : "?" }
}

Message:

    {{TOPIC "greeting"}} {{TOPIC "subject" "1"}} {{TOPIC "punctuation" "exclamation"}}

Result:

    Hello World !

Explanation:

  • First param is value name.
  • Second param is not present for Scalar values, it is numeric for Array values and it is string for Hash values.
  • Accessing value in a way which do not corresponds with its type will not print such value. For example {{TOPIC "greeting" "foo"}} will be skipped because value under "greeting" key is a Scalar, not a Hash. The same applies to {{TOPIC "subject"}}, which is an Array and expects index to be given.
  • Arrays are enumerated from 0. Index is numeric but still must be quoted like any other param according to general Dynamic Content rules.
  • Arrays can only be indexed in positive, integer, ASCII-only numbers. There are no tricks like counting backwards using negative index, known from some programming languages.
  • It is impossible to use numbers as hash keys.
  • You can not traverse into nested lexpad { "foo" : { "bar" : { "baz" : 123 } } } using single mergeword like {{TOPIC "foo" "bar" "baz"}}. To access deeper values LOOP must be used.

LOOP tag

This tag iterates over Hash and Array structures and binds values to new names.

Iterating over Array

Lexpad:

    {
"cars" : [ "Dodge", "Uaz", "Fiat" ],
}

Message:

    {{LOOP "cars" "car"}}
{{TOPIC "car"}}
{{ENDLOOP}}

Result:

    Dodge
Uaz
Fiat

Explanation:

  • First param is value name.
  • Value is expected to be an Array. Otherwise whole LOOP will be skipped.
  • Second param describes new name that values within Array will be bound to. This "car" name will be available for TOPICs, IFs or nested LOOPs within this LOOP scope (continue reading to encounter those mythical creatures).

Iterating over Hash

Lexpad:

    {
"sounds" : {
"cat" : "meow",
"pig" : "oink",
"dog" : "woof"
}
}

Message:

    {{LOOP "sounds" "pet" "does"}}
{{TOPIC "pet"}} - {{TOPIC "does"}}
{{ENDLOOP}}

Result:

    cat - meow
dog - woof
pig - oink

Explanation:

  • First param is value name.
  • Value is expected to be a Hash. Otherwise whole LOOP will be skipped.
  • Second and third param describe new key and value name that values within Hash will be bound to. Those values are available for TOPICs, IFs or nested LOOPs within this LOOP scope.
  • Hashes are unordered by nature, therefore order from your JSON file will not be preserved. Iteration will go over keys sorted lexicographically.

Advanced stuff

Let's assume you have diet business and you are sending daily diet for each contact. We'll use the same lexpad for next few cases:

Lexpad:

    {
"diet" => {
"Breakfast" : {
"Eggs" : {
"preparation" : "boiled",
"amount" : "2",
"nutrition" : {
"fat" : "25g",
"proteins" : "26g",
"carbs" : "4g"
},
"recipe" : "https://example.com/boiled_eggs.html"
}
},
"Dinner" : {
"Beef" : {
"preparation" : "grilled",
"amount" : "100g",
"nutrition" : {
"proteins" : "30g",
"fat" : "10g"
},
"recipe" : "https://example.com/grilled_burger.html"
},
"Potatoes" : {
"preparation" : "boiled",
"amount" : "100g",
"nutrition" : {
"proteins" : "2g",
"fat" : "1g",
"carbs" : "20g"
},
"recipe" : "https://example.com/boiled_potatoes.html"
}
}
}
}

Nested iteration

It was already shown in Synopsis, but now let's go through the details:

    {{LOOP "diet" "meal" "ingredients"}}
...
{{ENDLOOP}}

This will bind keys in "diet" Hash to "meal" name. So "meal" will be "Breakfest" and "Dinner". Values in "diet" hash will be bound to "ingredients" name and they contain Hash. So to display every ingredient in every meal you have to iterate over "ingredients".

    {{LOOP "diet" "meal" "ingredients"}}
{{LOOP "ingredients" "ingredient_name" "ingredient_attributes"}}
...
{{ENDLOOP}}
{{ENDLOOP}}

You can go as deeply as needed, just be aware of data type (Hash or Array) you are iterating on.

Binding scopes

Now that we have base iteration let's print something useful:

    {{LOOP "diet" "meal" "ingredients"}}
For {{TOPIC "meal"}} prepare:
{{LOOP "ingredients" "ingredient_name" "ingredient_attributes"}}
- {{TOPIC "ingredient_attributes" "amount"}} {{TOPIC "ingredient_attributes" "preparation"}} {{TOPIC "ingredient_name"}}
Enjoy {{TOPIC "meal"}}!
{{ENDLOOP}}
{{ENDLOOP}}

Which will print:

    For Breakfast prepare:
- 2 boiled Eggs
Enjoy Breakfast!
For Dinner prepare:
- 100g grilled Beef
- 100g boiled Potatoes
Enjoy Dinner!

In this example you can see, that:

  • TOPIC behaves exactly the same way, doesn't matter how deep in LOOP structures it is burried. If "ingredient_attributes" in inner most loop is a Hash then you can get value under "amount" key by using {{TOPIC "ingredient_attributes" "amount"}}.
  • Each name assigned in LOOP is also available in subLOOPs. That's why it is possible to use Enjoy {{TOPIC "meal"}} in inner most loop, while "meal" was bound in outer loop.

Re-binding scopes

You can rebind name in inner LOOP.

    {{LOOP "diet" "name" "ingredients"}}
{{LOOP "ingredients" "name" "attributes"}}
{{ENDLOOP}}
{{ENDLOOP}}

Try to avoid it unless you know what you are doing. In this case for example you won't be able to use Enjoy {{TOPIC "name"}} part, because ingredient name in inner LOOP scope masks meal name from outer LOOP.

Clicktracking and conditions

If given name is scalar value then you can use it in IF or LINK tags.

    {{LOOP "diet" "meal" "ingredients"}}
        {{TOPIC "meal"}}:
      {{LOOP "ingredients" "ingredient" "ingredient_attributes"}}
- {{TOPIC "ingredient"}}
{{LOOP "ingredient_attributes" "ingredient_attribute_name" "ingredient_attribute_value"}}
{{IF "(ingredient_attribute_name STRING_EQ 'recipe')"}}
Recipe {{LINK "ingredient_attribute_value"}}
{{ENDIF}}
{{ENDLOOP}}
      {{ENDLOOP}}
    {{ENDLOOP}}

This will print:

    Breakfast:
  - Eggs
Recipe http://getresponse.com/click.html?...
Dinner
  - Beef
Recipe http://getresponse.com/click.html?...
- Potatoes
Recipe http://getresponse.com/click.html?...

And all those links will be visible under message statistics.

One shortcoming that you've probably noticed is that LINK and IF cannot reach into Hashes or Arrays like TOPIC can. They need Scalars, but under "ingredient_attributes" name is a Hash. So to clicktrack value under "recipe" key you have to make another LOOP, inside which "ingredient_attribute_name" and "ingredient_attribute_value" will be Scalars. Then you should filter out keys which you are not interested in using IF and clicktrack desired value using LINK.