Within Marketing Cloud's Automation Studio, SQL Query activities on the ListSubscribers view can be run to determine subscription information for users and lists. For SOAP calls, and programming languages using them, we work with ListSubscriber and SubscriberList types defined in the MC SOAP WSDL for querying and updating, respectively. This entry provides some examples of querying and updating, both via direct SOAP calls using MC's Postman workspace and programmatically with the FuelSDK-Java (see here for initialization code not covered in the code snippets below.)
How to query a user's subscriptions: The below SOAP request provides options on querying user subscription information by subscriberKey and optionally on subscription status (Active or Unsubscribed). The filters equals
and IN
are shown but there are several others, see the WSDL above (search on SimpleOperator) to see the full list.
<?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>ListSubscriber</ObjectType> <Properties>SubscriberKey</Properties> <Properties>ListID</Properties> <Properties>Status</Properties> <Filter xsi:type="SimpleFilterPart"> <Property>SubscriberKey</Property> <SimpleOperator>IN</SimpleOperator> <Value>...subscriberKey1...</Value> <Value>...subscriberKey2...</Value> <Value>...subscriberKey3...</Value> </Filter> <!--Filter xsi:type="ComplexFilterPart"> <LeftOperand xsi:type="SimpleFilterPart"> <Property>SubscriberKey</Property> <SimpleOperator>equals</SimpleOperator> <Value>...subscriberKey1...</Value> </LeftOperand> <LogicalOperator>AND</LogicalOperator> <RightOperand xsi:type="SimpleFilterPart"> <Property>Status</Property> <SimpleOperator>equals</SimpleOperator> <Value>Active</Value> </RightOperand> </Filter--> </RetrieveRequest> </RetrieveRequestMsg> </s:Body> </s:Envelope>
Sample output:
<Results xsi:type="ListSubscriber"> <PartnerKey xsi:nil="true" /> <ObjectID xsi:nil="true" /> <Status>Unsubscribed</Status> <ListID>2552723</ListID> <SubscriberKey>...subscriberKey1...</SubscriberKey> </Results> <Results xsi:type="ListSubscriber"> <PartnerKey xsi:nil="true" /> <ObjectID xsi:nil="true" /> <Status>Active</Status> <ListID>2591367</ListID> <SubscriberKey>...subscriberKey2...</SubscriberKey> </Results>
If querying by subscriber it is much faster to query many at once using the IN clause as shown above instead of making separate API calls for each subscriber. (I've found 50 at a time works well, I did not test larger amounts.) Most of the overhead involves making a request and receiving the response, with each extra user queried adding only a minor amount to the total response time.
A similar query, this time using Java and the Fuel SDK:
public RetrieveRequestMsg buildActiveListRequestMsg(String subscriberKey) { RetrieveRequest retrieveRequest = new RetrieveRequest(); retrieveRequest.setObjectType("ListSubscriber"); retrieveRequest.getProperties().add("SubscriberKey"); retrieveRequest.getProperties().add("ListID"); retrieveRequest.getProperties().add("Status"); retrieveRequest.setFilter(ETUtils.composeFilter( ETUtils.composeFilter("SubscriberKey", SimpleOperators.EQUALS, subscriberKey), LogicalOperators.AND, ETUtils.composeFilter("Status", SimpleOperators.EQUALS, "Active") )); RetrieveRequestMsg retrieveRequestMsg = new RetrieveRequestMsg(); retrieveRequestMsg.setRetrieveRequest(retrieveRequest); return retrieveRequestMsg; } private static FilterPart composeFilter(FilterPart leftPredicate, LogicalOperators operator, FilterPart rightPredicate) { ComplexFilterPart filter = new ComplexFilterPart(); filter.setLeftOperand(leftPredicate); filter.setLogicalOperator(operator); filter.setRightOperand(rightPredicate); return filter; }
Subsequent processing to get the subscriber's active subscriptions:
RetrieveResponseMsg response = etClient.getSoapConnection().getSoap().retrieve(buildActiveListRequestMsg("subscriberKey1")); if ("OK".equalsIgnoreCase(retrieveResponseMsg.getOverallStatus())) { Listresults = retrieveResponseMsg.getResults(); List listIDs = results.stream() .map(apiObject -> ((ListSubscriber) apiObject).getListID()) .collect(Collectors.toList()); }
How to update a user's subscriptions: The MC documentation provides several examples of working with the SubscriberList object for subscribing and unsubscribing users. Note there are two ways to unsubscribe a user from a list: marking the user Unsubscribed, which keeps an entry for the user on the subscription list with an unsubscribed date, and just deleting the user from the list. Marketing Cloud generally recommends the former approach. For list maintenance, we specify a user by subscriber key, and a series of lists. For each list, the List ID, an action (create
, update
, upsert
, delete
) and sometimes a status (Active
or Unsubscribed
) is provided. The delete
action does not take a status, and if status is otherwise omitted, it is treated as Active
if the user is not already on the list or keeps the status as-is if the user is. Sample Postman query:
<?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">Create</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"> <UpdateRequest xmlns="http://exacttarget.com/wsdl/partnerAPI"> <Objects xsi:type="Subscriber"> <SubscriberKey>...subscriberkey1...</SubscriberKey> <Lists> <ID>2716501</ID> <Action>upsert</Action> </Lists> </Objects> <Objects xsi:type="Subscriber"> <SubscriberKey>...subscriberkey1...</SubscriberKey> <Lists> <ID>2712867</ID> <Action>delete</Action> </Lists> </Objects> <Objects xsi:type="Subscriber"> <SubscriberKey>...subscriberkey2...</SubscriberKey> <Lists> <ID>2712867</ID> <Status>Active</Status> <Action>upsert</Action> </Lists> <Lists> <ID>2716962</ID> <Status>Active</Status> <Action>upsert</Action> </Lists> </Objects> </UpdateRequest> </s:Body> </s:Envelope>
Some notes for the above request:
Objects
block per list, as with the SubscriberKey1 user, or by having multiple Lists
under a single Objects
block as done with the SubscriberKey2 user. For the latter case, if an error would occur with any single list, testing is showing that none of the updates will occur under that Objects
block, and furthermore the error response will specify the problem but not which of the subscription changes is causing it.Objects
block, each user needs its own Objects
block(s).upsert
for subscribes (status = Active) and unsubscribes (status = Unsubscribed) are very unlikely to throw exceptions, providing the list ID and the user's subscriber key exists. This makes upserts a better option for many-lists-in-one-Objects-block changes. (The upsert
creates a record if it does not exist, but updates it otherwise.)upsert
use create
or update
.create
of a user of whatever status who is already on the list), NotOnList (13007) (for an update
of a user not on the list).upsert
you don't provide a status for the user, if the user is not on the list he will be added with status of Active. Otherwise, no change for the user (will remain Active or Unsubscribed as before.) Providing you don't delete users from lists but just mark them as Unsubscribed, this will allow you to add users to a list while respecting past unsubscribes.In Java using the FuelSDK, subscription changes are handled via an UpdateRequest. Here, we take Subscriber object(s) in which we identify each user by his SubscriberKey, and then populate each Subscriber object's lists
attribute with SubscriberList objects, with each object containing a List ID, action (create
, update
, delete
, upsert
, with the default apparently being upsert
) and optionally status (Active, Unsubscribed), subject to the same rules given above for the direct SOAP calls. Sample adding and unsubscribing lists from a given Subscriber:
import com.exacttarget.fuelsdk.internal.Subscriber; import com.exacttarget.fuelsdk.internal.SubscriberList; import com.exacttarget.fuelsdk.internal.SubscriberStatus; import com.exacttarget.fuelsdk.internal.UpdateOptions; import com.exacttarget.fuelsdk.internal.UpdateRequest; import com.exacttarget.fuelsdk.internal.UpdateResponse; import com.exacttarget.fuelsdk.ETSdkException; class BulkSubscriptionUpdate { private Subscriber subscriber; private final Setsubscribes; private final Set unsubscribes; .... } public UpdateResponse updateSubscriptions(BulkSubscriptionUpdate bulkSubscriptionUpdate) throws ETSubscriberServiceException { UpdateRequest updateRequest = new UpdateRequest(); UpdateOptions options = new UpdateOptions(); options.setQueuePriority(Priority.HIGH); options.setRequestType(RequestType.SYNCHRONOUS); updateRequest.setOptions(options); Subscriber subscriber = new Subscriber(); subscriber.setSubscriberKey(generateSubscriberKey(user.getId())); List subscriptions = new ArrayList<>(); if (!CollectionUtils.isEmpty(bulkSubscriptionUpdate.getSubscribes())) { for (Integer etid : bulkSubscriptionUpdate.getSubscribes()) { subscriptions.add(buildSubscriptionUpdate(etid, SubscriberStatus.ACTIVE)); } } if (!CollectionUtils.isEmpty(bulkSubscriptionUpdate.getUnsubscribes())) { for (Integer etid : bulkSubscriptionUpdate.getUnsubscribes()) { subscriptions.add(buildSubscriptionUpdate(etid, SubscriberStatus.UNSUBSCRIBED)); } } if (!CollectionUtils.isEmpty(subscriptions)) { subscriber.getLists().addAll(subscriptions); updateRequest.getObjects().add(subscriber); try { return update(updateRequest); } catch (ETSdkException e) { LOGGER.error("Failed to update subscriptions for subscriber {}", bulkSubscriptionUpdate.getSubscriber(), e); throw e; } } // nothing to update return null; } private static SubscriberList buildSubscriptionUpdate(Integer etid, SubscriberStatus status) { SubscriberList subscription = new SubscriberList(); subscription.setId(etid); subscription.setStatus(status); subscription.setAction("upsert"); return subscription; }
Posted by Glen Mazza in Marketing Cloud at 03:00AM Aug 28, 2022 | Comments[0]