🚨 Alert and monitoring system for Google Ads with Make

:robot: Download the PDF with the 6 scripts in :es: and :us: from LinkedIn:
Francisco Fernando de Brito Fontes on LinkedIn: Automatizar Google Ads Script con Make | 12 comments

Marketing Automation: Google Ads Scripts

Tell me if this sounds familiar to you:

You work with multiple Google Ads campaigns, spend hours creating the best ad campaign structures, carefully selecting keywords, and making the best ad copy.

Then, you activate the campaign, and after a few hours, everything goes well; you feel relieved, and the contacts begin to arrive! And you relax.

Then the problems begin to reveal themselves. Google disapproves ads, campaign quality score goes down, cost per conversion goes up, budget runs out, campaign pauses, and finally, 20% of clicks were invalid or spam.

But the worst thing is that you realized it several days later!!!, so you have wasted time and money, and now you have to give explanations to your clients.

Unfortunately, this is very common; if any of these common errors sound familiar to you, continue reading.

Alert and monitoring system for Google Ads
With Google Ads Script and Make, we will create an alert and monitoring system for advertising accounts at the MCC (My Control Center) level. It is advantageous if you work in an SEM agency or are a freelancer and have several clients.

When detecting an anomaly in the accounts, the system alerts the agency by sending a message via Telegram and a Slack channel.

What are Google Ads Scripts?
Google Ads scripts are snippets of code that allow you to perform automated tasks or account reviews.

You can mark how often it runs and see what changes it will make to your account before you approve it.

By using these scripts, you can spend less time on tedious stuff and more time on changes that will get you results.

Benefits of using Google Ads Script with Make

  • They help optimize the performance of campaigns.
  • It allows you to eliminate tedious tasks.
  • It allows for eliminating human error or forgetfulness.
  • They help you be more productive.
  • They help to focus more on the advertising strategy than on routine tasks.

How to use Google Ads Script with Make?
Each Google Ads script has a task associated with it; in our case, it collects information about the ad accounts that have a problem.

The compiled information is sent to Make by calling a URL. The URL is nothing more than a Webhook created in Make, which receives the data and creates the messages to be sent through different communication channels.

 

 

:rotating_light: Case 1
Alert for low budget in campaigns
Use this automation to track campaign budgets and receive daily alerts. In addition, it is allowed to set a goal for the percentage of consumption and the remaining days.

:sunglasses: Alert message received on Telegram

image

