You updated 100 email templates. Content blocks refreshed, subject lines approved, everything signed off. Then someone asks whether the Triggered Sends are actually picking up the new content. And you realize every single one still needs to be manually paused, republished, and restarted in Email Studio. One by one.
We ran into this on a client account where the marketing team had updated 80 transactional email templates before a major campaign launch. Nobody had told them that refreshing the email in Content Builder was not enough. The live Triggered Send Definitions were still serving the old cached version and would keep doing so until someone manually refreshed each one. This is one of those SFMC maintenance tasks that looks minor until you are actually sitting in front of it.
This guide covers why the refresh is necessary, what sequence the SOAP API expects, and the WSProxy script we now run as a standard step after any large template update — across the full folder in one automation, with a log DE so we know exactly what happened.
Why Editing the Email in Content Builder Is Not Enough
When you update an email asset in Content Builder — even one that is actively linked to a running Triggered Send Definition — the TSD does not pick up the change automatically. SFMC caches the email content at the point the TSD was last published. Every trigger that fires against that definition continues delivering the cached version until you force a refresh.
The manual fix is three steps per TSD: pause it, publish it, restart it. For a handful of TSDs that takes a few minutes. For 80, it is the better part of a morning and it introduces a real error risk. Someone forgets one, that triggered email quietly serves stale content, and nobody catches it until a subscriber reports it.
RefreshContent: true SOAP API property is the programmatic equivalent of clicking Publish in the UI. It forces the TSD to re-read the email, resolve all content block references, and create a new active job. All future triggers use this new job going forward.
One thing that catches people out when scripting this: the sequence matters and the API enforces it. You must set the TSD to Inactive before you can push RefreshContent: true. Trying to refresh an Active TSD without pausing first will fail, usually silently. The correct order is always Inactive, then refresh, then back to Active.
The Script
We use WSProxy for this rather than the Core Library’s TriggeredSend.Init() approach. The Core Library works for one or two TSDs but it is not suited for loops over large sets — more overhead per call, less control over errors, no clean way to log results. WSProxy talks directly to the SOAP API, handles arrays natively, and lets you write every outcome to a Data Extension so you have a full audit trail after each run.
Before running, create a Data Extension called TSD_Refresh_Log with this schema:
| Field Name | Type | Length | Notes |
|---|---|---|---|
| RunID PK | Text | 50 | Timestamp plus index to identify the run |
| CustomerKey | Text | 200 | TSD external key |
| TSDName | Text | 200 | Friendly name for readability |
| PauseStatus | Text | 50 | OK or ERROR |
| PublishStatus | Text | 50 | OK or ERROR |
| StartStatus | Text | 50 | OK or ERROR |
| ErrorDetail | Text | 4000 | Full error message if any step fails |
| RefreshedAt | Date | Timestamp of this record |
SSJS Script Activity — Bulk TSD Refresh with Logging
<script runat="server"> Platform.Load("core", "1"); var prox = new Script.Util.WSProxy(); var logDE = "TSD_Refresh_Log"; var runID = Now().toString().replace(/\s/g, "_"); var tsdList = [ { "key": "TSD-CUSTOMER-KEY-001", "name": "Welcome Email" }, { "key": "TSD-CUSTOMER-KEY-002", "name": "Order Confirmation" }, { "key": "TSD-CUSTOMER-KEY-003", "name": "Password Reset" } ]; for (var i = 0; i < tsdList.length; i++) { var cKey = tsdList[i].key; var cName = tsdList[i].name; var pauseStatus = "SKIP"; var pubStatus = "SKIP"; var startStatus = "SKIP"; var errDetail = ""; try { var pauseResult = prox.updateItem("TriggeredSendDefinition", { "CustomerKey": cKey, "TriggeredSendStatus": "Inactive" }); pauseStatus = pauseResult.Status; var pubResult = prox.updateItem("TriggeredSendDefinition", { "CustomerKey": cKey, "RefreshContent": true }); pubStatus = pubResult.Status; var startResult = prox.updateItem("TriggeredSendDefinition", { "CustomerKey": cKey, "TriggeredSendStatus": "Active" }); startStatus = startResult.Status; } catch(e) { errDetail = Stringify(e); } var de = DataExtension.Init(logDE); de.Rows.Add({ "RunID": runID + "_" + i, "CustomerKey": cKey, "TSDName": cName, "PauseStatus": pauseStatus, "PublishStatus": pubStatus, "StartStatus": startStatus, "ErrorDetail": errDetail, "RefreshedAt": Now() }); } </script>
When the TSD List Is Too Large to Maintain Manually
If your org has TSDs spread across multiple folders and keeping a hand-maintained CustomerKey list is not realistic, drive the script from a folder retrieval instead. The version below retrieves all active TSDs under a given Category ID and refreshes every one it finds.
SSJS Script Activity — Folder-Based Bulk Refresh
<script runat="server"> Platform.Load("core", "1"); var prox = new Script.Util.WSProxy(); var logDE = "TSD_Refresh_Log"; var runID = Now().toString().replace(/\s/g, "_"); var folderID = XXXXXX; var cols = ["CustomerKey", "Name", "TriggeredSendStatus"]; var filter = { "Property": "CategoryID", "SimpleOperator": "equals", "Value": folderID }; var results = prox.retrieve("TriggeredSendDefinition", cols, filter); if (results && results.Results) { for (var i = 0; i < results.Results.length; i++) { var tsd = results.Results[i]; var cKey = tsd.CustomerKey; var cName = tsd.Name; if (tsd.TriggeredSendStatus !== "Active") { continue; } var pauseSt = "SKIP", pubSt = "SKIP", startSt = "SKIP", err = ""; try { prox.updateItem("TriggeredSendDefinition", { "CustomerKey": cKey, "TriggeredSendStatus": "Inactive" }); pauseSt = "OK"; prox.updateItem("TriggeredSendDefinition", { "CustomerKey": cKey, "RefreshContent": true }); pubSt = "OK"; prox.updateItem("TriggeredSendDefinition", { "CustomerKey": cKey, "TriggeredSendStatus": "Active" }); startSt = "OK"; } catch(e) { err = Stringify(e); } var de = DataExtension.Init(logDE); de.Rows.Add({ "RunID": runID+"_"+i, "CustomerKey": cKey, "TSDName": cName, "PauseStatus": pauseSt, "PublishStatus": pubSt, "StartStatus": startSt, "ErrorDetail": err, "RefreshedAt": Now() }); } } </script>
Before You Run This
- Create the
TSD_Refresh_LogDE first. The logging step will fail silently if it does not exist and you will lose your audit trail. - Test against one or two non-critical TSDs before pointing it at your full list or folder.
- The folder-based version only processes Active TSDs. Anything in an intermediate state is skipped and will show up in the log as SKIP.
- Run this as an SSJS Script Activity in Automation Studio, not from a CloudPage. CloudPages time out on large sets.
- After the run, send a test trigger on at least two TSDs to confirm the new content is live before signing off.
- If a TSD fails at the Pause step, do not re-run the whole script. Fix that TSD manually first, then re-run only the ones that failed.
Frequently Asked Questions
Yes. Journey Builder Triggered Send interactions use the same underlying TSD object model, so refreshing them via this script updates the content the Journey uses. In some Journey configurations you may also need to republish the Journey itself for all changes to fully propagate.
Run a WSProxy retrieve call and write the results to a DE: prox.retrieve("TriggeredSendDefinition", ["CustomerKey", "Name", "TriggeredSendStatus"], {}). This gives you a full inventory you can use to build your tsdList array.
Check the ErrorDetail field in the log DE to understand what caused the failure. To restart it, you can either use the Email Studio UI or push a direct TriggeredSendStatus: "Active" update via WSProxy. Do not retry the full script run until you have resolved what caused that specific TSD to fail.
For new builds, yes. Salesforce’s Transactional Messaging REST API is the modern replacement. It resolves content at send time rather than at publish time, which means this entire refresh problem simply does not exist. If you are maintaining a legacy implementation with dozens of TSDs, this script is the right fix for now. Migration to the Transactional Messaging API is the right call long term.
Managing a Complex SFMC Implementation?
Large SFMC environments with legacy Triggered Send architectures, content block dependencies, and Journey configurations need structured technical oversight. Genetrix helps teams clean up, document, and future-proof their Marketing Cloud setups.