Debugging & Error Handling

To catch, or not to catch, that is debugging.

When you start working with programmatic languages in Salesforce Marketing Cloud, you will quickly become close friends with Error 500 page. And if you beat it and go forward with the code and official documentation, you will fall in love with unexpected errors in functions and API responses. How to survive those?


SSJS Testing Ground

Before diving deep into errors, let's talk where to write the SSJS. The language is useful in many places within the Marketing Cloud ecosystem. Among else in:

  • Cloud Pages (Web Studio)
  • Code Resources (Web Studio)
  • Content Blocks (Content Builder)
  • Emails and other communication (Email Studio / Mobile Studio / Content Builder)
  • Script Activities (Automation Studio)

In all of the above cases, you might write more complex logic that will be error-prone and in need of debugging. However, only a few of those are really good to test your code, as only some of them provide access to methods mentioned later in the article.

Cloud Page

The most popular recommendation is Cloud Page - write or paste the SSJS code there, publish and check whether it is working correctly. It is a great way, as it allows you to easily leverage SSJS for backend and HTML/CSS/JavaScript for frontend.

It has, however, two flaws that, in some scenarios, might guide you to a different solution.

  1. Cost. Each view of the Cloud Page costs one Super Message. During debugging, you might hit quite a lot of those. And those tests - across your team and over time - stack up.
  2. Speed and context. Each time you want to republish updated Cloud Page you have to go through the Preview window (that load the whole code in POST method context), confirm it and go to the page via URL to see the GET method context.

JSON Code Resource

When I want to go around those issues, I work in JSON Code Resource (also available in Web Studio). It generates a link you can use to check whether everything works, just like a Cloud Page. However, page views are free. There is also no preview allowing for a faster save-reload cycle.

The cons? You won't be able to use any frontend (neither HTML nor JavaScript), which might be a deal-breaker in some scenarios.

So where?

If you needCloud PageJSON Code Resource
SSJS
HTML/CSS/JS
No Cost
Quick Save
Email Studio

Some also use Email Studio for testing various scripts, but I recommend against it. It is slower than Code Resource and doesn't support SSJS Core Library. And even if you don't need it for your script, it is handy for debugging.


500 - Internal Server Error

The first type of error you might encounter is the dreaded 500 Error that you see right after trying to run your code. Reason? The backend code (SSJS/AMPScript) is invalid. There might be a few reasons for this. The most popular are:

  1. Typo in SSJS Function Name
  2. Unclosed or wrongly closed Bracket
  3. Use of JavaScript feature that is not available in SSJS (there is quite a lot of those...)
  4. Lack of declaration (in most cases either missing variable declaration, Platform.Load("Core","1"); or var soap = new Script.Util.WSProxy(); while using it later in the code)

How to deal with those issues? Apart from just reading through your code line by line, there are two faster solutions.

Divide and Conquer

The first one can be done directly in the Marketing Cloud but is a bit of a brute-force approach. You block half of your code from running by enclosing it in a multiline comment (/* here goes the code */) and check again. If it works, you have half of the SSJS validated (from the 500 error perspective). If not, you split the remaining code in half and comment it out. Rinse, repeat. This way, you can quite quickly find the few lines that are the source of the issue and focus on validating them.

SSJS Linting

For many issues leading to 500 error, there is an even faster solution, but it requires some pre-work:

  1. You need to have Visual Studio Code with ESLint extension installed.
  2. You need to have NodeJS installed.
  3. You need to have a folder where you will store your SSJS code in files with .ssjs file extension.
  4. You need to install the excellent ESLing configuration for SSJS made by Joern Berkefeld. To do this, run in terminal npm i -D eslint eslint-config-ssjs for the SSJS folder.

Once done, you have automatic SSJS code validation (again, only from the perspective of 500 error - but it is still worth it).

There is also much more basic and less helpful, but built-in solution that can validate your SSJS code directly in the Marketing Cloud. Script Activity in Automation Studio. After adding your code there and clicking Validate Syntax button, it will check it against some basic rules. However, I recommend going with the ESLint. It provides much more value and is faster once configured.