var CONFIG = {
    STATUS_CUSTOMER: 'ENABLED',
    MIN_REMAINING_DAY: 2,
    MAX_AVG_CONSUMPTION: 98,
    BUDGET_NAME: 'Consultoria Web S.p.A',
    URL_WEBHOOK: '**CHANGE THIS -YOUR URL WEBHOOK**',
    DATA_RESULT: []
  };

  function main () {
    var mccAccount = AdsApp.currentAccount();
    var accountIterator = AdsManagerApp.accounts().get();
    while (accountIterator.hasNext()) {
      var account = accountIterator.next();
      processAccount(account);
    }
    processResults();
  }

  function processAccount(account) {   
    MccApp.select(account);
    var adWordsAccount = AdWordsApp.currentAccount();
    
    var query = 'SELECT ' +
    ' account_budget.name, account_budget.status, account_budget.total_adjustments_micros,' +
    ' account_budget.id, account_budget.billing_setup, account_budget.amount_served_micros, account_budget.approved_end_date_time,' +
    ' account_budget.approved_end_time_type, account_budget.approved_spending_limit_micros, account_budget.approved_spending_limit_type, account_budget.approved_start_date_time,' +
    ' customer.id, customer.status, customer.descriptive_name' +
    ' FROM account_budget' +
    ' WHERE customer.status = "'+ CONFIG.STATUS_CUSTOMER  +'" AND' +
    ' account_budget.status = "APPROVED" AND account_budget.name LIKE "%'+ CONFIG.BUDGET_NAME  +'%" AND account_budget.approved_spending_limit_type != "INFINITE"' +
    ' ORDER BY account_budget.approved_start_date_time DESC LIMIT 1';
    
    var report = AdsApp.report(query);
    var rows = report.rows();
    
    while (rows.hasNext()) {
      var row = rows.next();
      
      var startDate = new Date(row['account_budget.approved_start_date_time']);
      startDate = Utilities.formatDate(startDate, "PST", "yyyyMMdd");

      var spent = adWordsAccount.getStatsFor(startDate, Utilities.formatDate(new Date(), "PST", "yyyyMMdd")).getCost();
      var lastWeekSpent = adWordsAccount.getStatsFor('LAST_7_DAYS').getCost();
      var avgDaily = Math.round(lastWeekSpent / 7);
      
      var spentLimit = Math.round(row['account_budget.approved_spending_limit_micros']/ 1000000);
      var totalAdjustments = Math.round(row['account_budget.total_adjustments_micros'] ? row['account_budget.total_adjustments_micros']/ 1000000 : 0);
      var remainingBudget = Math.round(spentLimit + totalAdjustments - spent);
      var remainingDays = Math.round(remainingBudget/avgDaily);
      var avgBudget = Math.round((spent * 100) / spentLimit);
      
      if(remainingBudget >= 0 && (remainingDays <= CONFIG.MIN_REMAINING_DAY || avgBudget >= CONFIG.MAX_AVG_CONSUMPTION) ){
        var resultAccount = Object.assign({}, row);
        resultAccount["remainingDays"] = remainingDays;
        resultAccount["remainingBudget"] = remainingBudget;
        resultAccount["avgBudget"] = avgBudget;

        CONFIG.DATA_RESULT.push(resultAccount);
      }      
    }
    
    return;
  }

  function processResults() {
    if (CONFIG.DATA_RESULT.length==0) {
      return;
    }
    
    var options = {
      'method' : 'post',
      'contentType': 'application/json',
      'payload' : JSON.stringify({"body": {"data":CONFIG.DATA_RESULT,"type":"budget"}})
    };
    
    UrlFetchApp.fetch(CONFIG.URL_WEBHOOK, options);
  }


 

 

:rotating_light: Case 2
Optimization Score Decrease Alert
Use this automation to track the optimization level of your ad campaigns. The optimization score estimates the potential performance of Google Ads accounts.

:sunglasses: Alert message received on Telegram

image

:sunglasses: Alert message received on Slack

image

var CONFIG = {
    STATUS_CUSTOMER: 'ENABLED',
    SCORE_CUSTOMER: 0.85,
    URL_WEBHOOK: '**CHANGE THIS -YOUR URL WEBHOOK**',
    DATA_RESULT: []
  };

  function main () {
    var mccAccount = AdsApp.currentAccount();
    var accountIterator = AdsManagerApp.accounts().get();
    while (accountIterator.hasNext()) {
      var account = accountIterator.next();
      processAccount(account);
    }
    processResults();
  }

  function processAccount(account) {   
    MccApp.select(account);
    var adWordsAccount = AdWordsApp.currentAccount();

    var query = 'SELECT ' +
    ' customer.optimization_score, customer.status, customer.descriptive_name, customer.id' +
    ' FROM customer' +
    ' WHERE customer.status = "'+ CONFIG.STATUS_CUSTOMER  +'" AND customer.optimization_score < '+ CONFIG.SCORE_CUSTOMER;
    
    var report = AdsApp.report(query);
    var rows = report.rows();
    
    while (rows.hasNext()) {
      var row = rows.next();
      CONFIG.DATA_RESULT.push(row);
    }
    
    return;
  }

  function processResults() {
    if (CONFIG.DATA_RESULT.length==0) {
      return;
    }
    
    var options = {
      'method' : 'post',
      'contentType': 'application/json',
      'payload' : JSON.stringify({"body": {"data":CONFIG.DATA_RESULT,"type":"score"}})
    };
    
    UrlFetchApp.fetch(CONFIG.URL_WEBHOOK, options);
  }


 

 

Case 3
:rotating_light: Invalid URL Alert
This automation scans your landing pages and alerts you whenever it finds an invalid URL. It’s useful when you’re doing a lot of testing with different landing pages, and you might forget to pause ads pointing to a page that doesn’t now exist.

:sunglasses: Alert message received on Slack

.

