Skip to main content

How to manage spam complaints on Salesforce Marketing Cloud

There are several factors to take into consideration for a successful landing in the inbox. Open rates, bounce rates, ctr and other metrics all reveal a lot about your emails reputation for ISPs and your clients. An important factor we need to keep an eye on is spam complaints rate because it can easily damage your domains and IPs hardly earned reputation.

In this article, you will learn how to manage email complaints on a multi-business unit organisation on Salesforce Marketing Cloud when using a custom solution for managing consents.

Before digging into the subject, I want to give an overview about the architecture of the Marketing Cloud account we are using.

SFMC account architecture

We have two brands we’re going to call Brand A and Brand B that are running on a multi-org account. At the beginning of the project, we did not have longterm visibility about the client’s needs. So, we decided to work with a basic and simple architecture:

SFMC Architecture connected to Sales Cloud

A parent Business Unit, the only one that was used at that time: Brand A. A second BU, which is a contract’s bonus from Salesforce.

Why is this architecture not convenient?


I would not recommend using this architecture for many reasons. Some are explained below, and some are not. What I suggest is using the parent BU as an Admin BU and use child BUs for your brands if you have many. This way, you will avoid a lot of headaches in the future..


We can’t use the business-level unsubscribes standard functionality at the parent BU. Remember that our parent BU represents our Brand A.

This functionality is available at Properties settings of a Business unit. We have to choose between these two options:

  1. Subscribers will be unsubscribed from all business units in the Enterprise
  2. Subscribers will be unsubscribed from this business unit only

The problem is, we don’t have a choice at the parent BU. SFMC forces you to choose the first option. This will result in an unwanted behaviour:


A contact will no longer receive emails from Brand B if he unsubscribes from a Brand’s A email.

FeedBack Loops

There are two ways a recipient can use to unsubscribe from emails. He can either click the unsubscribe link on the email, or make use of the “Mark as spam” button in the inbox.

It’s important to understand how spam complaints are handled by Marketing Cloud because, depending on how we are managing consents, we might adjust the standard handling process.

ISP Feedback Loops for Marketing Cloud


A feedback loop (FBL) is a service provided by some ISPs to let recipients inform senders that a given email is unwanted (by letting the user hit the “This is spam” button on his email client).

After the click on this button, an “FBL” complaint is sent back to Marketing Cloud. Once processed, the recipient will be marked as unsubscribed on AllSubscribers and the complaint is logged in the Complaint data view.

Do all ISPs have Feedback loops?

As stated in this article, All Marketing Cloud clients are signed up for the ISP feedback loops below automatically. Salesforce Marketing Cloud bulk-registers all of its sending IP address ranges with these ISP feedback loops:


Bluetie (Excite), Comcast, Cox, Fastmail, Microsoft Hotmail, Italia Online, La Poste, Liberty Global, Locaweb,, OpenSRS (Tucows), Rackspace (Mailtrust), Seznam, Synacor, Telenor, Telstra, Terra, UOL (Brazil),, XS4ALL, and Yandex


We see Microsoft Hotmail on the list, but it’s important to know that FBL for Outlook Desktop version is not offered by Microsoft. This was confirmed by the SFMC’s support.

I can’t see Yahoo on the list, what about them?

As explained in this article, Yahoo Mail’s Feedback Loop requires that all mail be signed with DKIM. Which means, we need to have Private Domain or Sender Authentication Package on our account because it’s necessary to sign all mail with DKIM (DomainKeys Identified Mail) authentication.

In our case, we have SAP and private domains on both BUs. We followed the statement in the article saying:

If you have a sending domain configured as a Private Domain with Marketing Cloud, please reach out to support to have the domain registered for the Yahoo FBL.

We’ve created an email asking the support to activate this feature, but it appears that it’s enabled automatically for private domains as well as SAP. Anyway, I recommend you asking them to enable it for you, who knows ..

What about Gmail?

Google has a different mechanism that does not function like a standard FBL. They call it GFL (Gmail Feedback Loop). It offers a high level reputation related statistics through their tool “Gmail Postmaster Tools”. Unlike other ISPs, SFMC does not register its clients domains with Gmail Postmaster Tools.


You can check this enrollment guide to register for GFL.

I’ve done some tests (we are in 2018), and it appears that if a recipient clicks on the “Mark as spam” button, it will not be logged in Complaints data view, but unlike what it is said in the SFMC documentation, the recipient will be marked as unsubscribed in AllSubscribers.

How to handle complaints on Salesforce Marketing Cloud?

Handling complaints depends on our SFMC architecture and how are we handling consents. In our case, we moved consents logic outside of AllSubscribers.


You can check out this article where I explain a way to manage Data Privacy between Sales and Marketing Clouds.

We are using a Sales Cloud plugin called Data Privacy Manager to handle consents. A custom field is created on Lead, Account and Contact objects. Each consent is represented by a unique auto-generated six digit code. For example, if we have two types of communications for our contacts, let’s call them Newsletter and SpecialOffers, they will have the ids 100001 & 100002. These values are concatenated into the field and separated by a comma.

