KYZN
WhatsApp Broadcast via Freshchat
Automate WhatsApp messaging campaigns using Freshchat API and Google Apps Script
Last updated: Jan 3, 2026
WhatsApp Broadcast via Freshchat
Google Apps Script Implementation
Main Function: Send Messages
This function handles the entire WhatsApp broadcast workflow including template management, parameter substitution, and delivery tracking.
function sendMessage() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var setupSheet = ss.getSheetByName('setup_freshchat');
// Fetch sheet names from column H on the "setup_freshchat" sheet
var sheetNames = setupSheet.getRange('H2:H' + setupSheet.getLastRow()).getValues().flat().filter(String);
var reportSheet = ss.getSheetByName('wa_blast_report');
// Fetch template and language settings
var languageCode = setupSheet.getRange('B1').getValue();
var headerType = setupSheet.getRange('B2').getValue();
var customParamsConditions = setupSheet.getRange('B3:B7').getValues().flat();
var bodyParamsKeys = setupSheet.getRange('C3:C7').getValues().flat();
// Fetch column letters from setup_freshchat
var phoneNumberColumn = setupSheet.getRange('O2').getValue();
var statusColumn = setupSheet.getRange('L2').getValue();
var templateNameColumn = setupSheet.getRange('M2').getValue();
var mediaUrlColumn = setupSheet.getRange('N2').getValue();
// Log the column letters
Logger.log('Phone Number Column: ' + phoneNumberColumn);
Logger.log('Status Column: ' + statusColumn);
Logger.log('Template Name Column: ' + templateNameColumn);
Logger.log('Media URL Column: ' + mediaUrlColumn);
// Convert column letters to zero-based indices
var phoneNumberIndex = letterToColumnIndex(phoneNumberColumn);
var statusIndex = letterToColumnIndex(statusColumn);
var templateNameIndex = letterToColumnIndex(templateNameColumn);
var mediaUrlIndex = letterToColumnIndex(mediaUrlColumn);
// Log the column indices
Logger.log('Phone Number Index: ' + phoneNumberIndex);
Logger.log('Status Index: ' + statusIndex);
Logger.log('Template Name Index: ' + templateNameIndex);
Logger.log('Media URL Index: ' + mediaUrlIndex);
var reportRows = [];
sheetNames.forEach(function(sheetName) {
var sheet = ss.getSheetByName(sheetName);
if (!sheet) return;
var lastRow = sheet.getLastRow();
var columns = [phoneNumberIndex, statusIndex, templateNameIndex, mediaUrlIndex];
var minColumn = Math.min.apply(null, columns) + 1;
var maxColumn = Math.max.apply(null, columns) + 1;
// Log the range details
Logger.log('Sheet Name: ' + sheetName);
Logger.log('Last Row: ' + lastRow);
Logger.log('Min Column: ' + minColumn);
Logger.log('Max Column: ' + maxColumn);
var dataRange = sheet.getRange(2, minColumn, lastRow - 1, maxColumn - minColumn + 1);
var dataValues = dataRange.getValues();
var statusUpdates = dataValues.map(row => [row[statusIndex - minColumn + 1]]);
dataValues.forEach((row, index) => {
var phoneNumber = row[phoneNumberIndex - minColumn + 1];
var status = row[statusIndex - minColumn + 1];
var templateName = row[templateNameIndex - minColumn + 1];
var mediaUrl = row[mediaUrlIndex - minColumn + 1];
if (status === "Ready") {
try {
// Construct bodyParams based on "Custom" condition
var bodyParams = constructBodyParams(bodyParamsKeys, customParamsConditions, index + 2, sheet);
var payload = {
"from": { "phone_number": "[YOUR PHONE NUMBER]" },
"provider": "whatsapp",
"to": [{ "phone_number": "+" + String(phoneNumber).trim() }],
"data": {
"message_template": {
"storage": "none",
"template_name": templateName,
"namespace": "[YOUR NAMESPACE]",
"language": {
"policy": "deterministic",
"code": languageCode
},
"rich_template_data": {}
}
}
};
// Add header only if headerType is not empty
if (headerType) {
payload.data.message_template.rich_template_data.header = {
"type": headerType,
"media_url": mediaUrl
};
}
// Add body params only if they exist
if (bodyParams.length > 0) {
payload.data.message_template.rich_template_data.body = {
"params": bodyParams
};
}
var options = {
"method": "post",
"contentType": "application/json",
"headers": {
"Authorization": "[YOUR API KEY]"
},
"payload": JSON.stringify(payload),
"muteHttpExceptions": true
};
var response = UrlFetchApp.fetch("https://api.freshchat.com/v2/outbound-messages/whatsapp", options);
var result = JSON.parse(response.getContentText());
Logger.log(result); // Log the full result to understand the response structure
if (result.request_id) {
reportRows.push([new Date(), phoneNumber, templateName, "Success"]);
statusUpdates[index] = ["Done"];
} else {
reportRows.push([new Date(), phoneNumber, templateName, "Failed"]);
statusUpdates[index] = ["Failed"];
Logger.log("Error: " + result.error_message || "Undefined error message");
}
} catch (e) {
reportRows.push([new Date(), phoneNumber, templateName, "Failed"]);
statusUpdates[index] = ["Failed"];
Logger.log("Error: " + e.toString());
}
}
});
if (reportRows.length > 0) {
reportSheet.getRange(reportSheet.getLastRow() + 1, 1, reportRows.length, 4).setValues(reportRows);
}
sheet.getRange('I2:I' + lastRow).setValues(statusUpdates);
});
}Helper Functions
Construct Body Parameters
Maps template parameters from spreadsheet values or custom static values.
function constructBodyParams(bodyParamsKeys, customParamsConditions, rowIndex, sheet) {
var bodyParams = [];
bodyParamsKeys.forEach((key, index) => {
if (customParamsConditions[index] === "Custom") {
bodyParams.push({ "data": key });
} else {
var columnLetter = customParamsConditions[index].split(' ')[1]; // Extracts column letter
if(columnLetter) {
try {
var value = sheet.getRange(columnLetter + rowIndex).getValue();
if (value) {
bodyParams.push({ "data": value });
}
} catch(e) {
Logger.log('Error accessing cell for column ' + columnLetter + ' in row ' + rowIndex + ': ' + e.toString());
}
}
}
});
return bodyParams;
}Convert Column Letter to Index
Converts Excel-style column letters (A, B, C, etc.) to zero-based array indices.
function letterToColumnIndex(letter) {
var columnIndex = 0;
var letters = letter.replace('Column ', '').toUpperCase();
for (var i = 0; i < letters.length; i++) {
columnIndex = columnIndex * 26 + (letters.charCodeAt(i) - 'A'.charCodeAt(0)) + 1;
}
return columnIndex - 1; // Convert to zero-based index
}Configuration Setup
Freshchat API Setup
- Get your API credentials from Freshchat Developer Portal
- Create WhatsApp message templates in Freshchat
- Configure template parameters and language code
Spreadsheet Structure
The solution uses Google Sheets with the following structure:
- setup_freshchat: Configuration sheet with template settings
- contact_lists: Individual sheets with contact data and phone numbers
- wa_blast_report: Report sheet tracking delivery status
Required Configuration
- Phone Number: Your registered WhatsApp business phone number
- API Key: Freshchat API authentication key
- Namespace: Your template namespace from Freshchat
- Language Code: ISO 639-1 language code (e.g., 'en', 'id')
Usage Workflow
- Prepare contact lists in individual sheets
- Mark rows with status "Ready" to send
- Configure template and parameters in setup_freshchat
- Run the
sendMessage()function - Monitor results in wa_blast_report
The script will automatically:
- Fetch all contact lists
- Construct API payloads with proper parameters
- Send messages via Freshchat API
- Track delivery status and report results
- Handle errors gracefully with logging