var CONFIG = {
  LABEL_CUSTOMER: 'CheckUrlDaily',
  STATUS_CUSTOMER: 'ENABLED',
  STATUS_CAMPAIGN: 'ENABLED',
  STATUS_GROUP: 'ENABLED',
  URL_WEBHOOK: '**CHANGE THIS -YOUR URL WEBHOOK**',
  VALID_CODES: [200,301],
  SLEEP_TIME: 250,
  BACKOFF_FACTOR: 2,
  DATA_RESULT: []
};

function main () {
  var mccAccount = AdsApp.currentAccount();

  var accountIterator = AdsManagerApp.accounts()
  .withCondition('LabelNames CONTAINS "' + CONFIG.LABEL_CUSTOMER + '"')
  .get();

  while (accountIterator.hasNext()) {
    var account = accountIterator.next();
    processAccount(account);
  }
  processResults();
}

function processAccount(account) {   
  MccApp.select(account);
  var adWordsAccount = AdWordsApp.currentAccount();

  var query = 'SELECT ' +
  ' ad_group_ad.ad.final_urls, ad_group_ad.ad.name, ad_group.name,' +
  ' campaign.name,' +
  ' customer.optimization_score, customer.descriptive_name, customer.id' +
  ' FROM ad_group_ad' +
  ' WHERE ad_group_ad.status NOT IN ("PAUSED", "REMOVED") AND ad_group.status = "'+CONFIG.STATUS_GROUP+'" AND campaign.status = "'+CONFIG.STATUS_CAMPAIGN+'" AND customer.status = "'+CONFIG.STATUS_CUSTOMER+'"';

  var report = AdsApp.report(query);
  var rows = report.rows();

  var listUrl= [];
  while (rows.hasNext()) {
    var row = rows.next();
    if(row["ad_group_ad.ad.final_urls"] && row["ad_group_ad.ad.final_urls"].length >0){
      listUrl.push({
        "final_url": row["ad_group_ad.ad.final_urls"][0],
        "customer.descriptive_name": row["customer.descriptive_name"],
        "customer.id": row["customer.id"],
        "customer.optimization_score": row["customer.optimization_score"]
      });      
    }
  }

  if(listUrl.length == 0){ return; } 

  const uniqueUrl = [...new Map(listUrl.map(item =>
    [item["final_url"], item])).values()];  

  let sleepTime = CONFIG.SLEEP_TIME;
  const checkUrl = async (item) => {
    try {
      var response = UrlFetchApp.fetch(item.final_url, {muteHttpExceptions: true});
      responseCode = response.getResponseCode();

      if (CONFIG.VALID_CODES.indexOf(responseCode) !== -1) {
        return;
      }

      CONFIG.DATA_RESULT.push(item);
      return;
    } catch(e) {
      Logger.log(e.message);
      if (e.message.indexOf('Service invoked too many times in a short time:') != -1) {
        Utilities.sleep(sleepTime);
        sleepTime *= CONFIG.BACKOFF_FACTOR;
      }
      return;
    }
  }

  uniqueUrl.forEach(async (item) => {
    await checkUrl(item);
  });

  return;
}

function processResults() {
  if (CONFIG.DATA_RESULT.length==0) {
    return;
  }

  var options = {
    'method' : 'post',
    'contentType': 'application/json',
    'payload' : JSON.stringify({"body": {"data":CONFIG.DATA_RESULT,"type":"urlchecker"}})
  };
  
  UrlFetchApp.fetch(CONFIG.URL_WEBHOOK, options);
}


 

 

:fire: Bonus track
Only 1 automation for all alerts


 

 
:pray: I will be genuinely grateful to have your support by leaving me a comment or simply telling someone this guide can help.

:robot: Download the PDF with the 6 scripts in :es: and :us: from LinkedIn:
Francisco Fernando de Brito Fontes on LinkedIn: Automatizar Google Ads Script con Make | 12 comments

1 Like

Heya @Francisco_Fontes :wave:

Thanks so much for sharing this insightful article on streamlining your Google Ads processes with Make. I’m sure quite a few digital marketers can 100% relate to the challenges you’re mentioning. The struggle is, indeed, real.

I’m sure this is gonna be increadibly helpful to many Makers. Thanks a lot for keeping us inspired :raised_hands:

1 Like