Production-ready prompts, scripts, frameworks and AI agents for Google Ads professionals. No payment required.
var CONFIG = {
// ═══════════════════════════════════════════════════════════════════════════
// OUTPUT SETTINGS
// ═══════════════════════════════════════════════════════════════════════════
SPREADSHEET_URL: 'CREATE_NEW', // Or paste existing spreadsheet URL
// Email alerts (leave empty array to disable)
EMAIL_RECIPIENTS: [],
// Slack webhook (leave empty to disable)
SLACK_WEBHOOK_URL: '',
// ═══════════════════════════════════════════════════════════════════════════
// DATE RANGE FOR PERFORMANCE METRICS
// ═══════════════════════════════════════════════════════════════════════════
DATE_RANGE: 'LAST_30_DAYS',
// ═══════════════════════════════════════════════════════════════════════════
// FILTERS
// ═══════════════════════════════════════════════════════════════════════════
CAMPAIGN_NAME_CONTAINS: '', // Filter campaigns (empty = all)
CAMPAIGN_NAME_EXCLUDES: '', // Exclude campaigns containing this
// ═══════════════════════════════════════════════════════════════════════════
// EXPORT OPTIONS
// ═══════════════════════════════════════════════════════════════════════════
INCLUDE_PAUSED_CAMPAIGNS: false,
INCLUDE_PAUSED_ADGROUPS: false,
INCLUDE_AUDIENCES: true,
INCLUDE_EXTENSIONS: true,
INCLUDE_CONVERSION_ACTIONS: true,
// ═══════════════════════════════════════════════════════════════════════════
// EXECUTION SETTINGS
// ═══════════════════════════════════════════════════════════════════════════
LOG_LEVEL: 'INFO', // DEBUG, INFO, WARN, ERROR
TIME_LIMIT_MINUTES: 25, // Exit gracefully before this (max 30)
BATCH_SIZE: 500 // Rows per spreadsheet write
};
/******************************************************************************
* MAIN EXECUTION
******************************************************************************/
function main() {
var startTime = new Date();
log('INFO', 'Account Snapshot Export started: ' + startTime.toISOString());
try {
var ss = initializeSpreadsheet();
var snapshot = collectAccountSnapshot(startTime);
writeAllSheets(ss, snapshot);
sendNotifications(snapshot, ss.getUrl(), startTime);
logSummary(snapshot, startTime);
} catch (error) {
handleFatalError(error, startTime);
}
}
/******************************************************************************
* DATA COLLECTION
******************************************************************************/
function collectAccountSnapshot(startTime) {
var snapshot = {
accountInfo: getAccountInfo(),
campaignStructure: getCampaignStructure(startTime),
budgetAllocation: getBudgetAllocation(),
conversionActions: CONFIG.INCLUDE_CONVERSION_ACTIONS ? getConversionActions() : [],
audienceSignals: CONFIG.INCLUDE_AUDIENCES ? getAudienceSignals() : [],
extensions: CONFIG.INCLUDE_EXTENSIONS ? getExtensions() : [],
networkSettings: getNetworkSettings(),
summary: {}
};
// Calculate summary metrics
snapshot.summary = calculateSummary(snapshot);
return snapshot;
}
function getAccountInfo() {
var account = AdsApp.currentAccount();
var stats = account.getStatsFor(CONFIG.DATE_RANGE);
return {
'Account Name': account.getName(),
'Account ID': account.getCustomerId(),
'Currency': account.getCurrencyCode(),
'Timezone': account.getTimeZone(),
'Date Range': CONFIG.DATE_RANGE,
'Total Cost': stats.getCost(),
'Total Clicks': stats.getClicks(),
'Total Impressions': stats.getImpressions(),
'Total Conversions': stats.getConversions(),
'Total Conv Value': stats.getConversionValue(),
'Avg CPC': stats.getAverageCpc(),
'CTR': stats.getCtr(),
'Conv Rate': stats.getConversionRate(),
'CPA': stats.getConversions() > 0 ? stats.getCost() / stats.getConversions() : 0,
'ROAS': stats.getCost() > 0 ? stats.getConversionValue() / stats.getCost() : 0,
'Export Date': new Date().toISOString()
};
}
function getCampaignStructure(startTime) {
var campaigns = [];
var campaignSelector = AdsApp.campaigns();
if (!CONFIG.INCLUDE_PAUSED_CAMPAIGNS) {
campaignSelector = campaignSelector.withCondition('Status = ENABLED');
}
var campaignIterator = campaignSelector.get();
var processed = 0;
while (campaignIterator.hasNext()) {
var campaign = campaignIterator.next();
var campaignName = campaign.getName();
// Apply campaign name filters
if (CONFIG.CAMPAIGN_NAME_CONTAINS &&
campaignName.toLowerCase().indexOf(CONFIG.CAMPAIGN_NAME_CONTAINS.toLowerCase()) === -1) {
continue;
}
if (CONFIG.CAMPAIGN_NAME_EXCLUDES &&
campaignName.toLowerCase().indexOf(CONFIG.CAMPAIGN_NAME_EXCLUDES.toLowerCase()) !== -1) {
continue;
}
var stats = campaign.getStatsFor(CONFIG.DATE_RANGE);
var budget = campaign.getBudget();
// Get ad group count
var adGroupSelector = campaign.adGroups();
if (!CONFIG.INCLUDE_PAUSED_ADGROUPS) {
adGroupSelector = adGroupSelector.withCondition('Status = ENABLED');
}
var adGroupCount = adGroupSelector.get().totalNumEntities();
// Get keyword count for Search campaigns
var keywordCount = 0;
try {
keywordCount = campaign.keywords().withCondition('Status = ENABLED').get().totalNumEntities();
} catch (e) {
// Not a search campaign
}
// Get ad count
var adCount = 0;
try {
adCount = campaign.ads().withCondition('Status = ENABLED').get().totalNumEntities();
} catch (e) {
// May not have ads
}
campaigns.push({
'Campaign Name': campaignName,
'Campaign ID': campaign.getId(),
'Status': campaign.isEnabled() ? 'ENABLED' : 'PAUSED',
'Campaign Type': campaign.getAdvertisingChannelType(),
'Bidding Strategy': campaign.getBiddingStrategyType(),
'Daily Budget': budget ? budget.getAmount() : 'Shared',
'Budget Name': budget ? (budget.getName() || 'Campaign Budget') : 'Shared Budget',
'Ad Groups': adGroupCount,
'Keywords': keywordCount,
'Ads': adCount,
'Cost': stats.getCost(),
'Clicks': stats.getClicks(),
'Impressions': stats.getImpressions(),
'Conversions': stats.getConversions(),
'Conv Value': stats.getConversionValue(),
'CTR': stats.getCtr(),
'Avg CPC': stats.getAverageCpc(),
'Conv Rate': stats.getConversionRate(),
'CPA': stats.getConversions() > 0 ? stats.getCost() / stats.getConversions() : null,
'ROAS': stats.getCost() > 0 ? stats.getConversionValue() / stats.getCost() : null
});
processed++;
if (processed % 50 === 0) {
log('DEBUG', 'Processed ' + processed + ' campaigns');
checkTimeLimit(startTime);
}
}
// Sort by cost descending
campaigns.sort(function(a, b) { return b.Cost - a.Cost; });
log('INFO', 'Collected ' + campaigns.length + ' campaigns');
return campaigns;
}
function getBudgetAllocation() {
var budgets = [];
var budgetMap = {};
var campaigns = AdsApp.campaigns().withCondition('Status = ENABLED').get();
while (campaigns.hasNext()) {
var campaign = campaigns.next();
var budget = campaign.getBudget();
if (budget) {
var budgetId = budget.getId ? budget.getId() : campaign.getId();
var budgetName = budget.getName() || 'Campaign: ' + campaign.getName();
if (!budgetMap[budgetId]) {
budgetMap[budgetId] = {
'Budget Name': budgetName,
'Daily Amount': budget.getAmount(),
'Delivery Method': budget.getDeliveryMethod ? budget.getDeliveryMethod() : 'STANDARD',
'Campaigns Using': [],
'Total Spend (Period)': 0,
'Campaign Count': 0
};
}
budgetMap[budgetId]['Campaigns Using'].push(campaign.getName());
budgetMap[budgetId]['Campaign Count']++;
budgetMap[budgetId]['Total Spend (Period)'] += campaign.getStatsFor(CONFIG.DATE_RANGE).getCost();
}
}
for (var id in budgetMap) {
var b = budgetMap[id];
budgets.push({
'Budget Name': b['Budget Name'],
'Daily Amount': b['Daily Amount'],
'Delivery Method': b['Delivery Method'],
'Campaign Count': b['Campaign Count'],
'Campaigns': b['Campaigns Using'].join(', '),
'Total Spend (Period)': b['Total Spend (Period)'],
'Monthly Projected': b['Daily Amount'] * 30.4
});
}
// Sort by daily amount descending
budgets.sort(function(a, b) { return b['Daily Amount'] - a['Daily Amount']; });
log('INFO', 'Collected ' + budgets.length + ' budgets');
return budgets;
}
function getConversionActions() {
var conversions = [];
// Use GAQL to get conversion action data
var query = "SELECT " +
"conversion_action.name, " +
"conversion_action.id, " +
"conversion_action.status, " +
"conversion_action.category, " +
"conversion_action.type, " +
"conversion_action.counting_type, " +
"conversion_action.include_in_conversions_metric, " +
"conversion_action.value_settings.default_value, " +
"metrics.all_conversions, " +
"metrics.all_conversions_value " +
"FROM conversion_action " +
"WHERE conversion_action.status != 'REMOVED'";
try {
var report = AdsApp.report(query);
var rows = report.rows();
while (rows.hasNext()) {
var row = rows.next();
conversions.push({
'Conversion Name': row['conversion_action.name'],
'Conversion ID': row['conversion_action.id'],
'Status': row['conversion_action.status'],
'Category': row['conversion_action.category'],
'Type': row['conversion_action.type'],
'Counting': row['conversion_action.counting_type'],
'Primary (In Conversions)': row['conversion_action.include_in_conversions_metric'],
'Default Value': row['conversion_action.value_settings.default_value'],
'All Conversions': row['metrics.all_conversions'],
'All Conv Value': row['metrics.all_conversions_value']
});
}
} catch (e) {
log('WARN', 'Could not fetch conversion actions: ' + e.message);
}
log('INFO', 'Collected ' + conversions.length + ' conversion actions');
return conversions;
}
function getAudienceSignals() {
var audiences = [];
// Campaign-level audiences
try {
var campaignAudiences = AdsApp.targeting().audiences().get();
while (campaignAudiences.hasNext()) {
var audience = campaignAudiences.next();
var campaign = audience.getCampaign();
audiences.push({
'Level': 'Campaign',
'Campaign': campaign.getName(),
'Ad Group': '-',
'Audience Name': audience.getName(),
'Audience ID': audience.getId(),
'Type': audience.getAudienceType ? audience.getAudienceType() : 'Unknown',
'Bid Modifier': audience.bidding().getBidModifier()
});
}
} catch (e) {
log('DEBUG', 'Could not fetch campaign audiences: ' + e.message);
}
// Ad group-level audiences
try {
var adGroupAudiences = AdsApp.adGroupTargeting().audiences().get();
while (adGroupAudiences.hasNext()) {
var audience = adGroupAudiences.next();
var adGroup = audience.getAdGroup();
var campaign = audience.getCampaign();
audiences.push({
'Level': 'Ad Group',
'Campaign': campaign.getName(),
'Ad Group': adGroup.getName(),
'Audience Name': audience.getName(),
'Audience ID': audience.getId(),
'Type': audience.getAudienceType ? audience.getAudienceType() : 'Unknown',
'Bid Modifier': audience.bidding().getBidModifier()
});
}
} catch (e) {
log('DEBUG', 'Could not fetch ad group audiences: ' + e.message);
}
log('INFO', 'Collected ' + audiences.length + ' audience signals');
return audiences;
}
function getExtensions() {
var extensions = [];
// Sitelinks
try {
var sitelinks = AdsApp.extensions().sitelinks().get();
while (sitelinks.hasNext()) {
var sl = sitelinks.next();
extensions.push({
'Type': 'Sitelink',
'Text': sl.getLinkText(),
'Description 1': sl.getDescription1(),
'Description 2': sl.getDescription2(),
'Final URL': sl.urls().getFinalUrl(),
'Mobile Preferred': sl.isMobilePreferred()
});
}
} catch (e) {
log('DEBUG', 'Could not fetch sitelinks: ' + e.message);
}
// Callouts
try {
var callouts = AdsApp.extensions().callouts().get();
while (callouts.hasNext()) {
var co = callouts.next();
extensions.push({
'Type': 'Callout',
'Text': co.getText(),
'Description 1': '',
'Description 2': '',
'Final URL': '',
'Mobile Preferred': co.isMobilePreferred()
});
}
} catch (e) {
log('DEBUG', 'Could not fetch callouts: ' + e.message);
}
// Structured Snippets
try {
var snippets = AdsApp.extensions().snippets().get();
while (snippets.hasNext()) {
var sn = snippets.next();
extensions.push({
'Type': 'Structured Snippet',
'Text': sn.getHeader() + ': ' + sn.getValues().join(', '),
'Description 1': '',
'Description 2': '',
'Final URL': '',
'Mobile Preferred': sn.isMobilePreferred()
});
}
} catch (e) {
log('DEBUG', 'Could not fetch snippets: ' + e.message);
}
// Call Extensions
try {
var phones = AdsApp.extensions().phoneNumbers().get();
while (phones.hasNext()) {
var ph = phones.next();
extensions.push({
'Type': 'Call',
'Text': ph.getPhoneNumber(),
'Description 1': ph.getCountry(),
'Description 2': '',
'Final URL': '',
'Mobile Preferred': ph.isMobilePreferred()
});
}
} catch (e) {
log('DEBUG', 'Could not fetch phone extensions: ' + e.message);
}
log('INFO', 'Collected ' + extensions.length + ' extensions');
return extensions;
}
function getNetworkSettings() {
var networkSettings = [];
var campaigns = AdsApp.campaigns().withCondition('Status = ENABLED').get();
while (campaigns.hasNext()) {
var campaign = campaigns.next();
var targeting = campaign.targeting();
var networks = [];
try {
if (campaign.getAdvertisingChannelType() === 'SEARCH') {
// For search campaigns, check network settings
networks.push('Search');
// Note: Search partners setting requires checking campaign settings
}
} catch (e) {
// Not available
}
// Get location targeting
var locations = [];
try {
var locIterator = targeting.targetedLocations().get();
while (locIterator.hasNext()) {
var loc = locIterator.next();
locations.push(loc.getName());
}
} catch (e) {
// Not available
}
// Get negative locations
var negLocations = [];
try {
var negLocIterator = targeting.excludedLocations().get();
while (negLocIterator.hasNext()) {
var negLoc = negLocIterator.next();
negLocations.push(negLoc.getName());
}
} catch (e) {
// Not available
}
// Get language targeting
var languages = [];
try {
var langIterator = targeting.languages().get();
while (langIterator.hasNext()) {
var lang = langIterator.next();
languages.push(lang.getName());
}
} catch (e) {
// Not available
}
networkSettings.push({
'Campaign': campaign.getName(),
'Campaign Type': campaign.getAdvertisingChannelType(),
'Targeted Locations': locations.join(', ') || 'All',
'Excluded Locations': negLocations.join(', ') || 'None',
'Languages': languages.join(', ') || 'All',
'Bidding Strategy': campaign.getBiddingStrategyType()
});
}
log('INFO', 'Collected network settings for ' + networkSettings.length + ' campaigns');
return networkSettings;
}
function calculateSummary(snapshot) {
var campaigns = snapshot.campaignStructure;
var enabledCampaigns = campaigns.filter(function(c) { return c.Status === 'ENABLED'; });
var searchCampaigns = campaigns.filter(function(c) { return c['Campaign Type'] === 'SEARCH'; });
var pmaxCampaigns = campaigns.filter(function(c) { return c['Campaign Type'] === 'PERFORMANCE_MAX'; });
var displayCampaigns = campaigns.filter(function(c) { return c['Campaign Type'] === 'DISPLAY'; });
var shoppingCampaigns = campaigns.filter(function(c) { return c['Campaign Type'] === 'SHOPPING'; });
var videoCampaigns = campaigns.filter(function(c) { return c['Campaign Type'] === 'VIDEO'; });
var totalBudget = snapshot.budgetAllocation.reduce(function(sum, b) {
return sum + b['Daily Amount'];
}, 0);
var totalAdGroups = campaigns.reduce(function(sum, c) { return sum + c['Ad Groups']; }, 0);
var totalKeywords = campaigns.reduce(function(sum, c) { return sum + c['Keywords']; }, 0);
var totalAds = campaigns.reduce(function(sum, c) { return sum + c['Ads']; }, 0);
return {
'Total Campaigns': campaigns.length,
'Enabled Campaigns': enabledCampaigns.length,
'Search Campaigns': searchCampaigns.length,
'PMax Campaigns': pmaxCampaigns.length,
'Display Campaigns': displayCampaigns.length,
'Shopping Campaigns': shoppingCampaigns.length,
'Video Campaigns': videoCampaigns.length,
'Total Daily Budget': totalBudget,
'Total Ad Groups': totalAdGroups,
'Total Keywords': totalKeywords,
'Total Ads': totalAds,
'Conversion Actions': snapshot.conversionActions.length,
'Audience Signals': snapshot.audienceSignals.length,
'Extensions': snapshot.extensions.length
};
}
/******************************************************************************
* OUTPUT FUNCTIONS
******************************************************************************/
function initializeSpreadsheet() {
var ss;
if (!CONFIG.SPREADSHEET_URL || CONFIG.SPREADSHEET_URL === 'YOUR_SPREADSHEET_URL_HERE' || CONFIG.SPREADSHEET_URL === 'CREATE_NEW') {
ss = SpreadsheetApp.create('PPC.io Account Snapshot - ' +
AdsApp.currentAccount().getName() + ' - ' +
formatDate(new Date()));
log('INFO', 'Created spreadsheet: ' + ss.getUrl());
} else {
ss = SpreadsheetApp.openByUrl(CONFIG.SPREADSHEET_URL);
}
return ss;
}
function writeAllSheets(ss, snapshot) {
// Summary sheet (first) - position 0
writeSummarySheet(ss, snapshot);
// Numbered sheets for clear navigation
writeSheet(ss, '2. Campaigns', snapshot.campaignStructure,
snapshot.campaignStructure.length > 0 ? Object.keys(snapshot.campaignStructure[0]) : []);
writeSheet(ss, '3. Budgets', snapshot.budgetAllocation,
snapshot.budgetAllocation.length > 0 ? Object.keys(snapshot.budgetAllocation[0]) : []);
writeSheet(ss, '4. Conversion Actions', snapshot.conversionActions,
snapshot.conversionActions.length > 0 ? Object.keys(snapshot.conversionActions[0]) : []);
writeSheet(ss, '5. Audiences', snapshot.audienceSignals,
snapshot.audienceSignals.length > 0 ? Object.keys(snapshot.audienceSignals[0]) : []);
writeSheet(ss, '6. Extensions', snapshot.extensions,
snapshot.extensions.length > 0 ? Object.keys(snapshot.extensions[0]) : []);
writeSheet(ss, '7. Network & Targeting', snapshot.networkSettings,
snapshot.networkSettings.length > 0 ? Object.keys(snapshot.networkSettings[0]) : []);
}
function writeSummarySheet(ss, snapshot) {
var sheet = ss.getSheetByName('1. Summary');
if (!sheet) {
sheet = ss.insertSheet('1. Summary', 0);
} else {
sheet.clear();
}
var data = [
['ACCOUNT SNAPSHOT', ''],
['Generated by PPC.io Script Engine', ''],
['https://ppc.io', ''],
['', ''],
['ACCOUNT DETAILS', ''],
['Account Name', snapshot.accountInfo['Account Name']],
['Account ID', snapshot.accountInfo['Account ID']],
['Currency', snapshot.accountInfo['Currency']],
['Timezone', snapshot.accountInfo['Timezone']],
['Export Date', snapshot.accountInfo['Export Date']],
['', ''],
['PERFORMANCE (' + CONFIG.DATE_RANGE + ')', ''],
['Total Cost', '$' + snapshot.accountInfo['Total Cost'].toFixed(2)],
['Total Clicks', snapshot.accountInfo['Total Clicks']],
['Total Impressions', snapshot.accountInfo['Total Impressions']],
['Total Conversions', snapshot.accountInfo['Total Conversions'].toFixed(2)],
['Total Conv Value', '$' + snapshot.accountInfo['Total Conv Value'].toFixed(2)],
['Avg CPC', '$' + snapshot.accountInfo['Avg CPC'].toFixed(2)],
['CTR', (snapshot.accountInfo['CTR'] * 100).toFixed(2) + '%'],
['Conv Rate', (snapshot.accountInfo['Conv Rate'] * 100).toFixed(2) + '%'],
['CPA', '$' + snapshot.accountInfo['CPA'].toFixed(2)],
['ROAS', snapshot.accountInfo['ROAS'].toFixed(2) + 'x'],
['', ''],
['STRUCTURE OVERVIEW', ''],
['Total Campaigns', snapshot.summary['Total Campaigns']],
['Enabled Campaigns', snapshot.summary['Enabled Campaigns']],
['Search Campaigns', snapshot.summary['Search Campaigns']],
['PMax Campaigns', snapshot.summary['PMax Campaigns']],
['Display Campaigns', snapshot.summary['Display Campaigns']],
['Shopping Campaigns', snapshot.summary['Shopping Campaigns']],
['Video Campaigns', snapshot.summary['Video Campaigns']],
['', ''],
['Total Daily Budget', '$' + snapshot.summary['Total Daily Budget'].toFixed(2)],
['Total Ad Groups', snapshot.summary['Total Ad Groups']],
['Total Keywords', snapshot.summary['Total Keywords']],
['Total Ads', snapshot.summary['Total Ads']],
['', ''],
['ADDITIONAL DATA', ''],
['Conversion Actions', snapshot.summary['Conversion Actions']],
['Audience Signals', snapshot.summary['Audience Signals']],
['Extensions', snapshot.summary['Extensions']],
['', ''],
['AI ANALYSIS PROMPTS', ''],
['Prompt 1', 'What\'s wrong with this account structure? Top 5 issues.'],
['Prompt 2', 'Which campaign types are performing best? Why?'],
['Prompt 3', 'Is budget allocation optimal across campaigns?'],
['Prompt 4', 'What conversion actions should be primary vs secondary?'],
['Prompt 5', 'Prioritize fixes by potential revenue impact.']
];
sheet.getRange(1, 1, data.length, 2).setValues(data);
// Format headers
sheet.getRange(1, 1, 1, 2).setFontWeight('bold').setFontSize(14);
sheet.getRange(5, 1).setFontWeight('bold');
sheet.getRange(12, 1).setFontWeight('bold');
sheet.getRange(24, 1).setFontWeight('bold');
sheet.getRange(38, 1).setFontWeight('bold');
sheet.getRange(42, 1).setFontWeight('bold');
sheet.setColumnWidth(1, 250);
sheet.setColumnWidth(2, 350);
}
function writeSheet(ss, sheetName, data, columns) {
if (!data || data.length === 0) {
log('DEBUG', 'No data for sheet: ' + sheetName);
return;
}
var sheet = ss.getSheetByName(sheetName);
if (!sheet) {
sheet = ss.insertSheet(sheetName);
} else {
sheet.clear();
}
// Headers
sheet.getRange(1, 1, 1, columns.length).setValues([columns]).setFontWeight('bold');
sheet.setFrozenRows(1);
// Data rows
var rows = data.map(function(row) {
return columns.map(function(col) {
var val = row[col];
return val !== null && val !== undefined ? val : '';
});
});
// Batch write
for (var i = 0; i < rows.length; i += CONFIG.BATCH_SIZE) {
var batch = rows.slice(i, Math.min(i + CONFIG.BATCH_SIZE, rows.length));
sheet.getRange(2 + i, 1, batch.length, columns.length).setValues(batch);
}
// Auto-resize columns
for (var col = 1; col <= Math.min(columns.length, 10); col++) {
sheet.autoResizeColumn(col);
}
log('DEBUG', 'Wrote ' + rows.length + ' rows to ' + sheetName);
}
/******************************************************************************
* NOTIFICATION FUNCTIONS
******************************************************************************/
function sendNotifications(snapshot, spreadsheetUrl, startTime) {
var duration = ((new Date() - startTime) / 1000).toFixed(1);
var message = [
'Account Snapshot Export Complete',
'',
'Account: ' + snapshot.accountInfo['Account Name'],
'Date: ' + formatDate(new Date()),
'Duration: ' + duration + 's',
'',
'Summary:',
'- Campaigns: ' + snapshot.summary['Total Campaigns'],
'- Ad Groups: ' + snapshot.summary['Total Ad Groups'],
'- Keywords: ' + snapshot.summary['Total Keywords'],
'- Budgets: ' + snapshot.budgetAllocation.length,
'- Conversion Actions: ' + snapshot.conversionActions.length,
'',
'Report: ' + spreadsheetUrl,
'',
'--',
'Generated by PPC.io Script Engine'
].join('\n');
if (CONFIG.EMAIL_RECIPIENTS && CONFIG.EMAIL_RECIPIENTS.length > 0) {
sendEmail(message, snapshot);
}
if (CONFIG.SLACK_WEBHOOK_URL) {
sendSlack(message, snapshot);
}
}
function sendEmail(message, snapshot) {
var subject = '[PPC.io] Account Snapshot - ' + snapshot.accountInfo['Account Name'];
try {
MailApp.sendEmail({
to: CONFIG.EMAIL_RECIPIENTS.join(','),
subject: subject,
body: message
});
log('INFO', 'Email sent to ' + CONFIG.EMAIL_RECIPIENTS.length + ' recipients');
} catch (e) {
log('ERROR', 'Failed to send email: ' + e.message);
}
}
function sendSlack(message, snapshot) {
var payload = {
text: ':camera: *PPC.io Account Snapshot*\n```' + message + '```',
mrkdwn: true
};
try {
UrlFetchApp.fetch(CONFIG.SLACK_WEBHOOK_URL, {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify(payload)
});
log('INFO', 'Slack notification sent');
} catch (e) {
log('ERROR', 'Failed to send Slack: ' + e.message);
}
}
/******************************************************************************
* UTILITY FUNCTIONS
******************************************************************************/
function checkTimeLimit(startTime) {
var elapsed = (new Date() - startTime) / 1000 / 60;
if (elapsed > CONFIG.TIME_LIMIT_MINUTES) {
throw new Error('TIME_LIMIT: Processed up to current batch after ' +
elapsed.toFixed(1) + ' minutes. Re-run to continue.');
}
}
function log(level, message) {
var levels = { 'DEBUG': 0, 'INFO': 1, 'WARN': 2, 'ERROR': 3 };
if (levels[level] >= levels[CONFIG.LOG_LEVEL]) {
Logger.log('[' + level + '] ' + message);
}
}
function logSummary(snapshot, startTime) {
var duration = ((new Date() - startTime) / 1000).toFixed(1);
log('INFO', '════════════════════════════════════════');
log('INFO', 'ACCOUNT SNAPSHOT COMPLETE');
log('INFO', 'Duration: ' + duration + ' seconds');
log('INFO', 'Campaigns: ' + snapshot.campaignStructure.length);
log('INFO', 'Budgets: ' + snapshot.budgetAllocation.length);
log('INFO', 'Conversion Actions: ' + snapshot.conversionActions.length);
log('INFO', 'Audiences: ' + snapshot.audienceSignals.length);
log('INFO', 'Extensions: ' + snapshot.extensions.length);
log('INFO', '════════════════════════════════════════');
}
function handleFatalError(error, startTime) {
log('ERROR', '════════════════════════════════════════');
log('ERROR', 'FATAL ERROR: ' + error.message);
log('ERROR', 'Stack: ' + error.stack);
log('ERROR', '════════════════════════════════════════');
if (CONFIG.EMAIL_RECIPIENTS && CONFIG.EMAIL_RECIPIENTS.length > 0) {
try {
MailApp.sendEmail({
to: CONFIG.EMAIL_RECIPIENTS.join(','),
subject: '[PPC.io ERROR] Account Snapshot Failed - ' + AdsApp.currentAccount().getName(),
body: 'Script failed after ' + ((new Date() - startTime) / 1000).toFixed(1) +
' seconds.\n\nError: ' + error.message + '\n\nStack:\n' + error.stack
});
} catch (e) {
log('ERROR', 'Could not send error email: ' + e.message);
}
}
}
function formatDate(date) {
return Utilities.formatDate(date, AdsApp.currentAccount().getTimeZone(), 'yyyy-MM-dd');
}