What about Try/Catch?

There will be a separate section about Try/Catch. It does not work for issues leading to 500 Error.


Write the Error down

You got past the 500 error. Great! The script is still not outputting what you wanted? It is time to check what else might be wrong. The easiest way is to use the Write function. It shows whatever you pass in it to the frontend view. It is especially useful as SSJS does not support console.log due to its server-side nature (a little trick to make it work is below).

To use this function just add Write('This will be visible on the website') between <script runat="server"> Platform.Load('Core','1'); and </script>.

Of course, in real life, you will probably want to use it for variables:

<script runat="server">
Platform.Load('Core','1');
var response = HTTP.Get('http://www.example.com');
Write('Response from example.com: ' + response + '<br><br>');
</script>

As you can see in the code snippet above I have more than just response variable within my Write. The string before describes what is printed (useful especially if you use multiple Writes in your code). The one after - <br><br> - will separete it from the rest of the website content (however, as it is HTML, it won't work in JSON Code Resource. You can use \n\n instead for the same outcome). I highly recommend this approach.

Few things to remember
  1. Write is Core Library function, so you need to load it first in your script with Platform.Load('Core','1');.
  2. As all Core Library functions, it will not function in Emails or SMS.
  3. If the variable you want to output is an object, you need to parse it to a string using Stringify(response).
  4. If you don't want to load Core Library use Platform versions of those functions: Platform.Response.Write() and Platform.Function.Stringify().

Debugging Variable

Whenever you are creating a script for a long-term, it is a good idea to keep your debugging Write functions. There might be new requirements. Data sources change. Marketing Cloud too. And sometimes those things might break your code.

Of course, keeping the Writes as above is a no-go. You don't want your customers to see those. But there is a neat little trick to eat the cake and have it too:

<script runat="server">
Platform.Load('Core','1');
var debugging = true;
var response = HTTP.Get('http://www.example.com');
if (debugging) {
Write('Response from example.com: ' + response + '<br><br>');
}
</script>

I added two things:

  1. There is var debugging = true; near the top. When debugging, keep it on true. When you publish it for production - change the value to false.
  2. The Write is within the if block. It now works only if the debugging variable equals true.

Console Logging

I mentioned before that SSJS does not support console.log as it is executing on a server. There is, however, a workaround, that might bring the SSJS values to your console. SSJS personalization strings passed to your JavaScript can help:

console.log('Response from example.com' + '<ctrl:var name=response />')
Passing Objects from SSJS to JS

If the variable you want to pass to JavaScript is an SSJS Object, you will need to create a parsed variable to make it work:

  1. In SSJS create var parsedResponse = Stringify(response);
  2. In JS change the personalization to console.log(<ctrl:var name=parsedResponse />) (notice different name value and lack of quotes around personalization string)

Try to Catch the Error

Using Write is a great and simple solution, but it will work only if the code runs correctly. And sometimes it won't. For those cases, add a Try/Catch block. Check the Response tab below to compare the difference between standard Write(response) and writing the caught error.

GET request to invalid URL within Try/Catch block
<script runat="server">
Platform.Load('Core','1');
var debugging = true;
// Try/Catch Block with error Write
try {
var response = HTTP.Get('http://www.example.c');
} catch (error) {
if (debugging) {
Write('Error caught: ' + Stringify(error) + '<br><br>')
}
}
// Standard Write
if (debugging) {
Write('Response from example.com: ' + response + '<br><br>');
}
</script>

Try/Catch is especially useful for SSJS Functions that are calling out-of-page data. Marketing Cloud Data Extensions, Salesforce Objects or even data sources from outside Salesforce environment. It also will help when you are handling responses from such sources. For example, trying to get value from a nested object.

You should know

Try/Catch block in SSJS has separate scope. It means that any variables declared within the block won't be accessible from outside. Neither in the other parts of SSJS nor by personalization strings.

There is a way to overcome this limitation. Declare the variables you want to use globally before the try and just modify their values within the block:

<script runat="server">
Platform.Load('Core','1');
var response;
try {
response = HTTP.Get('http://www.example.com');
} catch (error) {
// Error handling logic
}
</script>

Error Logging

Once you debug your code using the above methods, it is still a good idea to program defensively and leverage the Try/Catch block even on the production environment. It will allow you to handle the errors for the customers and to log the errors to a data extension (it will provide you with a history of problems along with a description of what happened). You can even build something more sophisticated like automatically create a bug ticket in JIRA.

Logging to Data Extension

The easiest way to log your errors is to leverage Marketing Cloud Data Extensions.

My recommendation is to create one Data Extension that will capture all the errors from the whole instance. It will make it much easier and faster to check whether there are any new issues.

Here you can find sample setup:

Column NamePrimary KeyLengthTypeNullableDefault Value
idYes50TextNo
errorSource100TextNo
errorMessage2000TextYes
errorDescription2000TextYes
errorDateDateYesCurrent Date

If you have multiple Business Units on your instance, add it as a Shared Data Extension.

It shouldn't be sendable or testable, but consider adding retention period. Align it with the internal process for checking it for new issues. Seven days per record is a good starting point.

Global Error Catching

Instead of writing Try/Catch blocks for every potentially risky function, you might create just one block that will capture whole code. You can even use it to catch errors in your AMPScript, as described by Zuzanna Jarczyńska.

This approach might make your code shorter and easier to read, but will limit your control over specific handling for various scenarios.

You Should Know

Any kind of redirect within your try block is recognized as an error and caught. If you need to use a redirect, don't use global Try/Catch approach and instead leverage it only for potentially problematic code.

You can also leverage one of the approaches mentioned by Gortonington in his article on Try/Catch. It might be especially useful, when you want to leverage redirect as a form of handling the error for the customer.


Error Handler Function

Once you start implementing the solutions mentioned above, it might get quite repetitive to add the same lines for conditional Write, log to Data Extension, et cetera.

You can solve it by creating signle Error Handler function and just calling it wherever it is needed:

<script runat="server">
Platform.Load('Core','1');
var debugging = true;
function handleError(error) {
if (debugging) {
Write(Stringify(error))
} else {
// Remember that if your Logging Data Extension is in Shared Folder, the name must be prefixed with "ENT."
Platform.Function.InsertData('Data_Extension_Name', ['id', 'errorSource', 'errorMessage', 'errorDescription'], [GUID(), 'Name of the place where the script runs', Stringify(error.message), Stringify(error.description)]);
}
}
try {
var response = HTTP.Get('http://www.example.c');
} catch (error) {
handleError(error);
}
</script>

Whenever Try/Catch block finds an error, the above snippet will check the value of debugging variable. If it is equal to true, it will just Write the error to the page to make it easier to debug quickly. If it is false, it will add the error to the Data Extension instead.

Most of the Marketing Cloud Errors are objects with two keys: message and description. Putting those elements into separate columns of the Data Extension make it easier to read the log.

Using Error Handler Function is useful especially when you are triggering it multiple times or when you have elaborate logic for handling (for example API calls to external systems for bug tracking).

You Should Know

You can also create your custom error, by passing a custom object to the handleErrror function:

handleError({message: 'Custom Error Message', description: 'Custom Error Description'});

There is also a new solution - SSJS Lib - created by email360. Among other tools, they support console logging and simplified debugging. You can read more about the implementation here.


Sum Up

If you have any problems with your SSJS code:

  1. Test it in Cloud Page or JSON Code Resource 🔗
  2. Overcome 500 Error with linting or divide & conquer methodology 🔗
  3. Use Write function to understand what happens with your variables during execution 🔗
  4. Use Try/Catch block to understand better what is the reason for errors you encounter 🔗
  5. Save errors from your production code to easily track and solve the problems 🔗
  6. Simplify the approach with custom Error Handler Function 🔗
Last updated on by Mateusz Dąbrowski