chore: more front end changes
This commit is contained in:
+193
-209
@@ -1,4 +1,4 @@
|
||||
<script>
|
||||
<script setup>
|
||||
/**
|
||||
* This component handles parsing and sending WhatsApp message templates.
|
||||
* It works as follows:
|
||||
@@ -11,223 +11,201 @@
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { requiredIf } from '@vuelidate/validators';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import Input from 'dashboard/components-next/input/Input.vue';
|
||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
NextButton,
|
||||
// eslint-disable-next-line vue/no-reserved-component-names
|
||||
Input,
|
||||
const props = defineProps({
|
||||
template: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
props: {
|
||||
template: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
});
|
||||
|
||||
const emit = defineEmits(['sendMessage', 'resetTemplate']);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const processVariable = str => {
|
||||
return str.replace(/{{|}}/g, '');
|
||||
};
|
||||
|
||||
const allKeysRequired = value => {
|
||||
const keys = Object.keys(value);
|
||||
return keys.every(key => value[key]);
|
||||
};
|
||||
|
||||
const processedParams = ref({});
|
||||
|
||||
const templateString = computed(() => {
|
||||
return props.template.components.find(component => component.type === 'BODY')
|
||||
.text;
|
||||
});
|
||||
|
||||
const headerComponent = computed(() => {
|
||||
return props.template.components.find(
|
||||
component => component.type === 'HEADER'
|
||||
);
|
||||
});
|
||||
|
||||
const hasMediaHeader = computed(() => {
|
||||
return (
|
||||
headerComponent.value &&
|
||||
headerComponent.value.format &&
|
||||
['IMAGE', 'VIDEO', 'DOCUMENT'].includes(headerComponent.value.format)
|
||||
);
|
||||
});
|
||||
|
||||
const variables = computed(() => {
|
||||
return templateString.value.match(/{{([^}]+)}}/g);
|
||||
});
|
||||
|
||||
const processedString = computed(() => {
|
||||
return templateString.value.replace(/{{([^}]+)}}/g, (match, variable) => {
|
||||
const variableKey = processVariable(variable);
|
||||
return processedParams.value[variableKey] || `{{${variable}}}`;
|
||||
});
|
||||
});
|
||||
|
||||
const v$ = useVuelidate(
|
||||
{
|
||||
processedParams: {
|
||||
requiredIfKeysPresent: requiredIf(variables),
|
||||
allKeysRequired,
|
||||
},
|
||||
},
|
||||
emits: ['sendMessage', 'resetTemplate'],
|
||||
setup(props, { emit }) {
|
||||
const processVariable = str => {
|
||||
return str.replace(/{{|}}/g, '');
|
||||
};
|
||||
{ processedParams }
|
||||
);
|
||||
|
||||
const allKeysRequired = value => {
|
||||
const keys = Object.keys(value);
|
||||
return keys.every(key => value[key]);
|
||||
};
|
||||
const generateVariables = () => {
|
||||
const allVariables = {};
|
||||
|
||||
const processedParams = ref({});
|
||||
|
||||
const templateString = computed(() => {
|
||||
return props.template.components.find(
|
||||
component => component.type === 'BODY'
|
||||
).text;
|
||||
// Process body variables
|
||||
const matchedVariables = templateString.value.match(/{{([^}]+)}}/g);
|
||||
if (matchedVariables) {
|
||||
allVariables.body = {};
|
||||
matchedVariables.forEach(variable => {
|
||||
const key = processVariable(variable);
|
||||
// Special handling for authentication templates
|
||||
if (props.template?.category === 'AUTHENTICATION') {
|
||||
if (
|
||||
key === '1' ||
|
||||
key.toLowerCase().includes('otp') ||
|
||||
key.toLowerCase().includes('code')
|
||||
) {
|
||||
allVariables.body.otp_code = '';
|
||||
} else if (
|
||||
key === '2' ||
|
||||
key.toLowerCase().includes('expiry') ||
|
||||
key.toLowerCase().includes('minute')
|
||||
) {
|
||||
allVariables.body.expiry_minutes = '';
|
||||
} else {
|
||||
allVariables.body[key] = '';
|
||||
}
|
||||
} else {
|
||||
allVariables.body[key] = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const headerComponent = computed(() => {
|
||||
return props.template.components.find(
|
||||
component => component.type === 'HEADER'
|
||||
);
|
||||
});
|
||||
// Add media URL field if template has media header
|
||||
if (hasMediaHeader.value) {
|
||||
if (!allVariables.header) allVariables.header = {};
|
||||
allVariables.header.media_url = '';
|
||||
allVariables.header.media_type = headerComponent.value.format.toLowerCase();
|
||||
}
|
||||
|
||||
const hasMediaHeader = computed(() => {
|
||||
return (
|
||||
headerComponent.value &&
|
||||
headerComponent.value.format &&
|
||||
['IMAGE', 'VIDEO', 'DOCUMENT'].includes(headerComponent.value.format)
|
||||
);
|
||||
});
|
||||
// Process button variables
|
||||
const buttonComponents = props.template.components.filter(
|
||||
component => component.type === 'BUTTONS'
|
||||
);
|
||||
|
||||
const variables = computed(() => {
|
||||
return templateString.value.match(/{{([^}]+)}}/g);
|
||||
});
|
||||
|
||||
const processedString = computed(() => {
|
||||
return templateString.value.replace(/{{([^}]+)}}/g, (match, variable) => {
|
||||
const variableKey = processVariable(variable);
|
||||
return processedParams.value[variableKey] || `{{${variable}}}`;
|
||||
});
|
||||
});
|
||||
|
||||
const v$ = useVuelidate(
|
||||
{
|
||||
processedParams: {
|
||||
requiredIfKeysPresent: requiredIf(variables),
|
||||
allKeysRequired,
|
||||
},
|
||||
},
|
||||
{ processedParams }
|
||||
);
|
||||
|
||||
const generateVariables = () => {
|
||||
const allVariables = {};
|
||||
|
||||
// Process body variables
|
||||
const matchedVariables = templateString.value.match(/{{([^}]+)}}/g);
|
||||
if (matchedVariables) {
|
||||
allVariables.body = {};
|
||||
matchedVariables.forEach(variable => {
|
||||
const key = processVariable(variable);
|
||||
// Special handling for authentication templates
|
||||
if (props.template?.category === 'AUTHENTICATION') {
|
||||
if (
|
||||
key === '1' ||
|
||||
key.toLowerCase().includes('otp') ||
|
||||
key.toLowerCase().includes('code')
|
||||
) {
|
||||
allVariables.body.otp_code = '';
|
||||
} else if (
|
||||
key === '2' ||
|
||||
key.toLowerCase().includes('expiry') ||
|
||||
key.toLowerCase().includes('minute')
|
||||
) {
|
||||
allVariables.body.expiry_minutes = '';
|
||||
} else {
|
||||
allVariables.body[key] = '';
|
||||
buttonComponents.forEach(buttonComponent => {
|
||||
if (buttonComponent.buttons) {
|
||||
buttonComponent.buttons.forEach((button, index) => {
|
||||
// Skip button parameter inputs for authentication templates
|
||||
// as they are auto-populated with OTP codes
|
||||
if (props.template?.category !== 'AUTHENTICATION') {
|
||||
// Handle URL buttons with variables
|
||||
if (
|
||||
button.type === 'URL' &&
|
||||
button.url &&
|
||||
button.url.includes('{{')
|
||||
) {
|
||||
const buttonVars = button.url.match(/{{([^}]+)}}/g) || [];
|
||||
if (buttonVars.length > 0) {
|
||||
if (!allVariables.buttons) allVariables.buttons = [];
|
||||
allVariables.buttons[index] = {
|
||||
type: 'url',
|
||||
parameter: '',
|
||||
url: button.url,
|
||||
variables: buttonVars.map(v => processVariable(v)),
|
||||
};
|
||||
}
|
||||
} else {
|
||||
allVariables.body[key] = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Add media URL field if template has media header
|
||||
if (hasMediaHeader.value) {
|
||||
if (!allVariables.header) allVariables.header = {};
|
||||
allVariables.header.media_url = '';
|
||||
allVariables.header.media_type =
|
||||
headerComponent.value.format.toLowerCase();
|
||||
}
|
||||
|
||||
// Process button variables
|
||||
const buttonComponents = props.template.components.filter(
|
||||
component => component.type === 'BUTTONS'
|
||||
);
|
||||
|
||||
buttonComponents.forEach(buttonComponent => {
|
||||
if (buttonComponent.buttons) {
|
||||
buttonComponent.buttons.forEach((button, index) => {
|
||||
// Skip button parameter inputs for authentication templates
|
||||
// as they are auto-populated with OTP codes
|
||||
if (props.template?.category !== 'AUTHENTICATION') {
|
||||
// Handle URL buttons with variables
|
||||
if (
|
||||
button.type === 'URL' &&
|
||||
button.url &&
|
||||
button.url.includes('{{')
|
||||
) {
|
||||
const buttonVars = button.url.match(/{{([^}]+)}}/g) || [];
|
||||
if (buttonVars.length > 0) {
|
||||
if (!allVariables.buttons) allVariables.buttons = [];
|
||||
allVariables.buttons[index] = {
|
||||
type: 'url',
|
||||
parameter: '',
|
||||
url: button.url,
|
||||
variables: buttonVars.map(v => processVariable(v)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Handle copy code buttons
|
||||
if (button.type === 'COPY_CODE') {
|
||||
if (!allVariables.buttons) allVariables.buttons = [];
|
||||
allVariables.buttons[index] = {
|
||||
type: 'copy_code',
|
||||
parameter: '',
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
// Handle copy code buttons
|
||||
if (button.type === 'COPY_CODE') {
|
||||
if (!allVariables.buttons) allVariables.buttons = [];
|
||||
allVariables.buttons[index] = {
|
||||
type: 'copy_code',
|
||||
parameter: '',
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
processedParams.value = allVariables;
|
||||
};
|
||||
|
||||
const resetTemplate = () => {
|
||||
emit('resetTemplate');
|
||||
};
|
||||
|
||||
const updateMediaUrl = value => {
|
||||
if (!processedParams.value.header) {
|
||||
processedParams.value.header = {};
|
||||
}
|
||||
processedParams.value.header.media_url = value;
|
||||
};
|
||||
|
||||
const sendMessage = () => {
|
||||
v$.value.$touch();
|
||||
if (v$.value.$invalid) return;
|
||||
|
||||
// Auto-populate button parameters for authentication templates
|
||||
const finalParams = { ...processedParams.value };
|
||||
if (
|
||||
props.template?.category === 'AUTHENTICATION' &&
|
||||
finalParams.buttons
|
||||
) {
|
||||
finalParams.buttons.forEach((button, index) => {
|
||||
if (button.type === 'url') {
|
||||
// For authentication templates, auto-populate URL button parameter with OTP
|
||||
if (finalParams.body?.['1']) {
|
||||
finalParams.buttons[index].parameter = finalParams.body['1'];
|
||||
} else if (finalParams.body?.otp_code) {
|
||||
finalParams.buttons[index].parameter = finalParams.body.otp_code;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const payload = {
|
||||
message: processedString.value,
|
||||
templateParams: {
|
||||
name: props.template.name,
|
||||
category: props.template.category,
|
||||
language: props.template.language,
|
||||
namespace: props.template.namespace,
|
||||
processed_params: finalParams,
|
||||
},
|
||||
};
|
||||
emit('sendMessage', payload);
|
||||
};
|
||||
|
||||
onMounted(generateVariables);
|
||||
|
||||
return {
|
||||
processedParams,
|
||||
variables,
|
||||
templateString,
|
||||
processedString,
|
||||
headerComponent,
|
||||
hasMediaHeader,
|
||||
v$,
|
||||
resetTemplate,
|
||||
sendMessage,
|
||||
updateMediaUrl,
|
||||
};
|
||||
},
|
||||
processedParams.value = allVariables;
|
||||
};
|
||||
|
||||
const resetTemplate = () => {
|
||||
emit('resetTemplate');
|
||||
};
|
||||
|
||||
const updateMediaUrl = value => {
|
||||
if (!processedParams.value.header) {
|
||||
processedParams.value.header = {};
|
||||
}
|
||||
processedParams.value.header.media_url = value;
|
||||
};
|
||||
|
||||
const sendMessage = () => {
|
||||
v$.value.$touch();
|
||||
if (v$.value.$invalid) return;
|
||||
|
||||
// Auto-populate button parameters for authentication templates
|
||||
const finalParams = { ...processedParams.value };
|
||||
if (props.template?.category === 'AUTHENTICATION' && finalParams.buttons) {
|
||||
finalParams.buttons.forEach((button, index) => {
|
||||
if (button.type === 'url') {
|
||||
// For authentication templates, auto-populate URL button parameter with OTP
|
||||
if (finalParams.body?.['1']) {
|
||||
finalParams.buttons[index].parameter = finalParams.body['1'];
|
||||
} else if (finalParams.body?.otp_code) {
|
||||
finalParams.buttons[index].parameter = finalParams.body.otp_code;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const payload = {
|
||||
message: processedString.value,
|
||||
templateParams: {
|
||||
name: props.template.name,
|
||||
category: props.template.category,
|
||||
language: props.template.language,
|
||||
namespace: props.template.namespace,
|
||||
processed_params: finalParams,
|
||||
},
|
||||
};
|
||||
emit('sendMessage', payload);
|
||||
};
|
||||
|
||||
onMounted(generateVariables);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -241,6 +219,7 @@ export default {
|
||||
{{ template.name }}
|
||||
</h3>
|
||||
<span class="text-xs text-n-slate-11">
|
||||
{{ t('WHATSAPP_TEMPLATES.PARSER.LANGUAGE') }}:
|
||||
{{ template.language || 'en' }}
|
||||
</span>
|
||||
</div>
|
||||
@@ -254,12 +233,12 @@ export default {
|
||||
</div>
|
||||
|
||||
<div class="text-xs text-n-slate-11">
|
||||
{{ t('WHATSAPP_TEMPLATES.PARSER.CATEGORY') }}:
|
||||
{{ template.category || 'UTILITY' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="variables || hasMediaHeader">
|
||||
<!-- Media Header Section -->
|
||||
<div v-if="hasMediaHeader" class="mb-4">
|
||||
<p class="mb-2.5 text-sm font-semibold">
|
||||
{{
|
||||
@@ -279,7 +258,13 @@ export default {
|
||||
:model-value="processedParams.header?.media_url || ''"
|
||||
type="url"
|
||||
class="flex-1"
|
||||
:placeholder="`Enter ${headerComponent.format.toLowerCase()} URL`"
|
||||
:placeholder="
|
||||
t('WHATSAPP_TEMPLATES.PARSER.MEDIA_URL_LABEL', {
|
||||
type:
|
||||
headerComponent.format.charAt(0) +
|
||||
headerComponent.format.slice(1).toLowerCase(),
|
||||
})
|
||||
"
|
||||
@update:model-value="updateMediaUrl"
|
||||
/>
|
||||
</div>
|
||||
@@ -310,10 +295,12 @@ export default {
|
||||
class="flex-1"
|
||||
:placeholder="
|
||||
key === 'otp_code'
|
||||
? 'Enter 4-8 digit OTP'
|
||||
? t('WHATSAPP_TEMPLATES.PARSER.OTP_CODE')
|
||||
: key === 'expiry_minutes'
|
||||
? 'Enter expiry minutes'
|
||||
: `Enter ${key} value`
|
||||
? t('WHATSAPP_TEMPLATES.PARSER.EXPIRY_MINUTES')
|
||||
: t('WHATSAPP_TEMPLATES.PARSER.VARIABLE_PLACEHOLDER', {
|
||||
variable: key,
|
||||
})
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
@@ -322,10 +309,7 @@ export default {
|
||||
<!-- Button Variables Section -->
|
||||
<div v-if="processedParams.buttons">
|
||||
<p class="mb-2.5 text-sm font-semibold">
|
||||
{{
|
||||
$t('WHATSAPP_TEMPLATES.PARSER.BUTTON_PARAMETERS') ||
|
||||
'Button Parameters'
|
||||
}}
|
||||
{{ t('WHATSAPP_TEMPLATES.PARSER.BUTTON_PARAMETERS') }}
|
||||
</p>
|
||||
<div
|
||||
v-for="(button, index) in processedParams.buttons"
|
||||
@@ -339,8 +323,8 @@ export default {
|
||||
class="flex-1"
|
||||
:placeholder="
|
||||
button.type === 'copy_code'
|
||||
? 'Enter coupon code (max 15 chars)'
|
||||
: 'Enter button parameter'
|
||||
? t('WHATSAPP_TEMPLATES.PARSER.COUPON_CODE')
|
||||
: t('WHATSAPP_TEMPLATES.PARSER.BUTTON_PARAMETER')
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,42 +1,45 @@
|
||||
{
|
||||
"WHATSAPP_TEMPLATES": {
|
||||
"MODAL": {
|
||||
"TITLE": "Whatsapp Templates",
|
||||
"SUBTITLE": "Select the whatsapp template you want to send",
|
||||
"TEMPLATE_SELECTED_SUBTITLE": "Configure template: {templateName}"
|
||||
},
|
||||
"PICKER": {
|
||||
"SEARCH_PLACEHOLDER": "Search Templates",
|
||||
"NO_TEMPLATES_FOUND": "No templates found for",
|
||||
"HEADER": "Header",
|
||||
"BODY": "Body",
|
||||
"FOOTER": "Footer",
|
||||
"BUTTONS": "Buttons",
|
||||
"CATEGORY": "Category",
|
||||
"MEDIA_CONTENT": "Media Content",
|
||||
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
|
||||
"REFRESH_BUTTON": "Refresh templates",
|
||||
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
|
||||
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
|
||||
"LABELS": {
|
||||
"LANGUAGE": "Language",
|
||||
"TEMPLATE_BODY": "Template Body",
|
||||
"CATEGORY": "Category"
|
||||
}
|
||||
},
|
||||
"PARSER": {
|
||||
"VARIABLES_LABEL": "Variables",
|
||||
"VARIABLE_PLACEHOLDER": "Enter {variable} value",
|
||||
"GO_BACK_LABEL": "Go Back",
|
||||
"SEND_MESSAGE_LABEL": "Send Message",
|
||||
"FORM_ERROR_MESSAGE": "Please fill all variables before sending",
|
||||
"MEDIA_HEADER_LABEL": "{type} Header",
|
||||
"MEDIA_URL_LABEL": "{type} URL",
|
||||
"OTP_CODE": "OTP Code",
|
||||
"EXPIRY_MINUTES": "Expiry (minutes)",
|
||||
"BUTTON_PARAMETERS": "Button Parameters",
|
||||
"BUTTON_LABEL": "Button {index}",
|
||||
"COUPON_CODE": "Coupon Code"
|
||||
}
|
||||
"WHATSAPP_TEMPLATES": {
|
||||
"MODAL": {
|
||||
"TITLE": "Whatsapp Templates",
|
||||
"SUBTITLE": "Select the whatsapp template you want to send",
|
||||
"TEMPLATE_SELECTED_SUBTITLE": "Configure template: {templateName}"
|
||||
},
|
||||
"PICKER": {
|
||||
"SEARCH_PLACEHOLDER": "Search Templates",
|
||||
"NO_TEMPLATES_FOUND": "No templates found for",
|
||||
"HEADER": "Header",
|
||||
"BODY": "Body",
|
||||
"FOOTER": "Footer",
|
||||
"BUTTONS": "Buttons",
|
||||
"CATEGORY": "Category",
|
||||
"MEDIA_CONTENT": "Media Content",
|
||||
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
|
||||
"REFRESH_BUTTON": "Refresh templates",
|
||||
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
|
||||
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
|
||||
"LABELS": {
|
||||
"LANGUAGE": "Language",
|
||||
"TEMPLATE_BODY": "Template Body",
|
||||
"CATEGORY": "Category"
|
||||
}
|
||||
},
|
||||
"PARSER": {
|
||||
"VARIABLES_LABEL": "Variables",
|
||||
"LANGUAGE": "Language",
|
||||
"CATEGORY": "Category",
|
||||
"VARIABLE_PLACEHOLDER": "Enter {variable} value",
|
||||
"GO_BACK_LABEL": "Go Back",
|
||||
"SEND_MESSAGE_LABEL": "Send Message",
|
||||
"FORM_ERROR_MESSAGE": "Please fill all variables before sending",
|
||||
"MEDIA_HEADER_LABEL": "{type} Header",
|
||||
"OTP_CODE": "Enter 4-8 digit OTP",
|
||||
"EXPIRY_MINUTES": "Enter expiry minutes",
|
||||
"BUTTON_PARAMETERS": "Button Parameters",
|
||||
"BUTTON_LABEL": "Button {index}",
|
||||
"COUPON_CODE": "Enter coupon code (max 15 chars)",
|
||||
"MEDIA_URL_LABEL": "Enter {type} URL",
|
||||
"BUTTON_PARAMETER": "Enter button parameter"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-538
@@ -1,538 +0,0 @@
|
||||
[
|
||||
{
|
||||
"id": "1381151706284063",
|
||||
"name": "event_invitation_static",
|
||||
"status": "APPROVED",
|
||||
"category": "MARKETING",
|
||||
"language": "en",
|
||||
"components": [
|
||||
{
|
||||
"text": "You're invited to {{event_name}} at {{location}}, Join us for an amazing experience!",
|
||||
"type": "BODY",
|
||||
"example": {
|
||||
"body_text_named_params": [
|
||||
{
|
||||
"example": "F1",
|
||||
"param_name": "event_name"
|
||||
},
|
||||
{
|
||||
"example": "Dubai",
|
||||
"param_name": "location"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "BUTTONS",
|
||||
"buttons": [
|
||||
{
|
||||
"url": "https://events.example.com/register",
|
||||
"text": "Visit website",
|
||||
"type": "URL"
|
||||
},
|
||||
{
|
||||
"url": "https://maps.app.goo.gl/YoWAzRj1GDuxs6qz8",
|
||||
"text": "Get Directions",
|
||||
"type": "URL"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"sub_category": "CUSTOM",
|
||||
"parameter_format": "NAMED"
|
||||
},
|
||||
{
|
||||
"id": "767076159336759",
|
||||
"name": "purchase_receipt",
|
||||
"status": "APPROVED",
|
||||
"category": "UTILITY",
|
||||
"language": "en_US",
|
||||
"components": [
|
||||
{
|
||||
"type": "HEADER",
|
||||
"format": "DOCUMENT",
|
||||
"example": {
|
||||
"header_handle": [
|
||||
"https://scontent.whatsapp.net/v/t61.29466-34/521502500_767076162670092_7371147273350347509_n.pdf?ccb=1-7&_nc_sid=8b1bef&_nc_ohc=x2s0Gbzdo68Q7kNvwFzj3k-&_nc_oc=AdkXq_pVHoQPmYQzalo1ND-wfvQT9GC_jJJphgVOxpqcTRktALRNit4t0tuNcX93kzU&_nc_zt=3&_nc_ht=scontent.whatsapp.net&edm=AH51TzQEAAAA&_nc_gid=OShKjgNv2PbNF9AIEuRgZA&oh=01_Q5Aa2AEmTve8tKHAhT_FIvARW7CFUeGf9d_1wiw3QUGu-FW_tQ&oe=68B1370B"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"text": "Thank you for using your {{1}} card at {{2}}. Your {{3}} is attached as a PDF.",
|
||||
"type": "BODY",
|
||||
"example": {
|
||||
"body_text": [
|
||||
[
|
||||
"credit",
|
||||
"CS Mutual",
|
||||
"receipt"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameter_format": "POSITIONAL",
|
||||
"library_template_name": "purchase_receipt_2"
|
||||
},
|
||||
{
|
||||
"id": "729007633084301",
|
||||
"name": "basic_otp",
|
||||
"status": "APPROVED",
|
||||
"category": "AUTHENTICATION",
|
||||
"language": "en",
|
||||
"components": [
|
||||
{
|
||||
"text": "*{{1}}* is your verification code. For your security, do not share this code.",
|
||||
"type": "BODY",
|
||||
"example": {
|
||||
"body_text": [
|
||||
[
|
||||
"123456"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "BUTTONS",
|
||||
"buttons": [
|
||||
{
|
||||
"url": "https://www.whatsapp.com/otp/code/?otp_type=COPY_CODE&code=otp{{1}}",
|
||||
"text": "Copy code",
|
||||
"type": "URL",
|
||||
"example": [
|
||||
"https://www.whatsapp.com/otp/code/?otp_type=COPY_CODE&code=otp123456"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"parameter_format": "POSITIONAL",
|
||||
"message_send_ttl_seconds": 600
|
||||
},
|
||||
{
|
||||
"id": "1145229434109366",
|
||||
"name": "secure_login_otp",
|
||||
"status": "APPROVED",
|
||||
"category": "AUTHENTICATION",
|
||||
"language": "en",
|
||||
"components": [
|
||||
{
|
||||
"text": "*{{1}}* is your verification code. For your security, do not share this code.",
|
||||
"type": "BODY",
|
||||
"example": {
|
||||
"body_text": [
|
||||
[
|
||||
"123456"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"text": "This code expires in 10 minutes.",
|
||||
"type": "FOOTER"
|
||||
},
|
||||
{
|
||||
"type": "BUTTONS",
|
||||
"buttons": [
|
||||
{
|
||||
"url": "https://www.whatsapp.com/otp/code/?otp_type=ZERO_TAP&cta_display_name=Autofill&package_name=com.chatwoot.app&signature_hash=12121212121&code_expiration_minutes=10&code=otp{{1}}",
|
||||
"text": "Copy code",
|
||||
"type": "URL",
|
||||
"example": [
|
||||
"https://www.whatsapp.com/otp/code/?otp_type=ZERO_TAP&cta_display_name=Autofill&package_name=com.chatwoot.app&signature_hash=12121212121&code_expiration_minutes=10&code=otp123456"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"parameter_format": "POSITIONAL",
|
||||
"message_send_ttl_seconds": 600
|
||||
},
|
||||
{
|
||||
"id": "1469258364071127",
|
||||
"name": "discount_coupon",
|
||||
"status": "APPROVED",
|
||||
"category": "MARKETING",
|
||||
"language": "en",
|
||||
"components": [
|
||||
{
|
||||
"text": "🎉 Special offer for you! Get {{discount_percentage}}% off your next purchase. Use the code below at checkout",
|
||||
"type": "BODY",
|
||||
"example": {
|
||||
"body_text_named_params": [
|
||||
{
|
||||
"example": "30",
|
||||
"param_name": "discount_percentage"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "BUTTONS",
|
||||
"buttons": [
|
||||
{
|
||||
"text": "Copy offer code",
|
||||
"type": "COPY_CODE",
|
||||
"example": [
|
||||
"SAVE1OFF"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"sub_category": "CUSTOM",
|
||||
"parameter_format": "NAMED"
|
||||
},
|
||||
{
|
||||
"id": "1075221534579807",
|
||||
"name": "support_callback",
|
||||
"status": "APPROVED",
|
||||
"category": "UTILITY",
|
||||
"language": "en",
|
||||
"components": [
|
||||
{
|
||||
"text": "Hello {{name}}, our support team will call you regarding ticket # {{ticket_id}}.",
|
||||
"type": "BODY",
|
||||
"example": {
|
||||
"body_text_named_params": [
|
||||
{
|
||||
"example": "muhsin",
|
||||
"param_name": "name"
|
||||
},
|
||||
{
|
||||
"example": "232323",
|
||||
"param_name": "ticket_id"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "BUTTONS",
|
||||
"buttons": [
|
||||
{
|
||||
"text": "Call Support",
|
||||
"type": "PHONE_NUMBER",
|
||||
"phone_number": "+16506677566"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"sub_category": "CUSTOM",
|
||||
"parameter_format": "NAMED"
|
||||
},
|
||||
{
|
||||
"id": "1023596726651144",
|
||||
"name": "training_video",
|
||||
"status": "APPROVED",
|
||||
"category": "MARKETING",
|
||||
"language": "en",
|
||||
"components": [
|
||||
{
|
||||
"type": "HEADER",
|
||||
"format": "VIDEO",
|
||||
"example": {
|
||||
"header_handle": [
|
||||
"https://scontent.whatsapp.net/v/t61.29466-34/521582686_1023596729984477_1872358575355618432_n.mp4?ccb=1-7&_nc_sid=8b1bef&_nc_ohc=MPOPgVrub7QQ7kNvwGMm61X&_nc_oc=AdnrE_z6RpqXyeEwVwrhiBQlpBy8DrqDD2QhPPQJaGj-97a93gS5jIVPzttJya__2tI&_nc_zt=28&_nc_ht=scontent.whatsapp.net&edm=AH51TzQEAAAA&_nc_gid=OShKjgNv2PbNF9AIEuRgZA&oh=01_Q5Aa2AFzaevej9gu9OdX2oy0aYNQqkkogb1mwGKDfTTi83fv0Q&oe=68B12B15"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"text": "Hi {{name}}, here's your training video. Please watch by{{date}}.",
|
||||
"type": "BODY",
|
||||
"example": {
|
||||
"body_text_named_params": [
|
||||
{
|
||||
"example": "john",
|
||||
"param_name": "name"
|
||||
},
|
||||
{
|
||||
"example": "July 31",
|
||||
"param_name": "date"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"sub_category": "CUSTOM",
|
||||
"parameter_format": "NAMED"
|
||||
},
|
||||
{
|
||||
"id": "1106685194739985",
|
||||
"name": "order_confirmation",
|
||||
"status": "APPROVED",
|
||||
"category": "MARKETING",
|
||||
"language": "en",
|
||||
"components": [
|
||||
{
|
||||
"type": "HEADER",
|
||||
"format": "IMAGE",
|
||||
"example": {
|
||||
"header_handle": [
|
||||
"https://scontent.whatsapp.net/v/t61.29466-34/518466505_1106685198073318_3569250580697484416_n.jpg?ccb=1-7&_nc_sid=8b1bef&_nc_ohc=pzLMH8hPY0EQ7kNvwGidwBU&_nc_oc=Adn_w6yCXTBXnTtrYwxz7REXJFo_Oyy5OBtOMTFvc9_gNurQhQIiBvc1_xernpemAzw&_nc_zt=3&_nc_ht=scontent.whatsapp.net&edm=AH51TzQEAAAA&_nc_gid=OShKjgNv2PbNF9AIEuRgZA&oh=01_Q5Aa2AEHx0zLxNWnsUOwq5pcl8lyklT3fahHJXPw0ey25AfP3g&oe=68B13A47"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"text": "Hi your order {{1}} is confirmed. Please wait for further updates",
|
||||
"type": "BODY",
|
||||
"example": {
|
||||
"body_text": [
|
||||
[
|
||||
"blue canvas shoes"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"sub_category": "CUSTOM",
|
||||
"parameter_format": "POSITIONAL"
|
||||
},
|
||||
{
|
||||
"id": "1242180011253003",
|
||||
"name": "product_launch",
|
||||
"status": "APPROVED",
|
||||
"category": "MARKETING",
|
||||
"language": "en",
|
||||
"components": [
|
||||
{
|
||||
"type": "HEADER",
|
||||
"format": "IMAGE",
|
||||
"example": {
|
||||
"header_handle": [
|
||||
"https://scontent.whatsapp.net/v/t61.29466-34/510408113_1242180014586336_1564209110680717071_n.jpg?ccb=1-7&_nc_sid=8b1bef&_nc_ohc=5XTMSTbB6OsQ7kNvwHmy1uY&_nc_oc=AdlAssE60GFSe_C27DSuF16i8N7XbG5V68WxDwiZZCK2zQbouc0ItRm3vAHHD6vybC8&_nc_zt=3&_nc_ht=scontent.whatsapp.net&edm=AH51TzQEAAAA&_nc_gid=OShKjgNv2PbNF9AIEuRgZA&oh=01_Q5Aa2AFxHLQFYq2X4WlzHDCQ-Jb-bpde--W4v1cDXwxwwgY_yw&oe=68B1315C"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"text": "New arrival! Our stunning coat now available in {{color}} color.",
|
||||
"type": "BODY",
|
||||
"example": {
|
||||
"body_text_named_params": [
|
||||
{
|
||||
"example": "blue",
|
||||
"param_name": "color"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"text": "Free shipping on orders over $100. Limited time offer.",
|
||||
"type": "FOOTER"
|
||||
}
|
||||
],
|
||||
"sub_category": "CUSTOM",
|
||||
"parameter_format": "NAMED"
|
||||
},
|
||||
{
|
||||
"id": "1449876326175680",
|
||||
"name": "technician_visit",
|
||||
"status": "APPROVED",
|
||||
"category": "UTILITY",
|
||||
"language": "en_US",
|
||||
"components": [
|
||||
{
|
||||
"text": "Technician visit",
|
||||
"type": "HEADER",
|
||||
"format": "TEXT"
|
||||
},
|
||||
{
|
||||
"text": "Hi {{1}}, we're scheduling a technician visit to {{2}} on {{3}} between {{4}} and {{5}}. Please confirm if this time slot works for you.",
|
||||
"type": "BODY",
|
||||
"example": {
|
||||
"body_text": [
|
||||
[
|
||||
"John",
|
||||
"123 Maple St",
|
||||
"2025-12-31",
|
||||
"10:00 AM",
|
||||
"2:00 PM"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "BUTTONS",
|
||||
"buttons": [
|
||||
{
|
||||
"text": "Confirm",
|
||||
"type": "QUICK_REPLY"
|
||||
},
|
||||
{
|
||||
"text": "Reschedule",
|
||||
"type": "QUICK_REPLY"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"parameter_format": "POSITIONAL",
|
||||
"library_template_name": "appointment_scheduling_address"
|
||||
},
|
||||
{
|
||||
"id": "997298832221901",
|
||||
"name": "greet",
|
||||
"status": "APPROVED",
|
||||
"category": "MARKETING",
|
||||
"language": "en",
|
||||
"components": [
|
||||
{
|
||||
"text": "Hey {{customer_name}} how may I help you?",
|
||||
"type": "BODY",
|
||||
"example": {
|
||||
"body_text_named_params": [
|
||||
{
|
||||
"example": "John",
|
||||
"param_name": "customer_name"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"sub_category": "CUSTOM",
|
||||
"parameter_format": "NAMED"
|
||||
},
|
||||
{
|
||||
"id": "632315222954611",
|
||||
"name": "hello_world",
|
||||
"status": "APPROVED",
|
||||
"category": "UTILITY",
|
||||
"language": "en_US",
|
||||
"components": [
|
||||
{
|
||||
"text": "Hello World",
|
||||
"type": "HEADER",
|
||||
"format": "TEXT"
|
||||
},
|
||||
{
|
||||
"text": "Welcome and congratulations!! This message demonstrates your ability to send a WhatsApp message notification from the Cloud API, hosted by Meta. Thank you for taking the time to test with us.",
|
||||
"type": "BODY"
|
||||
},
|
||||
{
|
||||
"text": "WhatsApp Business Platform sample message",
|
||||
"type": "FOOTER"
|
||||
}
|
||||
],
|
||||
"parameter_format": "POSITIONAL"
|
||||
},
|
||||
{
|
||||
"id": "1005921651322035",
|
||||
"name": "otp_verification",
|
||||
"status": "APPROVED",
|
||||
"category": "AUTHENTICATION",
|
||||
"language": "en_US",
|
||||
"components": [
|
||||
{
|
||||
"text": "Use code *{{1}}* to verify your transaction of {{2}}.",
|
||||
"type": "BODY",
|
||||
"example": {
|
||||
"body_text": [
|
||||
[
|
||||
"123456",
|
||||
"$12.34"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "BUTTONS",
|
||||
"buttons": [
|
||||
{
|
||||
"url": "https://www.whatsapp.com/otp/code/?otp_type=COPY_CODE&code=otp{{1}}",
|
||||
"text": "Copy code",
|
||||
"type": "URL",
|
||||
"example": [
|
||||
"https://www.whatsapp.com/otp/code/?otp_type=COPY_CODE&code=otp123456"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"parameter_format": "POSITIONAL",
|
||||
"library_template_name": "verify_transaction_2",
|
||||
"message_send_ttl_seconds": 600
|
||||
},
|
||||
{
|
||||
"id": "787864066907971",
|
||||
"name": "feedback_request",
|
||||
"status": "APPROVED",
|
||||
"category": "MARKETING",
|
||||
"language": "en",
|
||||
"components": [
|
||||
{
|
||||
"text": "Hey {{name}}, how was your experience with Puma? We'd love your feedback!",
|
||||
"type": "BODY",
|
||||
"example": {
|
||||
"body_text_named_params": [
|
||||
{
|
||||
"example": "muhsin",
|
||||
"param_name": "name"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "BUTTONS",
|
||||
"buttons": [
|
||||
{
|
||||
"url": "https://feedback.example.com/survey",
|
||||
"text": "Leave Feedback",
|
||||
"type": "URL"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"sub_category": "CUSTOM",
|
||||
"parameter_format": "NAMED"
|
||||
},
|
||||
{
|
||||
"id": "1938057163677205",
|
||||
"name": "address_update",
|
||||
"status": "APPROVED",
|
||||
"category": "UTILITY",
|
||||
"language": "en_US",
|
||||
"components": [
|
||||
{
|
||||
"text": "Address update",
|
||||
"type": "HEADER",
|
||||
"format": "TEXT"
|
||||
},
|
||||
{
|
||||
"text": "Hi {{1}}, your delivery address has been successfully updated to {{2}}. Contact {{3}} for any inquiries.",
|
||||
"type": "BODY",
|
||||
"example": {
|
||||
"body_text": [
|
||||
[
|
||||
"John",
|
||||
"123 Main St",
|
||||
"support@telco.com"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameter_format": "POSITIONAL",
|
||||
"library_template_name": "address_update"
|
||||
},
|
||||
{
|
||||
"id": "1644094842949394",
|
||||
"name": "delivery_confirmation",
|
||||
"status": "APPROVED",
|
||||
"category": "UTILITY",
|
||||
"language": "en_US",
|
||||
"components": [
|
||||
{
|
||||
"text": "{{1}}, your order was successfully delivered on {{2}}.\n\nThank you for your purchase.\n",
|
||||
"type": "BODY",
|
||||
"example": {
|
||||
"body_text": [
|
||||
[
|
||||
"John",
|
||||
"Jan 1, 2024"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameter_format": "POSITIONAL",
|
||||
"library_template_name": "delivery_confirmation_4"
|
||||
}
|
||||
]
|
||||
@@ -1,351 +0,0 @@
|
||||
# WhatsApp Template Testing Cases
|
||||
|
||||
This document contains test cases for WhatsApp template functionality in Chatwoot, with screenshots showing expected behavior in both Chatwoot UI and WhatsApp delivery.
|
||||
|
||||
### hello_world
|
||||
|
||||
**Template Details:**
|
||||
- **Name**: `hello_world`
|
||||
- **Category**: UTILITY
|
||||
- **Type**: Text Header + Body + Footer
|
||||
- **Components**:
|
||||
- Header: "Hello World" (TEXT)
|
||||
- Body: "Welcome and congratulations!! This message demonstrates your ability to send a WhatsApp message notification from the Cloud API, hosted by Meta. Thank you for taking the time to test with us."
|
||||
- Footer: "WhatsApp Business Platform sample message"
|
||||
|
||||
|
||||
**Chatwoot UI**
|
||||
|
||||

|
||||
|
||||
**WhatsApp Delivery**
|
||||
|
||||
|
||||

|
||||
|
||||
### greet
|
||||
|
||||
**Template Details:**
|
||||
- **Name**: `greet`
|
||||
- **Category**: MARKETING
|
||||
- **Type**: Text Only (Body with Named Parameter)
|
||||
- **Components**:
|
||||
- Body: "Hey {{customer_name}} how may I help you?"
|
||||
- **Parameters**:
|
||||
- `customer_name` (NAMED parameter format)
|
||||
|
||||
**Chatwoot UI**
|
||||
|
||||

|
||||
|
||||
**WhatsApp Delivery**
|
||||
|
||||

|
||||
|
||||
### delivery_confirmation
|
||||
|
||||
**Template Details:**
|
||||
- **Name**: `delivery_confirmation`
|
||||
- **Category**: UTILITY
|
||||
- **Type**: Text Only (Body with Positional Parameters)
|
||||
- **Components**:
|
||||
- Body: "{{1}}, your order was successfully delivered on {{2}}.\n\nThank you for your purchase.\n"
|
||||
- **Parameters**:
|
||||
- `{{1}}` - Customer name (POSITIONAL)
|
||||
- `{{2}}` - Delivery date (POSITIONAL)
|
||||
|
||||
**Chatwoot UI**
|
||||
|
||||

|
||||
|
||||
**WhatsApp Delivery**
|
||||
|
||||

|
||||
|
||||
### address_update
|
||||
|
||||
**Template Details:**
|
||||
- **Name**: `address_update`
|
||||
- **Category**: UTILITY
|
||||
- **Type**: Text Header + Body (Positional Parameters)
|
||||
- **Components**:
|
||||
- Header: "Address update" (TEXT)
|
||||
- Body: "Hi {{1}}, your delivery address has been successfully updated to {{2}}. Contact {{3}} for any inquiries."
|
||||
- **Parameters**:
|
||||
- `{{1}}` - Customer name (POSITIONAL)
|
||||
- `{{2}}` - New address (POSITIONAL)
|
||||
- `{{3}}` - Contact info (POSITIONAL)
|
||||
|
||||
#### Chatwoot UI
|
||||
|
||||

|
||||
|
||||
#### WhatsApp Delivery
|
||||
|
||||

|
||||
|
||||
### order_confirmation
|
||||
|
||||
**Template Details:**
|
||||
- **Name**: `order_confirmation`
|
||||
- **Category**: MARKETING
|
||||
- **Type**: Image Header + Body (Positional Parameters)
|
||||
- **Components**:
|
||||
- Header: IMAGE format
|
||||
- Body: "Hi your order {{1}} is confirmed. Please wait for further updates"
|
||||
- **Parameters**:
|
||||
- Media URL for image header
|
||||
- `{{1}}` - Order details (POSITIONAL)
|
||||
|
||||
**Chatwoot UI**
|
||||
|
||||

|
||||
|
||||
**WhatsApp Delivery**
|
||||
|
||||

|
||||
|
||||
### product_launch
|
||||
|
||||
**Template Details:**
|
||||
- **Name**: `product_launch`
|
||||
- **Category**: MARKETING
|
||||
- **Type**: Image Header + Body + Footer (Named Parameters)
|
||||
- **Components**:
|
||||
- Header: IMAGE format
|
||||
- Body: "New arrival! Our stunning coat now available in {{color}} color."
|
||||
- Footer: "Free shipping on orders over $100. Limited time offer."
|
||||
- **Parameters**:
|
||||
- Media URL for image header
|
||||
- `color` - Product color (NAMED)
|
||||
|
||||
**Chatwoot UI**
|
||||
|
||||

|
||||
|
||||
**WhatsApp Delivery**
|
||||
|
||||

|
||||
|
||||
### training_video
|
||||
|
||||
**Template Details:**
|
||||
- **Name**: `training_video`
|
||||
- **Category**: MARKETING
|
||||
- **Type**: Video Header + Body (Named Parameters)
|
||||
- **Components**:
|
||||
- Header: VIDEO format
|
||||
- Body: "Hi {{name}}, here's your training video. Please watch by{{date}}."
|
||||
- **Parameters**:
|
||||
- Media URL for video header
|
||||
- `name` - Employee name (NAMED)
|
||||
- `date` - Due date (NAMED)
|
||||
|
||||
**Chatwoot UI**
|
||||
|
||||

|
||||
|
||||
**WhatsApp Delivery**
|
||||
|
||||

|
||||
|
||||
### purchase_receipt
|
||||
|
||||
**Template Details:**
|
||||
- **Name**: `purchase_receipt`
|
||||
- **Category**: UTILITY
|
||||
- **Type**: Document Header + Body (Positional Parameters)
|
||||
- **Components**:
|
||||
- Header: DOCUMENT format
|
||||
- Body: "Thank you for using your {{1}} card at {{2}}. Your {{3}} is attached as a PDF."
|
||||
- **Parameters**:
|
||||
- Media URL for document header
|
||||
- `{{1}}` - Card type (POSITIONAL)
|
||||
- `{{2}}` - Merchant name (POSITIONAL)
|
||||
- `{{3}}` - Document type (POSITIONAL)
|
||||
|
||||
**Chatwoot UI**
|
||||
|
||||

|
||||
|
||||
**WhatsApp Delivery**
|
||||
|
||||

|
||||
|
||||
### event_invitation_static
|
||||
|
||||
**Template Details:**
|
||||
- **Name**: `event_invitation_static`
|
||||
- **Category**: MARKETING
|
||||
- **Type**: Body + Static URL Buttons (Named Parameters)
|
||||
- **Components**:
|
||||
- Body: "You're invited to {{event_name}} at {{location}}, Join us for an amazing experience!"
|
||||
- Buttons: 2 URL buttons ("Visit website", "Get Directions") with static URLs
|
||||
- **Parameters**:
|
||||
- `event_name` - Event name (NAMED)
|
||||
- `location` - Event location (NAMED)
|
||||
- **Button URLs**:
|
||||
- Visit website: `https://events.example.com/register` (static)
|
||||
- Get Directions: `https://maps.app.goo.gl/YoWAzRj1GDuxs6qz8` (static)
|
||||
|
||||
**Chatwoot UI**
|
||||
|
||||

|
||||
|
||||
**WhatsApp Delivery**
|
||||
|
||||

|
||||
|
||||
### feedback_request
|
||||
|
||||
**Template Details:**
|
||||
- **Name**: `feedback_request`
|
||||
- **Category**: MARKETING
|
||||
- **Type**: Body + URL Button (Named Parameters)
|
||||
- **Components**:
|
||||
- Body: "Hey {{name}}, how was your experience with Puma? We'd love your feedback!"
|
||||
- Buttons: 1 URL button ("Leave Feedback") with static URL
|
||||
- **Parameters**:
|
||||
- `name` - Customer name (NAMED)
|
||||
- **Button URL**:
|
||||
- Leave Feedback: `https://feedback.example.com/survey` (static)
|
||||
|
||||
**Chatwoot UI**
|
||||
|
||||

|
||||
|
||||
**WhatsApp Delivery**
|
||||
|
||||

|
||||
|
||||
### support_callback
|
||||
|
||||
**Template Details:**
|
||||
- **Name**: `support_callback`
|
||||
- **Category**: UTILITY
|
||||
- **Type**: Body + Phone Button (Named Parameters)
|
||||
- **Components**:
|
||||
- Body: "Hello {{name}}, our support team will call you regarding ticket # {{ticket_id}}."
|
||||
- Buttons: 1 PHONE_NUMBER button ("Call Support")
|
||||
- **Parameters**:
|
||||
- `name` - Customer name (NAMED)
|
||||
- `ticket_id` - Support ticket ID (NAMED)
|
||||
|
||||
**Chatwoot UI**
|
||||
|
||||

|
||||
|
||||
**WhatsApp Delivery**
|
||||
|
||||

|
||||
|
||||
### discount_coupon
|
||||
|
||||
**Template Details:**
|
||||
- **Name**: `discount_coupon`
|
||||
- **Category**: MARKETING
|
||||
- **Type**: Body + Copy Code Button (Named Parameters)
|
||||
- **Components**:
|
||||
- Body: "🎉 Special offer for you! Get {{discount_percentage}}% off your next purchase. Use the code below at checkout"
|
||||
- Buttons: 1 COPY_CODE button ("Copy offer code")
|
||||
- **Parameters**:
|
||||
- `discount_percentage` - Discount amount (NAMED)
|
||||
- Coupon code for button
|
||||
|
||||
**Chatwoot UI**
|
||||
|
||||

|
||||
|
||||
**WhatsApp Delivery**
|
||||
|
||||

|
||||
|
||||
### technician_visit
|
||||
|
||||
**Template Details:**
|
||||
- **Name**: `technician_visit`
|
||||
- **Category**: UTILITY
|
||||
- **Type**: Text Header + Body + Quick Reply Buttons (Positional Parameters)
|
||||
- **Components**:
|
||||
- Header: "Technician visit" (TEXT)
|
||||
- Body: "Hi {{1}}, we're scheduling a technician visit to {{2}} on {{3}} between {{4}} and {{5}}. Please confirm if this time slot works for you."
|
||||
- Buttons: 2 QUICK_REPLY buttons ("Confirm", "Reschedule")
|
||||
- **Parameters**:
|
||||
- `{{1}}` - Customer name (POSITIONAL)
|
||||
- `{{2}}` - Address (POSITIONAL)
|
||||
- `{{3}}` - Date (POSITIONAL)
|
||||
- `{{4}}` - Start time (POSITIONAL)
|
||||
- `{{5}}` - End time (POSITIONAL)
|
||||
|
||||
**Chatwoot UI**
|
||||
|
||||

|
||||
|
||||
**WhatsApp Delivery**
|
||||
|
||||

|
||||
|
||||
### basic_otp
|
||||
|
||||
**Template Details:**
|
||||
- **Name**: `basic_otp`
|
||||
- **Category**: AUTHENTICATION
|
||||
- **Type**: Body + URL Button (Positional Parameters)
|
||||
- **Components**:
|
||||
- Body: "*{{1}}* is your verification code. For your security, do not share this code."
|
||||
- Buttons: 1 URL button with OTP code ("Copy code")
|
||||
- **Parameters**:
|
||||
- `{{1}}` - OTP code (POSITIONAL)
|
||||
- **Special Features**: Auto-populated button parameter with OTP code
|
||||
|
||||
**Chatwoot UI**
|
||||
|
||||

|
||||
|
||||
**WhatsApp Delivery**
|
||||
|
||||

|
||||
|
||||
### otp_verification
|
||||
|
||||
**Template Details:**
|
||||
- **Name**: `otp_verification`
|
||||
- **Category**: AUTHENTICATION
|
||||
- **Type**: Body + URL Button (Positional Parameters)
|
||||
- **Components**:
|
||||
- Body: "Use code *{{1}}* to verify your transaction of {{2}}."
|
||||
- Buttons: 1 URL button with OTP code ("Copy code")
|
||||
- **Parameters**:
|
||||
- `{{1}}` - OTP code (POSITIONAL)
|
||||
- `{{2}}` - Transaction amount (POSITIONAL)
|
||||
- **Special Features**: Auto-populated button parameter with OTP code
|
||||
|
||||
**Chatwoot UI**
|
||||
|
||||

|
||||
|
||||
**WhatsApp Delivery**
|
||||
|
||||

|
||||
|
||||
### secure_login_otp
|
||||
|
||||
**Template Details:**
|
||||
- **Name**: `secure_login_otp`
|
||||
- **Category**: AUTHENTICATION
|
||||
- **Type**: Body + Footer + URL Button (Positional Parameters)
|
||||
- **Components**:
|
||||
- Body: "*{{1}}* is your verification code. For your security, do not share this code."
|
||||
- Footer: "This code expires in 10 minutes."
|
||||
- Buttons: 1 URL button with Zero-tap auto-fill ("Copy code")
|
||||
- **Parameters**:
|
||||
- `{{1}}` - OTP code (POSITIONAL)
|
||||
- **Special Features**: Zero-tap auto-fill functionality, Auto-populated button parameter
|
||||
|
||||
**Chatwoot UI**
|
||||
|
||||

|
||||
|
||||
#### WhatsApp Delivery
|
||||
|
||||

|
||||
@@ -1,365 +0,0 @@
|
||||
# WhatsApp Templates in Chatwoot
|
||||
|
||||
Chatwoot supports WhatsApp message templates for outbound messaging via the WhatsApp Business Platform.
|
||||
|
||||
📘 **Reference**: Meta Docs – [Message Templates](https://developers.facebook.com/docs/whatsapp/business-management-api/message-templates)
|
||||
|
||||
## Overview
|
||||
|
||||
WhatsApp Business API requires pre-approved message templates for initiating conversations with customers. Chatwoot provides support for creating and sending various types of templates while maintaining compliance with WhatsApp's template policies.
|
||||
|
||||
## ✅ What is Supported
|
||||
|
||||
### Template Categories
|
||||
|
||||
- **UTILITY** - Transactional messages (order confirmations, receipts, etc.)
|
||||
- **MARKETING** - Promotional content and offers
|
||||
- **AUTHENTICATION** - OTP codes and verification messages with automatic button parameter population
|
||||
- **SHIPPING_UPDATE** - Package and delivery status updates
|
||||
- **TICKET_UPDATE** - Support ticket notifications
|
||||
- **ISSUE_RESOLUTION** - Customer service follow-ups
|
||||
|
||||
### Template Components
|
||||
|
||||
#### Headers
|
||||
|
||||
- **TEXT** - Plain text headers with variable placeholders ({{1}}, {{2}}, etc.)
|
||||
- **IMAGE** - Image headers with media URL parameters (JPEG, PNG)
|
||||
- **VIDEO** - Video headers with media URL parameters (MP4, 3GPP)
|
||||
- **DOCUMENT** - Document headers with file URL parameters (PDF, DOC, DOCX, XLS, XLSX, PPT, PPTX, TXT)
|
||||
- **None** - Templates without headers
|
||||
|
||||
#### Body (Required)
|
||||
|
||||
- **TEXT** - Always required component that supports:
|
||||
- Static text content
|
||||
- Variable placeholders ({{1}}, {{2}}, etc.)
|
||||
- Special authentication parameters (OTP codes, expiry times)
|
||||
- Rich text formatting preservation
|
||||
|
||||
#### Footer (Optional)
|
||||
|
||||
- **TEXT** - Plain text footers with variable support
|
||||
|
||||
#### Buttons (Optional)
|
||||
|
||||
- **URL** - Call-to-action buttons with dynamic URLs and variable parameters
|
||||
- **PHONE_NUMBER** - Click-to-call buttons
|
||||
- **COPY_CODE** - Coupon/discount code buttons (max 15 characters)
|
||||
- **QUICK_REPLY** - Interactive reply buttons for user responses
|
||||
|
||||
### Special Template Types
|
||||
|
||||
#### Authentication Templates
|
||||
|
||||
- **OTP Templates**: Automatic OTP code and expiry time parameter handling
|
||||
- **Button Auto-population**: URL buttons automatically populated with OTP codes
|
||||
- **Enhanced Validation**: OTP codes (4-8 digits), expiry times validation
|
||||
- **Structured Parameters**: Organized body and button parameter processing
|
||||
|
||||
#### Media Templates
|
||||
|
||||
- **Image Templates**: Full support for JPEG, PNG formats with URL validation
|
||||
- **Video Templates**: Full support for MP4, 3GPP formats with URL validation
|
||||
- **Document Templates**: **NEW** - Full support for PDF, Office documents, and text files
|
||||
- **URL Validation**: Comprehensive validation, sanitization, and accessibility checks
|
||||
- **Media Parameter Building**: Proper WhatsApp API format generation
|
||||
|
||||
## 🔄 Supported Template Combinations
|
||||
|
||||
### Basic Templates
|
||||
|
||||
1. **Text Only**: Body component only
|
||||
2. **Text + Footer**: Body with footer text
|
||||
3. **Text Header + Body**: Text header with body content
|
||||
4. **Text Header + Body + Footer**: Full text template with all components
|
||||
|
||||
### Media Templates
|
||||
|
||||
5. **Image Header + Body**: Image with descriptive text
|
||||
6. **Image Header + Body + Footer**: Image template with footer
|
||||
7. **Video Header + Body**: Video with descriptive text
|
||||
8. **Document Header + Body**: **NEW** - Document with description (PDFs, Office docs, etc.)
|
||||
9. **Media + Buttons**: Any media header with action buttons
|
||||
|
||||
### Button Templates
|
||||
|
||||
10. **Body + URL Button**: Text with call-to-action and dynamic URLs
|
||||
11. **Body + Phone Button**: Text with click-to-call
|
||||
12. **Body + Copy Code**: Text with coupon code validation
|
||||
13. **Body + Quick Reply Button**: Text with interactive reply buttons
|
||||
14. **Authentication + URL Button**: OTP templates with auto-populated button parameters
|
||||
|
||||
### Authentication Templates
|
||||
|
||||
15. **OTP Template**: Body with OTP code and expiry with enhanced processing
|
||||
16. **OTP + Footer**: OTP template with footer text
|
||||
17. **OTP + URL Buttons**: **NEW** - OTP template with auto-populated action buttons
|
||||
|
||||
## ❌ What is Not Supported
|
||||
|
||||
### Interactive Components
|
||||
|
||||
- **LIST** templates with selectable options
|
||||
- **PRODUCT** templates with catalog integration
|
||||
- **CATALOG** templates for product browsing
|
||||
- **Multi-select** components
|
||||
|
||||
### Location Components
|
||||
|
||||
- **LOCATION** headers with map coordinates
|
||||
- **Address** parameters with latitude/longitude
|
||||
- **Location-based** templates
|
||||
|
||||
### Advanced Features
|
||||
|
||||
- **Rich text formatting** in template creation (preserved in sending)
|
||||
- **Carousel** templates with multiple cards
|
||||
- **Form** components for data collection
|
||||
- **Payment** integration templates
|
||||
- **Flow** templates with conditional logic
|
||||
|
||||
### Limitations
|
||||
|
||||
- **Button limit**: Maximum 3 buttons per template
|
||||
- **Variable limit**: Maximum 10 variables per component
|
||||
- **Character limits**: Per WhatsApp Business API restrictions
|
||||
- **Media size limits**: Documents < 100MB, Images < 5MB, Videos < 16MB
|
||||
|
||||
## 🚀 Using Templates in Chatwoot
|
||||
|
||||
### Template Creation
|
||||
|
||||
1. Create templates through WhatsApp Business Manager
|
||||
2. Ensure templates are in **approved** status
|
||||
3. Templates will automatically/manually sync to Chatwoot
|
||||
4. Unsupported template types are automatically filtered out
|
||||
|
||||
### Sending Templates
|
||||
|
||||
#### Legacy Template Interface (Current)
|
||||
|
||||
1. **Select Template**: Choose from approved templates in the picker
|
||||
2. **Fill Parameters**:
|
||||
- **Media URLs**: For image/video/document headers (must be publicly accessible)
|
||||
- **Body Variables**: Text values for {{1}}, {{2}}, etc. placeholders
|
||||
- **Button Parameters**: Dynamic values for URL buttons and copy codes
|
||||
- **Authentication Values**: OTP codes with automatic validation
|
||||
|
||||
### Parameter Validation & Processing
|
||||
|
||||
#### Frontend Processing
|
||||
|
||||
- **Variable Detection**: Automatic parsing of {{}} placeholders
|
||||
- **Parameter Organization**: Structured grouping by component type
|
||||
- **Validation Rules**: Real-time validation with user feedback
|
||||
- **Special Handling**: Authentication template auto-population
|
||||
|
||||
#### Backend Processing
|
||||
|
||||
- **Enhanced Template Processing**: Handles structured parameter format
|
||||
- **Media Parameter Building**: Generates proper WhatsApp API media objects
|
||||
- **Authentication Support**: Auto-populates button parameters with OTP values
|
||||
- **URL Validation**: Comprehensive accessibility and format checking
|
||||
|
||||
## 🔧 Technical Implementation
|
||||
|
||||
### Frontend Architecture
|
||||
|
||||
#### Components
|
||||
|
||||
- **TemplatesPicker.vue**: Template selection with filtering for supported types
|
||||
- **TemplateParser.vue**: **Enhanced** - Parameter input with structured processing
|
||||
- Media URL input for IMAGE/VIDEO/DOCUMENT headers
|
||||
- Body parameter handling with authentication special cases
|
||||
- Button parameter processing with auto-population
|
||||
- Enhanced validation and error handling
|
||||
|
||||
#### TODO:
|
||||
- Add support all the advanced template types for new Conversation templates
|
||||
- Add support for all the advanced template types for WhatsApp Campaigns
|
||||
|
||||
### Backend Architecture
|
||||
|
||||
#### Core Service
|
||||
|
||||
- **TemplateProcessorService**: Main processing engine with multiple pathways:
|
||||
- **Enhanced Processing**: For structured parameters (header/body/buttons)
|
||||
- **Legacy Processing**: For simple text-only templates
|
||||
- **Authentication Processing**: Special handling for OTP templates
|
||||
- **Media Processing**: Dedicated handling for IMAGE/VIDEO/DOCUMENT templates
|
||||
|
||||
#### Processing Flow
|
||||
|
||||
```ruby
|
||||
# 1. Parameter Routing
|
||||
if structured_params?(header/body/buttons)
|
||||
process_enhanced_template_params()
|
||||
else
|
||||
# Legacy processing for simple templates
|
||||
process_legacy_template_params()
|
||||
end
|
||||
|
||||
2. Component Building
|
||||
- Header: Media parameter generation
|
||||
- Body: Text parameter processing
|
||||
- Buttons: URL/copy_code parameter handling
|
||||
|
||||
3. WhatsApp API Formatting
|
||||
Convert to proper WhatsApp Cloud API structure
|
||||
```
|
||||
|
||||
#### Media Parameter Handling
|
||||
|
||||
- **URL Validation**: Scheme, accessibility, format checking
|
||||
- **Media Type Detection**: Automatic format detection and validation
|
||||
- **Parameter Building**: Generates proper WhatsApp media objects
|
||||
- **Error Handling**: Comprehensive validation with helpful error messages
|
||||
|
||||
### API Integration
|
||||
|
||||
- **WhatsApp Cloud API**: Primary integration for template sending
|
||||
- **Template Sync**: Automatic synchronization of approved templates with filtering
|
||||
- **Parameter Formatting**: Converts Chatwoot structured parameters to WhatsApp format
|
||||
- **Error Handling**: Validates parameters and provides detailed error feedback
|
||||
|
||||
## 📋 Best Practices
|
||||
|
||||
### Template Design
|
||||
|
||||
- Keep message content **clear and concise**
|
||||
- Use **meaningful variable names** for better organization
|
||||
- Test templates thoroughly before approval
|
||||
- Follow WhatsApp's **template policies** and guidelines
|
||||
|
||||
### Parameter Management
|
||||
|
||||
- **Media URLs**: Use publicly accessible URLs (avoid temporary/auth-required URLs)
|
||||
- ✅ `https://your-domain.com/files/document.pdf`
|
||||
- ❌ `https://scontent.whatsapp.net/...` (temporary WhatsApp URLs)
|
||||
- **Authentication Templates**: Let system auto-populate button parameters
|
||||
- **Validation**: Always validate parameters before sending
|
||||
- **Error Handling**: Implement proper error handling for failed parameters
|
||||
|
||||
### Media Best Practices
|
||||
|
||||
- **Documents**: Use direct download URLs, ensure < 100MB file size
|
||||
- **Images**: Optimize for mobile viewing, ensure < 5MB file size
|
||||
- **Videos**: Keep under 16MB, use MP4 format for best compatibility
|
||||
- **Accessibility**: Ensure all media URLs are publicly accessible without authentication
|
||||
|
||||
### Compliance
|
||||
|
||||
- Ensure all templates are **properly approved** before use
|
||||
- Follow **opt-in requirements** for marketing templates
|
||||
- Respect **rate limits** and sending windows
|
||||
- Monitor **template quality ratings** and compliance metrics
|
||||
|
||||
## 🆘 Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### Template Issues
|
||||
|
||||
- **Template not appearing**: Check approval status and supported format
|
||||
- **Interactive templates missing**: LIST/PRODUCT/CATALOG templates are not supported
|
||||
- **Location templates missing**: LOCATION templates are not supported
|
||||
|
||||
#### Parameter Issues
|
||||
|
||||
- **Parameter validation errors**: Verify required fields are filled
|
||||
- **Media loading failures**: Ensure URLs are publicly accessible and valid format
|
||||
- **Button parameters empty**: Check auto-population for authentication templates
|
||||
|
||||
#### Sending Failures
|
||||
|
||||
- **Format mismatch errors**: Verify template structure matches expected format
|
||||
- **Media upload errors (131053)**: Check media URL accessibility and file size
|
||||
- **Missing parameter errors**: Ensure all required template variables are filled
|
||||
|
||||
### Error Messages & Solutions
|
||||
|
||||
#### Media Errors
|
||||
|
||||
- `Media upload error (131053)`:
|
||||
- **Cause**: URL not accessible, file too large, or unsupported format
|
||||
- **Solution**: Use publicly accessible URLs, check file size limits
|
||||
|
||||
#### Parameter Errors
|
||||
|
||||
- `Required parameter is missing`:
|
||||
- **Cause**: Template variables not filled or button parameters missing
|
||||
- **Solution**: Fill all required fields, check authentication auto-population
|
||||
|
||||
#### Template Errors
|
||||
|
||||
- `Template not found`: Template may not be approved or synced
|
||||
- `Invalid parameter format`: Check variable formatting and requirements
|
||||
- `OTP validation failed`: Ensure OTP is 4-8 digits numeric only
|
||||
|
||||
### Debugging Support
|
||||
|
||||
- Check WhatsApp Business Manager for template approval status
|
||||
- Verify template compliance with supported component types
|
||||
- Review parameter structure and validation requirements
|
||||
- Check media URL accessibility independently
|
||||
- Monitor Rails logs for detailed error information
|
||||
|
||||
## 📈 Future Roadmap & TODOs
|
||||
|
||||
### Future Enhancements
|
||||
|
||||
While Chatwoot currently provides comprehensive core template functionality, potential future improvements include:
|
||||
|
||||
- **Rich text formatting** support in template creation
|
||||
- **Enhanced media validation** with format conversion
|
||||
- **Template performance analytics** and usage metrics
|
||||
- **Advanced parameter management** with preset values
|
||||
- **Custom template validation rules** and business logic
|
||||
- **Template testing environment** for development
|
||||
- **Media asset management** with CDN integration
|
||||
|
||||
### Integration Improvements
|
||||
|
||||
- **Webhook support** for template status changes
|
||||
- **Advanced error handling** with retry mechanisms
|
||||
- **Template versioning** and rollback capabilities
|
||||
- **Multi-language template** management
|
||||
- **Template approval workflow** integration
|
||||
|
||||
## ⚠️ Known Limitations
|
||||
|
||||
### Dynamic URL Button Issues
|
||||
|
||||
Currently, there are known issues with dynamic URL buttons in certain templates due to WhatsApp Business Manager generating malformed URL structures with mixed parameter formats. **Workaround**: Use static URLs in your button templates instead of dynamic parameters for reliable functionality.
|
||||
|
||||
### QA Checklist
|
||||
|
||||
**Complete Template Coverage: 16 Templates covering all 17 Supported Combinations**
|
||||
|
||||
| Template Name | Category | Header | Buttons | Template Type | Coverage |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| greet | MARKETING | None | None | Text Only | ✅ |
|
||||
| delivery_confirmation | UTILITY | None | None | Text Only | ✅ |
|
||||
| hello_world | UTILITY | TEXT | None | Text Header + Body + Footer | ✅ |
|
||||
| address_update | UTILITY | TEXT | None | Text Header + Body | ✅ |
|
||||
| order_confirmation | MARKETING | IMAGE | None | Image Header + Body | ✅ |
|
||||
| product_launch | MARKETING | IMAGE | None | Image Header + Body + Footer | ✅ |
|
||||
| training_video | MARKETING | VIDEO | None | Video Header + Body | ✅ |
|
||||
| purchase_receipt | UTILITY | DOCUMENT | None | Document Header + Body | ✅ |
|
||||
| event_invitation_static | MARKETING | None | URL, URL | Body + Static URL Buttons | ✅ |
|
||||
| feedback_request | MARKETING | None | URL | Body + URL Button | ✅ |
|
||||
| support_callback | UTILITY | None | PHONE_NUMBER | Body + Phone Button | ✅ |
|
||||
| discount_coupon | MARKETING | None | COPY_CODE | Body + Copy Code | ✅ |
|
||||
| technician_visit | UTILITY | TEXT | QUICK_REPLY, QUICK_REPLY | Body + Quick Reply Button | ✅ |
|
||||
| basic_otp | AUTHENTICATION | None | URL (Copy Code) | Basic OTP Template | ✅ |
|
||||
| otp_verification | AUTHENTICATION | None | URL (Copy Code) | Authentication + URL Button | ✅ |
|
||||
| secure_login_otp | AUTHENTICATION | None | URL (Zero-tap) | OTP + Footer + Zero-tap | ✅ |
|
||||
|
||||
**Summary:**
|
||||
- **Total Templates**: 16
|
||||
- **Template Combinations Covered**: 17/17 (100%)
|
||||
- **Authentication Methods**: Copy Code, Zero-tap Auto-fill
|
||||
- **Media Types**: TEXT, IMAGE, VIDEO, DOCUMENT
|
||||
- **Button Types**: URL, PHONE_NUMBER, COPY_CODE, QUICK_REPLY
|
||||
- **Parameter Formats**: NAMED, POSITIONAL
|
||||
Reference in New Issue
Block a user