On the SFMC side, each consent will be represented by a boolean field on our Consent data extension. It should look like below:

Field NameField TypeLengthDefault Value

Getting today’s complaints from Complaints data view

Salesforce Marketing Cloud provides a data view to check complaints data related to emails from our BUs. By querying this data view, we can get spam complaints from our subscribers about our emails sends for the last six months.

To get today’s complaints, we will be creating an automation with two activities. An SQL query to get data, and a Script activity to update consents on Sales Cloud.

The automation will be scheduled to run daily at 5AM. The schedule time depends on other automations on our account.


I would suggest scheduling this automation early in the morning so that “complainers” are excluded from that day’s communications as well.


We need a data extension to store complaints in our socle. Let’s go with this format:

Field NameField TypeLengthDefault Value

You should be asking, why do we need the OYBAccountID and Secret fields? Good question. In our case, we have two business units, each business unit represents a different brand and have its own domains and IP addresses. And since AllSubscribers is shared between BUs as well as Complaints data view, complaints from all business units will be regrouped in this data view, we don’t want a subscriber that complaints about a BrandA’s email to be considered as a BrandB’s complainer.


AllSubscribers is shared between all BUs of an SFMC instance. The same applies for Complaints data view, complaints from all business units will be regrouped in this data view.


OYBAccountID is a field in our data view that is populated automatically by Marketing Cloud only if the complaint is from a child BU. Hence, if a complaint is from our child BU, we can use OYBAccountID to determine from which consents we need to unsubscribe the complainer.

I’ll be explaining the utility of “Secret” field later on.

SQL Query to get complaints


ISNULL(comp.OYBAccountID,comp.AccountID) as OYBAccountID,

FROM _Complaint comp
INNER JOIN _job j ON comp.jobid = j.jobid

AND convert(date, comp.eventdate) >= dateadd(day, datediff(day, 0, getdate()) - 1, 0)
AND convert(date, comp.eventdate) < getdate()
AND ((OYBAccountID IS not null and OYBAccountID = 50000XXXX) OR (OYBAccountID IS null))

When OYBAccountID is null, which will always be the case when running the query from the parent BU, the ISNULL function populates it with the AccountID. We use Datediff function to get yesterday’s date and compare it with today’s date.

Automation to update consents on Sales Cloud

Run AMScript in a Script Activity in Automation Studio

After marking an email as spam, the contact is marked as Unsubscribed in AllSubscribers. This is not enough in our case because we are relying on a data extension to manage our contacts’ consents. We need to update consents on Sales Cloud using the mechanism described here.

To unsubscribe a contact, we need to use the AMPScript function CreateSalesforceObject to create a Task on Sales Cloud with specific texts in the subject and comments fields.

But, as you might already know, Script activities work only with SSJS. Hence, we have two options here: use AMPScript directly in our script activity using concatenation and TreatAsContent function (which I recommend only if your code is simple and not too long) . Or, using this way:

  • Create a Code Snippet content block in Content Builder
  • Add our AMPScript to it
  • Execute the AMPScript from the Script Activity using SSJS’s platform function ContentBlockByKey

Code Snippet code

This code starts with a lookup on our BRAND_A_COMPLAINTS data extension hosting all our complaints of the day.


Use the field Secret, which is a number field with a default value of 1, as an always true condition in a lookup to get all records of a data extension. It’s a sort of a workaround.

Then, we are getting the subscriberkey of the complainer and creating a task on Sales Cloud with the specific texts in the subject and comments. In this case, we are unsubscribing contacts from all email communications.

At the end, we are logging subscriberkeys and the date of the unsubscriptions.

%%[ var @rows, @row, @rowCount, @subKey, @counter

set @rows = LookupRows("BRANDA_COMPLAINTS","OYBAccountID","50000XXXX","Secret", 1)
set @rowCount = rowcount(@rows)

if @rowCount > 0 then
for @counter = 1 to @rowCount do
set @row = row(@rows, @counter)
set @subKey = field(@row,"SubscriberKey")
set @created_taskId= CreateSalesforceObject('Task',3,'WhoId', @subKey, 'Subject', 'Datapm:Unsubscribe', 'Description', '{"channel":"email"}')

InsertDE("COMPLAINTS_LOG","SubscriberKey", @subKey,"Event_Date", FormatDate(Now(),"iso"))

Script activity code

In the script activity, we are using the ContentBlockByKey function to execute our AMPScript. Don’t forget to replace YOUR_CONTENTBLOCK_KEY by your Code Snippet key. We can use ContentBlockByName or ContentBlockById functions as well.

<script runat="server">
var ampscriptCode = Platform.Function.ContentBlockByKey("YOUR_CONTENTBLOCK_KEY");

We discovered a technical way to handle complaints when using a custom consents management solution. We need to understand that if the percentage is high (it is said that a bad rate would be above 0.3%), we might consider checking our emailing strategy (content and frequency) because at the end, the source of the problem is what needs to be fixed.

Next time, I’ll be writing about how to handle complaints from ISPs that are not handled by default by Marketing Cloud. Let me know if this would be interesting, and don’t forget, all comments and suggestions are welcome.