Skip to Content

Automate Email Asset Metadata Logging in Salesforce Marketing Cloud

Ever wondered how to fetch all your email assets in Salesforce Marketing Cloud and log their juicy metadata (like custom attributes) into a Data Extension?

Yeah, we did too. And let’s be real—manually opening every email asset like it’s 2010 isn’t it. So we built a smart, server-side script that does all the heavy lifting for us. Buckle up—this one’s a time-saver.

 What’s This Script All About?

This script is a full-blown automation for metadata logging. It:

  • Authenticates using OAuth2 Client Credentials
  • Pulls paginated email asset data from the SFMC Asset API
  • Extracts custom metadata (like __AdditionalEmailAttribute1 through 5)
  • Inserts records into a logging Data Extension: EmailAssetLogging_DE

Prerequisites

Before diving in, make sure you've got these essentials ready:

✅ A valid Client ID, Client Secret, and Account ID

✅ An Installed Package with Asset REST API access

✅ A Data Extension named EmailAssetLogging_DE with fields:

EmailID, EmailName, AdditionalEmailAttribute1 ... up to 5

Step 1: Authenticate Like a Boss

var payload = {
  "grant_type": "client_credentials",
  "client_id": clientId,
  "client_secret": clientSecret,
  "account_id": accountId
};

We’re hitting the /v2/token endpoint to get our access_token. Without it, we’re not going anywhere.

Step 2: Get Paginated Email Asset Data

This API call grabs all the email assets in pages of 100. You can tweak that size if you’re feeling adventurous (or throttled).

var pagedUrl = assetUrlBase
  + "?$page=" + currentPage
  + "&$pageSize=" + pageSize
  + "&$orderBy=name asc"
  + "&$filter=assetType.name like 'email'"
  + "&$fields=id,customerKey,name,data";

Notice the $filter for "assetType.name like 'email'"—we don’t want images, folders, or old relics.

Step 3: Extract Custom Attributes (If They Exist)

Custom attributes are tucked inside item.data.email.attributes. We gracefully parse and loop through them.

if (
  item.hasOwnProperty("data") &&
  item.data.hasOwnProperty("email") &&
  item.data.email.hasOwnProperty("attributes")
) {
  attributes = item.data.email.attributes;
}

Then we match those attribute names like __AdditionalEmailAttribute1, etc. and assign values accordingly.

Step 4: Insert Into Data Extension

We throw it all into EmailAssetLogging_DE using:

Platform.Function.InsertData("EmailAssetLogging_DE",
  ["EmailID", "EmailName", "AdditionalEmailAttribute1", "AdditionalEmailAttribute2", ...],
  [emailId, emailName, attr1, attr2, ...]);

very successful insert increments our count. Failures are logged for debugging.

Step 5: Validate the Results

Once all pages are processed, we do a final validation:

var finalRows = Platform.Function.LookupRows("EmailAssetLogging_DE", "EmailID", "*");

We compare:

  • ✅ Total assets reported by API
  • ✅ Rows inserted
  • ✅ Rows currently in the DE

If they all match up, you’re golden. If not? Check for paging issues or failed inserts.

Cross-Check with Content Builder UI

We’re not just trusting the API—we’re checking receipts.

Here’s how:

  1. Navigate to Content Builder
  2. Apply a filter for Asset Type = Email
  3. Scroll to the bottom and note the total asset count
  4. Compare that number with:
    • API count (totalCount)
    • DE count (finalRowCount)

If all three line up? 💯 Your system’s bulletproof.

Full Script 

<script runat="server">
Platform.Load("Core", "1.1.1");
Write("<br>Script started...");

// CONFIG
var clientId = "ihdfl24a1e3vth3gq2rfc21u";
var clientSecret = "ENMBiBZ9whE5lbHMwRupGiHQ";
var accountId = "546002178";
var authUrl = "https://mcpbq4z2b121rg0v4mmy7tjls8h4.auth.marketingcloudapis.com/v2/token";
var assetUrlBase = "https://mcpbq4z2b121rg0v4mmy7tjls8h4.rest.marketingcloudapis.com/asset/v1/content/assets";
Write("<br>Config loaded.");

// AUTHENTICATE
Write("<br>Authenticating...");
var payload = {
    "grant_type": "client_credentials",
    "client_id": clientId,
    "client_secret": clientSecret,
    "account_id": accountId
};

