Invalid feature or feature not enabled

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 !

I just saw that my requests.json is not the right version, here it is :

"createOutboundEmail": {
      "schema": {
        "method": "POST",
        "host": "<%= iparam.domain %>",
        "path": "/api/v2/tickets/outbound_email",
        "headers": {
          "Authorization": "Basic <%= encode(iparam.api_key + ':X') %>",
          "Content-Type": "application/json"
        }
      }
    }

I was testing if the createTickets would work but it was still the same error.