Hello and good day !
I seem to have hit a wall regarding the creation of my app.
I have trying to send an outbound email using :
post
/api/v2/tickets/outbound_email
But I receive this error message :
Invalid feature or feature not enabled
We are on the omnichannel plan and Postman requests works.
Here are some snippets of my code :
Requests.json :
"createTicket": {
"schema": {
"method": "POST",
"host": "<%= iparam.domain %>",
"path": "/api/v2/tickets",
"headers": {
"Authorization": "Basic <%= encode(iparam.api_key + ':X') %>",
"Content-Type": "application/json"
}
}
}
iparams.html :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Group Email Mappings Configuration</title>
<script src="https://static.freshdev.io/fdk/2.0/assets/fresh_client.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="{{{appclient}}}"></script>
<style>
body { font-family: Arial, sans-serif; padding: 20px; }
.mapping { margin-bottom: 20px; border: 1px solid #ccc; padding: 10px; }
.email-list { margin-left: 20px; }
button { margin-top: 10px; }
#error-message { color: red; }
</style>
</head>
<body>
<h1>Group Email Mappings Configuration</h1>
<div>
<label for="domain">Freshdesk Domain:</label>
<input type="text" id="domain" name="domain" required>
</div>
<div>
<label for="api_key">API Key:</label>
<input type="password" id="api_key" name="api_key" required>
</div>
<button id="fetch-data">Fetch Groups and Mailboxes</button>
<div id="error-message"></div>
<div id="mappings-container"></div>
<button id="add-mapping">Add Group Mapping</button>
<!-- Add this after the existing fields -->
<div>
<label for="categories">Categories:</label>
<textarea id="categories" name="categories" rows="10" cols="50" readonly></textarea>
</div>
<button id="fetch-categories">Fetch Categories</button>
<script>
let mappings = [];
let groups = [];
let mailboxes = [];
async function fetchGroupsAndMailboxes() {
const domain = document.getElementById('domain').value;
const apiKey = document.getElementById('api_key').value;
console.log('Fetching data for domain:', domain);
if (!domain || !apiKey) {
showError('Please enter both domain and API key.');
return;
}
try {
const groupsResponse = await fetch(`https://${domain}/api/v2/groups?per_page=100`, {
headers: {
'Authorization': `Basic ${btoa(apiKey + ':X')}`
}
});
groups = await groupsResponse.json();
console.log('Fetched groups:', groups);
const mailboxesResponse = await fetch(`https://${domain}/api/v2/email/mailboxes?per_page=100`, {
headers: {
'Authorization': `Basic ${btoa(apiKey + ':X')}`
}
});
mailboxes = await mailboxesResponse.json();
console.log('Fetched mailboxes:', mailboxes);
renderMappings();
} catch (error) {
showError('Error fetching data. Please check your domain and API key.');
console.error('Error fetching data:', error);
}
}
async function fetchCategories() {
const domain = document.getElementById('domain').value;
const apiKey = document.getElementById('api_key').value;
if (!domain || !apiKey) {
showError('Please enter both domain and API key.');
return;
}
try {
const response = await fetch(`https://${domain}/api/v2/admin/ticket_fields/24000007707`, {
headers: {
'Authorization': `Basic ${btoa(apiKey + ':X')}`
}
});
const data = await response.json();
document.getElementById('categories').value = JSON.stringify(data, null, 2);
} catch (error) {
showError('Error fetching categories. Please check your domain and API key.');
console.error('Error fetching categories:', error);
}
}
function showError(message) {
document.getElementById('error-message').textContent = message;
}
function renderMappings() {
console.log('Rendering mappings. Current mappings:', mappings);
console.log('Available groups:', groups);
console.log('Available mailboxes:', mailboxes);
const container = document.getElementById('mappings-container');
container.innerHTML = '';
mappings.forEach((mapping, index) => {
const mappingDiv = document.createElement('div');
mappingDiv.className = 'mapping';
mappingDiv.innerHTML = `
<h3>Group ${index + 1}</h3>
<label>Group:
<select class="group-select" data-index="${index}">
${groups.map(group => `<option value="${group.id}" ${mapping.group_id == group.id ? 'selected' : ''}>${group.name}</option>`).join('')}
</select>
</label>
<div class="email-list">
${mapping.email_addresses.map((email, emailIndex) => `
<div>
<select class="email-select" data-group="${index}" data-email="${emailIndex}">
${mailboxes.map(mailbox => `<option value="${mailbox.id}" ${email.id === mailbox.id ? 'selected' : ''}>${mailbox.name} (${mailbox.support_email})</option>`).join('')}
</select>
<button class="remove-email" data-group="${index}" data-email="${emailIndex}">Remove</button>
</div>
`).join('')}
</div>
<button class="add-email" data-group="${index}">Add Email</button>
<button class="remove-mapping" data-group="${index}">Remove Group</button>
`;
container.appendChild(mappingDiv);
});
console.log('Finished rendering mappings');
}
function addMapping() {
console.log('Adding new mapping');
console.log('Current groups:', groups);
console.log('Current mailboxes:', mailboxes);
if (groups.length > 0 && mailboxes.length > 0) {
mappings.push({
group_id: groups[0].id,
group_name: groups[0].name,
email_addresses: [{
id: mailboxes[0].id,
name: mailboxes[0].name,
email: mailboxes[0].support_email
}]
});
console.log('New mapping added:', mappings[mappings.length - 1]);
renderMappings();
} else {
showError('Please fetch groups and mailboxes first.');
console.warn('Cannot add mapping: groups or mailboxes are empty');
}
}
function removeMapping(index) {
mappings.splice(index, 1);
renderMappings();
}
function addEmail(groupIndex) {
console.log(`Adding email to group at index ${groupIndex}`);
console.log('Current mailboxes:', mailboxes);
if (mailboxes.length > 0) {
mappings[groupIndex].email_addresses.push({
id: mailboxes[0].id,
name: mailboxes[0].name,
email: mailboxes[0].support_email
});
console.log('Email added:', mailboxes[0].support_email);
renderMappings();
} else {
showError('No mailboxes available. Please fetch data first.');
console.warn('Cannot add email: mailboxes are empty');
}
}
function removeEmail(groupIndex, emailIndex) {
mappings[groupIndex].email_addresses.splice(emailIndex, 1);
renderMappings();
}
document.addEventListener('click', function(event) {
if (event.target.classList.contains('remove-mapping')) {
removeMapping(parseInt(event.target.dataset.group));
} else if (event.target.classList.contains('add-email')) {
addEmail(parseInt(event.target.dataset.group));
} else if (event.target.classList.contains('remove-email')) {
removeEmail(parseInt(event.target.dataset.group), parseInt(event.target.dataset.email));
}
});
document.addEventListener('change', function(event) {
if (event.target.classList.contains('group-select')) {
const index = parseInt(event.target.dataset.index);
const selectedOption = event.target.options[event.target.selectedIndex];
mappings[index].group_id = parseInt(selectedOption.value);
mappings[index].group_name = selectedOption.textContent;
} else if (event.target.classList.contains('email-select')) {
const groupIndex = parseInt(event.target.dataset.group);
const emailIndex = parseInt(event.target.dataset.email);
const selectedMailbox = mailboxes.find(m => m.id === parseInt(event.target.value));
mappings[groupIndex].email_addresses[emailIndex] = {
id: selectedMailbox.id,
name: selectedMailbox.name,
email: selectedMailbox.support_email
};
}
});
document.getElementById('add-mapping').addEventListener('click', addMapping);
document.getElementById('fetch-data').addEventListener('click', fetchGroupsAndMailboxes);
document.getElementById('fetch-categories').addEventListener('click', fetchCategories);
function getConfigs(configs) {
console.log('Getting configs:', configs);
document.getElementById('domain').value = configs.domain || '';
document.getElementById('api_key').value = configs.api_key || '';
mappings = configs.group_email_mappings || [];
console.log('Loaded mappings:', mappings);
if (configs.domain && configs.api_key) {
fetchGroupsAndMailboxes();
}
}
function postConfigs() {
const domain = document.getElementById('domain').value;
const apiKey = document.getElementById('api_key').value;
const categories = JSON.parse(document.getElementById('categories').value || '{}');
const configs = {
domain: domain,
api_key: apiKey,
group_email_mappings: mappings, // Make sure this line is present
categories: categories
};
console.log('Saving configs:', configs);
return configs;
}
function validate() {
const domain = document.getElementById('domain').value;
const apiKey = document.getElementById('api_key').value;
const categories = document.getElementById('categories').value;
if (!domain || !apiKey) {
showError('Please enter both domain and API key.');
return false;
}
if (mappings.length === 0) {
showError('Please add at least one group mapping.');
return false;
}
if (!categories) {
showError('Please fetch categories.');
return false;
}
return true;
}
</script>
</body>
</html>
app.js :
async function handleFormSubmit(formData) {
try {
// First, try to create an outbound email ticket
const outboundEmailResponse = await client.request.invoke('createOutboundEmail', {
body: JSON.stringify(formData)
});
if (outboundEmailResponse.status === 200) {
console.log('Outbound email ticket created successfully');
return outboundEmailResponse.data;
}
} catch (error) {
console.log('Error creating outbound email ticket:', error);
// If outbound email creation fails, try creating a regular ticket
try {
const regularTicketResponse = await client.request.invoke('createTicket', {
body: JSON.stringify(formData)
});
if (regularTicketResponse.status === 200) {
console.log('Regular ticket created successfully');
return regularTicketResponse.data;
}
} catch (ticketError) {
console.error('Error creating regular ticket:', ticketError);
throw ticketError;
}
}
throw new Error('Failed to create ticket');
}
function getFormData() {
const fromSelect = document.querySelector('#from');
if (!fromSelect || !fromSelect.value) {
showNotification('error', 'Please select a "From" email address.');
return null;
}
try {
const fromData = JSON.parse(fromSelect.value);
return {
name: "Requester Name", // You might want to get this from somewhere
email: document.querySelector('#to').value,
subject: document.querySelector('#subject').value,
description: document.querySelector('#description').value,
status: 5, // Closed
priority: parseInt(document.querySelector('#priority').value),
email_config_id: parseInt(fromData.id),
type: "Outbound Email",
custom_fields: {
cf_category: document.querySelector('#category').value,
cf_subcategory: document.querySelector('#subcategory').value
}
};
} catch (error) {
console.error('Error processing form data:', error);
showNotification('error', 'An error occurred while processing the form. Please try again.');
return null;
}
}
Manifest.json :
{
"platform-version": "2.3",
"product": {
"freshdesk": {
"location": {
"full_page_app": {
"url": "index.html",
"icon": "styles/images/icon.svg"
}
},
"requests": {
"createOutboundEmail": {
"schema": "createOutboundEmail"
}
}
}
},
"permissions": [
"data_storage",
"external_api_access"
],
"engines": {
"node": "18.19.1",
"fdk": "9.2.0"
}
}
Any help would be appreciated, thank you !