var authResp = HTTP.Post(authUrl, "application/json", Stringify(payload));
var token = "";

if (authResp.StatusCode === 200) {
    var authData = Platform.Function.ParseJSON(authResp.Response[0]);
    token = authData.access_token;
    Write("<br>Access token acquired.");
} else {
    Write("<br>Authentication failed. Response: " + authResp.Response[0]);
    return;
}

// HEADERS (no Content-Type on GET)
var headerNames = ["Authorization"];
var headerValues = ["Bearer " + token];

// INIT PAGINATION
var pageSize = 100;
var currentPage = 1;
var totalCount = 0;
var insertedCount = 0;
Write("<br>Pagination initialized.");

// FETCH + PROCESS LOOP
do {
    var pagedUrl = assetUrlBase
        + "?%24page=" + currentPage
        + "&%24pageSize=" + pageSize
        + "&%24orderBy=name%20asc"
        + "&%24filter=assetType.name%20like%20%27email%27"
        + "&%24fields=id%2CcustomerKey%2Cname%2Cdata";

    Write("<br><br>Fetching Page: " + currentPage);
    var resp = HTTP.Get(pagedUrl, headerNames, headerValues);

    // Skip pages with no content
    if (!resp.Content || resp.Content.length < 10) {
        Write("<br>No content returned on this page. Breaking.");
        break;
    }

    var parsed;
    try {
        parsed = Platform.Function.ParseJSON(resp.Content);
    } catch (e) {
        Write("<br>Failed to parse response JSON: " + Stringify(resp.Content));
        break;
    }

    if (currentPage === 1) {
        totalCount = parsed.count || 0;
        Write("<br>Total asset count reported by API: " + totalCount);
    }

    var items = parsed.items || [];
    Write("<br>Items in this page: " + items.length);

    for (var i = 0; i < items.length; i++) {
        var item = items[i];
        var emailId = item.id;
        var emailName = item.name || "";

        var attr1 = "", attr2 = "", attr3 = "", attr4 = "", attr5 = "";

        try {
            var attributes = [];

if (
    item.hasOwnProperty("data") &&
    item.data.hasOwnProperty("email") &&
    item.data.email.hasOwnProperty("attributes")
) {
    attributes = item.data.email.attributes;
}
            for (var j = 0; j < attributes.length; j++) {
                var attr = attributes[j];
                var name = attr.name;
                var value = attr.value || "";

                if (name === "__AdditionalEmailAttribute1") attr1 = value;
                if (name === "__AdditionalEmailAttribute2") attr2 = value;
                if (name === "__AdditionalEmailAttribute3") attr3 = value;
                if (name === "__AdditionalEmailAttribute4") attr4 = value;
                if (name === "__AdditionalEmailAttribute5") attr5 = value;
            }
        } catch (e) {
            Write("<br>Error reading attributes for Email ID: " + emailId + ". Skipping.");
            continue;
        }

        // Insert into DE
        var inserted = Platform.Function.InsertData("EmailAssetLogging_DE",
            ["EmailID", "EmailName", "AdditionalEmailAttribute1", "AdditionalEmailAttribute2", "AdditionalEmailAttribute3", "AdditionalEmailAttribute4", "AdditionalEmailAttribute5"],
            [emailId, emailName, attr1, attr2, attr3, attr4, attr5]);

        if (inserted > 0) {
            insertedCount++;
            Write("<br>Inserted Email ID: " + emailId);
        } else {
            Write("<br>Insert failed for Email ID: " + emailId);
        }
    }

    currentPage++;
} while ((currentPage - 1) * pageSize < totalCount);

// FINAL VALIDATION (using LookupRows)
Write("<br><br>Final validation...");
var finalRows = Platform.Function.LookupRows("EmailAssetLogging_DE", "EmailID", "*");
var finalRowCount = finalRows ? finalRows.length : 0;

Write("<br>Total reported from API: " + totalCount);
Write("<br>Total rows inserted in this run: " + insertedCount);
Write("<br>Total rows currently in DE: " + finalRowCount);

if (finalRowCount == totalCount) {
    Write("<br>Validation passed. All records are in sync.");
} else {
    Write("<br>Validation mismatch. DE has: " + finalRowCount + ", Expected: " + totalCount);
}
</script>

Using Current Weather Data in Your Marketing Campaigns: An Example with AccuWeather