Skip to main content

WA Multiple Templates Sending

Introduction - Problem Overview

When businesses send WhatsApp template messages to customers, the customers often ignore the messages after reading them and don't take any action on the specific templates. For such users, businesses want a feature that enables them to push another template if the user has read the message but hasn't taken any action for a configurable amount of time.

https://docs.google.com/document/d/1AkvXeibAcCQ_50x0YV4uEBRVhS4p8UVzA8i89B6RBYk/edit

How to identify Message is read in whatsapp ?

WhatsApp provides several message statuses that can be received in your webhook when you integrate WhatsApp Business API for messaging. These statuses give you information about the current state of your sent messages.

// Read Receipt - WA Push Msg
if(!request.body.isNetcore && request.body.statuses && request.body.statuses[0] && request.body.statuses[0]['status'] && (request.body.statuses[0]['status'] === 'read' || request.body.statuses[0]['status'] === 'delivered') && request.body.statuses[0]['id'] && request.body.statuses[0]['recipient_id']) {
try {
if(request.body.statuses[0]['status'] === 'read') {
const notif_doc = await WaNotifications.findOneAndUpdate({ psid: request.body.statuses[0]['recipient_id'], wa_msgId: request.body.statuses[0]['id'] }, { msgRead: true, msgReadAt: momentTimezone().tz(TIMEZONE) }).exec()

//template ReEngadgement
if(templatesForReengadgement && notif_doc && notif_doc.template && templateOperation.templatesForReengadgement[notif_doc.template] && templateOperation.templatesForReengadgement[notif_doc.template].isActive){
await sendReEngagementTemplate(templatesForReengadgement, notif_doc)
}
}
} catch (err) {
handleAppError({ err })
}
}

Solution Approach

We will be maintaining the following data in brand_specified_values :

"templateOperation" : {
"templatesForReengadgement" : {
"email_bounce_1" : {
"timeDuration" : 7200000,
"replacementTemplate" : "email_bounce_3",
"variablesToBePickedForComponentvariables" : {
"MSISDN" : "",
"EMAIL" : ""
},
"isUploadedByFile" : false,
"isActive" : true
}
},
"templateOperationalHours" : {
"email_bounce_3" : {
"durationStart" : 8,
"durationEnd" : 21
},
"vic_mnp_hindi_eng2" : {
"durationStart" : 8,
"durationEnd" : 19
}
},
},

Whenever we sent a template and user have read that template then we will be receiving the read status on our webhook. once we receive that we will be doing the following checks :

  • We should have template operations
  • In Template operation we should have Template for re engagement
  • In Template for Re engagement we should have our specific template added
  • That Template should be Active
if(templatesForReengadgement && notif_doc && notif_doc.template && templateOperation.templatesForReengadgement[notif_doc.template] && templateOperation.templatesForReengadgement[notif_doc.template].isActive){
await sendReEngagementTemplate(templatesForReengadgement, notif_doc)
}

Once all the condition are met we will initiate the process to send Re Engagement Template. In This, we check the following condition are met then only initiate the process. The following conditions are checked :

  • Notif Doc should be present
  • Template should be part of templatesForReengagement
  • Template should have timeDuration , Replacement template
  • Template should be marked active in templatesForReengagement

In case all conditions are met then we create job object for the replacement template to push replacement template for user. For picking the dynamic values in the template we have a key "isUploadedByFile" to be marked and we need to mention the variable place from where it needs to be picked.

// For sending Re-engadgement template
const sendReEngagementTemplate = async (templatesForReengadgement, notif_doc) => {
return new Promise(async (resolve, reject) => {
try {
/*------- Re-engagement -------*/
const template = notif_doc.template
if (notif_doc && templatesForReengadgement && templatesForReengadgement[template].timeDuration && templatesForReengadgement[template].replacementTemplate && templatesForReengadgement[template].isActive) {
const delay = templatesForReengadgement[template].timeDuration
let templateVariables = {}
const jobObj = {
notif_id: new Date().getTime(),
action: 'sendIndividualWhatsappMsgFromFile',
msisdn: notif_doc.userInfo.msisdn.toString(),
type: notif_doc.tag ? notif_doc.tag : 'noTag',
template: templatesForReengadgement[template].replacementTemplate ? templatesForReengadgement[template].replacementTemplate : '',
templateVariables,
isReEngagementTemplate: true,
wa_msgId: notif_doc.wa_msgId, //old wa_msgId
psid: notif_doc.psid //old psid
}
if (templatesForReengadgement[template].isUploadedByFile) {
for (let prop in notif_doc.userInfo) {
if (templatesForReengadgement[template].veriablesTobePickedFromNotifDoc.hasOwnProperty(prop)) {
jobObj.templateVariables[prop] = notif_doc.userInfo[prop]
}
}
}
else {
if(templatesForReengadgement[template].variablesToBePickedForComponentvariables){
jobObj.componentVariables = []
for (let prop in templatesForReengadgement[template].variablesToBePickedForComponentvariables) {
if (notif_doc.userInfo.hasOwnProperty(prop)) {
jobObj.componentVariables.push(notif_doc.userInfo[prop])
}
}}
else{
for (let prop in notif_doc.userInfo) {
if (templatesForReengadgement[template].veriablesTobePickedFromNotifDoc.hasOwnProperty(prop)) {
jobObj[prop] = notif_doc.userInfo[prop]
jobObj['componentVariables'].push(notif_doc.userInfo[prop])
}
}
}
}
const { addJobToQueue } = require('./bull/bullFunctions')
await addJobToQueue('sendReEngadgementTemplate', jobObj, { removeOnComplete: true, delay })
resolve('Processed');
} else {
//DOES NOT QUALIFY FOR RE-ENGADGEMENT
}
} catch (err) {
reject(err);
handleAppError({ err, scope: 'sendReEngagementTemplate' })
}
})
}

At time of sending the replacement template we check for the template operational hours and check if there is some action is taken on the original template on which replacement template is being sent. In case user have clicked onto the template then we ll not send the replacement template.

if(templateOperationalHours && data && templateOperationalHours[data.template]){
const hour = moment().add(5,'hours').add(30,'minutes').hour()-1;
const isBetweenOperationalHours = (hour >= templateOperationalHours[data.template].durationStart && hour < templateOperationalHours[data.template].durationEnd);
if (!isBetweenOperationalHours) {
reject("Template out of operational hours !!");
return;
}
}
if(data.isReEngagementTemplate){
const notif_doc = await WaNotifications.findOne({ psid: data.psid, wa_msgId: data.wa_msgId}).exec()
if(notif_doc.isClicked){
reject("Template already clicked, no re-engagement required !!");
return;
}
}

Limitations

Below are the current limitations with the implemented system:

  • Template with same ID can't be chosen as a replacement template.
  • Looping Issues can occur if we Trigger same template as replacement template.