Although the alerts provided by SFMC on Triggered Send issues and errors is very helpful, it can take many hours before they show up, which can sometimes lead to significant backups in your processes.
To get around this delay, I have come up with a monitoring automation that will give almost ‘real time’ alerts on your Triggers based on Queue limits you set for them.
For this version I did not include a review of current status, but this is fairly easy to implement with a couple adjustments if you feel necessary. My assumption is that if you need real time monitoring then it is a fairly heavily trafficked Triggered Email, so the queue should build up quickly – alerting you to a failure.
The other great aspect of this is if the Triggered Email does not actually error but enters what I call ‘Hung Status’ which basically means it still shows as running, but nothing is sent and everything queues up. As the status shows running, it would not show up on any status based alerts and you would not know of this issue.
What is this?
The below script will loop through a defined array of Triggered Email Send Definitions (TSDs) checking if the current queue count is within expected range and then if it exceeds the defined amount, it will shoot a triggered email to you – alerting you of each TSD that has exceeded the allowed limits. You will need to use the CustomerKey of the Triggers inside this array.
To allow for this loop to not time out, I have wrapped it inside of my ‘timer’ do/while. You have two choices for this script – to create an array of Triggers you want to monitor, or to pull all existing triggers to monitor. Either of these can result in different run times, so I would recommend timing the single run to see how long it runs for and then adjust the timeout value accordingly.
The huge benefit to setting it up this way is that you can then put two Script Activities of this inside of an automation, schedule it to be hourly and you will then have a continuously running alert as each Script Activity should fill most, if not all of the hour and then it runs again.
As a note though, this will require a new Trigger Email to be set up to grab this info and be sent out. This should be fairly simple as you can just output the tsdList
and tsdQueue
variable (passed in TSD WSProxy call) inside the email.
The Functions
Inside this script, I am including 3 functions to make life easier when coding it out. The first function is the one that will target the ‘TriggeredSendDefinitoin’ Object via WSProxy to return a list of all the CustomerKeys of your TSDs inside your BU.
I do also have a passable MID available so if in the future you want to re-purpose this script to target multiple BUs, it is mostly set up for this already. (Note: This function is unnecessary if you choose to use a manually defined array instead)
function getTSDKeys(mid) { var prox = new Script.Util.WSProxy(); /* Set ClientID */ if (mid) { prox.setClientId({ "ID": mid }); //Impersonates the BU } var cols = ["CustomerKey", "TriggeredSendStatus"]; var res = prox.retrieve("TriggeredSendDefinition", cols, filter); var results = res return results; }
Note, this function asks for a passed parameter of mid
, but it is not necessary if you are doing on current BU only.
The second function will target the ‘TriggeredSendSummary’ Object via WSProxy to return the Queue value for each of your TSDs. This one will require a passed parameter of the TSD’s CustomerKey in order to provide results.
function getTSDQueue(customerKey,mid) { var prox = new Script.Util.WSProxy(); /* Set ClientID */ if (mid) { prox.setClientId({ "ID": mid }); //Impersonates the BU } var cols = ["CustomerKey","Queued"]; var filter = { Property: "CustomerKey", SimpleOperator: "Equals", Value: customerKey }; var res = prox.retrieve("TriggeredSendSummary", cols, filter); var queue = res.Results[0].Queued return queue; }
You will note that this one also has the `mid` option built in, but as previously it is not necessary to be included for this to function on current BU.
The third and final function is the one to build and send the Alert Triggered Email to let your team know that there is a failure. Please do note that this Triggered Email will need to be created prior to this script activity. I will get more into that later in the article.
function sendTSDAlert(alertStr,queueAlertStr,emailAddress,subKey) { var proxy = new Script.Util.WSProxy(); var customerKey = "TSD_AlertTrigger"; //key of your TSD Alert Email var name = "TSD_AlertTrigger"; var ts= { TriggeredSendDefinition: {CustomerKey: customerKey, Name: name} ,Subscribers: [ { EmailAddress: emailAddress , SubscriberKey: subKey , Attributes: [ { Name: 'tsdList', Value: alertStr }, { Name: 'tsdQueue', Value: queueAlertStr } ] }] }; var res = proxy.createItem("TriggeredSend", ts); return res; }
The Triggered Email
Before getting further into the script, I want to take a minute to give some sample code for an alert email to be used for the above triggered email.
This code will retrieve the TSD list and Queue number provided via the above Function and then iterate through it to produce a list of those TSDs that have gone above the selected Queue limit.
<!doctype HTML> <html> <head> <style> </style> </head> <body> %%[ set @tsdList = AttributeValue("tsdList") set @tsdQueue = AttributeValue("tsdQueue") set @tsdListRows = BuildRowsetFromString(@tsdList,",") set @tsdQueueRows = BuildRowsetFromString(@tsdQueue,",") ]%% The following Triggers have exceed queue limits:<br> %%[FOR @i=1 TO Rowcount(@tsdListRows) DO SET @RowList = Row(@tsdListRows,@i) SET @RowQueue = Row(@tsdQueueRows,@i) if @i > 1 THEN ]%% <hr> %%[ endif ]%% <br> Trigger CustomerKey: %%=Substring(TRIM(Field(@RowList,1)),2,SUBTRACT(Length(TRIM(Field(@RowList,1))),2))=%%<br> Trigger Queue Count: %%=Field(@RowQueue,1)=%%<br> <br> %%[ NEXT @i ]%% </body> </html>
Using a copy/paste of the above (note I do not have any of the CAN-SPAM required items in it, which depending on your account you may need to add in) inside an email in Email Studio, you should then be ready to build out your Alert Trigger.
Here is the documentation on setting up a Triggered Send Definition in SFMC. This should help you get everything ready to continue forward in this script. As a note, I would recommend naming and setting the customerkey of your Alert Trigger as TSD_AlertTrigger
in order to utilize the default script I have – or I would make sure to adjust function 3 to use your custom name/customerkey.
The Script
I am going to break out a couple pieces of the script to describe what they do, as when it is pushed all together it can appear a bit confusing.
First part we are going to explore is my Time Limit script part to help keep the Script Activity from timing out. As I already have an article (linked above) expressing the details, I will keep this short.
var now = new Date(); var start = now.getTime(); var timeOut = 1680000; //28 minutes //60000 milliseconds in a minute do { //your JS code } while((new Date().getTime() - start) < timeOut)
Basically in this code you will set the top time limit you want your Script to run for before the loop breaks and your Script ends (timeOut
var). From there the do/while loop will keep checking current time minus start time until the milliseconds returned reach the timeOut limit you set.
Next section I have checks to see if you want to grab all triggers in the BU, or a specified array of Triggers. One thing to note is that you can only set a custom Queue limit on a specified array of triggers inside my current version.
Basically at the top you would have a variable that will act as a ‘switch’ to turn on/off allTriggers or custom array. From there an if statement will set the required variables accordingly
if (allTriggers) { var tsdArray = getTSDKeys(mid); var length = tsdArray.Results.length; } else { var tsdArray = ["TriggerA","TriggerB","TriggerC","TriggerD","TriggerE"] var length = tsdArray.length; var maxQueueArray = [500,500,500,500,500]; //Enter max queue here. Can make an array as well, if different maxes per TSD } if (!maxQueueArray || maxQueueArray.length == 0) { var maxQueueDefault = 500; //Default for if not using maxQueueArray }
Inside the for loop reviewing the Send Queues against the limits, I also have a variable that checks for if the Send ‘already failed’. This way you don’t get an email alert every minute about the same Trigger failing – you would, at most, get an alert every 30 mins. This is handled via an indexing of the ‘alertArray’ variable to see if it already exists there.
The Full Script
<script runat=server> Platform.Load("Core","1.1.1"); var mid = ''; var dev = 0; var allTriggers = 1; var alertEmailAddress = '[email protected]'; var alertSubKey = '[email protected]'; var now = new Date(); var start = now.getTime(); var timeOut = 1680000; //28 minutes //60000 milliseconds in a minute if (allTriggers) { var tsdArray = getTSDKeys(mid); var length = tsdArray.Results.length; } else { var tsdArray = ["TriggerA","TriggerB","TriggerC","TriggerD","TriggerE"] var length = tsdArray.length; var maxQueueArray = [500,500,500,500,500]; //Enter max queue here. Can make an array as well, if different maxes per TSD } if (!maxQueueArray || maxQueueArray.length == 0) { var maxQueueDefault = 500; //Default for if not using maxQueueArray } if (dev) { timeOut = 10000; maxQueueArray = []; maxQueueDefault = 5; } var alertArray = [] var queueAlertArray = [] do { var failures = 0; for (i=0; i < length; i++) { if(allTriggers) { var customerKey = tsdArray.Results[i].CustomerKey } else { var customerKey = tsdArray[i] } var queued = getTSDQueue(customerKey); var alreadyFail = Stringify(alertArray).indexOf(customerKey); dev ? Write('<hr><br>') : ''; dev ? Write('CustomerKey: ' + customerKey + '<br>') : ''; dev ? Write('Queue: ' + queued + '<br>') : ''; dev ? Write('RunTime: ' + (new Date().getTime() - start) + '<br>') : ''; var queueArrLength = maxQueueArray.length; // changes maxQueue to array value if exist and equal to i if (maxQueueArray.length > 0 && maxQueueArray.length <= i) { var maxQueue = maxQueueArray[i]; } else { var maxQueue = maxQueueDefault; } if (queued > maxQueue) { if (alreadyFail < 1) { dev ? Write('<span style="color:red; font-weight:bold;">Queue Failure</span><br>') : ''; alertArray.push(customerKey) queueAlertArray.push(queued) failures += 1 } } } dev ? Write('<br>FAILURES: ' + failures + '<br><br><br>') : ''; if (failures > 0) { var alertStr = Stringify(alertArray).replace('[','').replace(']',''); var queueAlertStr = Stringify(queueAlertArray).replace('[','').replace(']',''); var alertTrigger = sendTSDAlert(alertStr,queueAlertStr,alertEmailAddress,alertSubKey) dev ? Write('<br>' + Stringify(alertTrigger) + '<br>') : ''; } } while((new Date().getTime() - start) < timeOut) /************************* FUNCTION LIST ************************/ function getTSDKeys(mid) { var prox = new Script.Util.WSProxy(); /* Set ClientID */ if (mid) { prox.setClientId({ "ID": mid }); //Impersonates the BU } var cols = ["CustomerKey", "TriggeredSendStatus"]; var res = prox.retrieve("TriggeredSendDefinition", cols, filter); var results = res return results; } function getTSDQueue(customerKey) { var prox = new Script.Util.WSProxy(); /* Set ClientID */ if (mid) { prox.setClientId({ "ID": mid }); //Impersonates the BU } var cols = ["CustomerKey","Queued"]; var filter = { Property: "CustomerKey", SimpleOperator: "Equals", Value: customerKey }; var res = prox.retrieve("TriggeredSendSummary", cols, filter); var queue = res.Results[0].Queued return queue; } function sendTSDAlert(alertStr,queueAlertStr,emailAddress,subKey) { var proxy = new Script.Util.WSProxy(); var customerKey = "TSD_AlertTrigger"; var name = "TSD_AlertTrigger"; var ts= { TriggeredSendDefinition: {CustomerKey: customerKey, Name: name} ,Subscribers: [ { EmailAddress: emailAddress , SubscriberKey: subKey , Attributes: [ { Name: 'tsdList', Value: alertStr }, { Name: 'tsdQueue', Value: queueAlertStr } ] }] }; var res = proxy.createItem("TriggeredSend", ts); return res; } </script>
To help with setup, I have left in my ‘dev’ snippets inside this script as it will help verify (via Cloudpage) that it is functioning as well as provide a good average run time value for you to help gauge the timeout variable. Do note if you have thousands of Triggers in your account, this may go beyond the allowed load time in a Cloudpage, in which case you would instead need to run this in a script activity and push the info into a DE or content area in order to reference it.
Now you just add this into an Automation as a Script Activity and place it in there twice in two different steps. Then set the automation to hourly, and you now have a continuously running real time trigger queue alert set up in your BU.
Nice! I like your short conditionals to display debug messages.
Hello, Thank you so much for your Script. This will be very helpful for us. But I have a question, is there a way can we get the subscriber key for the errored record?
Wonderful!
Thank you so much for this! I enhanced it a little, adding the actual trigger name in since we have an 8 year old instance and a lot of the trigger customerkeys were auto-generated.