At work we used to create mailing lists on various search categories of interest to our readers, resulting in thousands of lists being created over the years. Since then we've switched to triggered sends, making these lists no longer of use, and hence we were looking for an automated way of deleting them.
Note it's very easy to delete a list, and in any automated process to delete a large number of lists it's important to make sure you're not accidentally deleting a list you need. For my work I checked any List ID to be deleted against a list of IDs that I knew our systems still used, disallowing the deletion were that the case. Such a process saved me from accidentally deleting two or three needed lists.
Some further points to note:
My first step was to get a list of lists in the system that I could examine to see which could be removed. To generate the list, I created a common_list_table_de
data extension and common_list_table_sq
SQL query activity, which are available in my Marketing Cloud Sends and Opens tutorial. Note the list ID in the data extension is what I used to identify a list for deletion. After running, I then viewed the extension in Email Studio, which also allows us to download the list from the browser.
I primarily needed the ListNames to determine which lists I could dispose of. In some cases, the MaxCreateDate and MaxUnsubscribe dates, which indicates the last time someone subscribed and unsubscribed to each list, was helpful in making that determination.
While I eventually needed a Java program to automate the deletion of our large number of unneeded lists, to get an idea of what would be needed, I first practiced with Postman to query and delete individual lists. SFMC offers an unofficial Postman workspace for making SOAP and REST MC calls, with documentation for using same on GitHub.
While the SFMC Postman collection doesn't provide out-of-the-box SOAP calls for Lists, it does have one for data extensions that I leveraged in creating commands for lists. The below request returns lists with three filter options: by list ID, list name (exact match), and list name (substring match), with no filter providing all lists up to a SFMC-determined maximum. The list ID that is to be subsequently used for deleting the list is the "id" value (see here for available values to query.) For a list query, it will be returned under the PartnerProperties
element.
List query SOAP call:
<?xml version="1.0" encoding="UTF-8"?> <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"> <s:Header> <a:Action s:mustUnderstand="1">Retrieve</a:Action> <a:To s:mustUnderstand="1">https://{{et_subdomain}}.soap.marketingcloudapis.com/Service.asmx</a:To> <fueloauth xmlns="http://exacttarget.com">{{dne_etAccessToken}}</fueloauth> </s:Header> <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <RetrieveRequestMsg xmlns="http://exacttarget.com/wsdl/partnerAPI"> <RetrieveRequest> <ObjectType>List</ObjectType> <Properties>ObjectID</Properties> <Properties>id</Properties> <Properties>CustomerKey</Properties> <Properties>ListName</Properties> <Properties>Category</Properties> <!--Filter xsi:type="SimpleFilterPart"> <Property>id</Property> <SimpleOperator>IN</SimpleOperator> <Value>123456</Value> <Value>234567</Value> </Filter--> <!--Filter xsi:type="SimpleFilterPart"> <Property>ListName</Property> <SimpleOperator>like</SimpleOperator> <Value>%News%</Value> </Filter--> <!--Filter xsi:type="SimpleFilterPart"> <Property>ListName</Property> <SimpleOperator>equals</SimpleOperator> <Value>My Sample Newsletter</Value> </Filter--> </RetrieveRequest> </RetrieveRequestMsg> </s:Body> </s:Envelope>
Response fragment:
<Results xsi:type="List"> <PartnerKey xsi:nil="true" /> <PartnerProperties> <Name>id</Name> <Value>123456</Value> </PartnerProperties> <ObjectID>....</ObjectID> <CustomerKey>....</CustomerKey> <ListName>My Newsletter</ListName> <Category>...folder ID...</Category> </Results>
A SOAP call for deleting list(s) by their List IDs are as follows:
<?xml version="1.0" encoding="UTF-8"?> <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"> <s:Header> <a:Action s:mustUnderstand="1">Delete</a:Action> <a:To s:mustUnderstand="1">https://{{et_subdomain}}.soap.marketingcloudapis.com/Service.asmx</a:To> <fueloauth xmlns="http://exacttarget.com">{{dne_etAccessToken}}</fueloauth> </s:Header> <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <DeleteRequest xmlns="http://exacttarget.com/wsdl/partnerAPI"> <Options></Options> <Objects xsi:type="List"> <ID xsi:nil="true">123456</ID> </Objects> <!-- Add as many additional List IDs as desired to delete all in one call--> <!--Objects xsi:type="List"> <ID xsi:nil="true">234567</ID> </Objects--> </DeleteRequest> </s:Body> </s:Envelope>
Switching over to Java, we created a utility that would read a list of ET List IDs that we no longer needed, and proceeded to delete them after confirming they weren't on our "needed" list. As shown in the above SOAP call, multiple lists can be deleted in just one call -- for the thousands we needed to delete, deleting 50 at a time seemed to work fastest, and our code managed about 3000 lists per minute.
Using the FuelSDK, pseudocode to delete lists would be as follows:
public DeleteResponse deleteListsInMC(java.util.List<Integer> etListIds) throws ETSdkException { // etListIds checked earlier to confirm they are not needed (e.g., query against a table of in-use lists) DeleteRequest deleteRequest = new DeleteRequest(); DeleteOptions options = new DeleteOptions(); options.setRequestType(RequestType.SYNCHRONOUS); options.setQueuePriority(priority); deleteRequest.setOptions(options); for (Integer i : etListIds) { List list = new List(); list.setId(i); deleteRequest.getObjects().add(list); } // configure ETClient similar to here: https://salesforce.stackexchange.com/a/312178 // ETClient etClient = .... return etClient.getSoapConnection().getSoap().delete(deleteRequest); }
The returned DeleteResponse object returns a list of DeleteResult objects, one object per list deleted. The result objects have an ordinal ID which map 1-to-1 to the order of lists given in the delete request. Sample result processing code:
DeleteResponse dr = deleteListsInMC(listToDelete); List<DeleteResult> delResult = dr.getResults(); for (DeleteResult drTemp : delResult) { Integer drTempListID = listToDelete.get(drTemp.getOrdinalID()); // error codes: https://developer.salesforce.com/docs/marketing/marketing-cloud/guide/13000_13099_list_object.html // Postman showing 13000 ErrorCode with ListNotFound status message in case of unknown lists if ("OK".equals(drTemp.getStatusCode())) { // OrdinalID is 0-based LOGGER.info("Deleted list {} from MC", drTempListID); } else if ("Error".equalsIgnoreCase(drTemp.getStatusCode())) { if (drTemp.getErrorCode() == 13000) { LOGGER.info("List {} not in MC, nothing to delete", drTempListID); } else { LOGGER.warn("Could not delete list {}: error code {} status message {}", drTempListID, drTemp.getErrorCode(), drTemp.getStatusMessage()); } } }
Posted by Glen Mazza in Marketing Cloud at 03:00AM Aug 22, 2022 | Comments[0]