diff --git a/docs/docs/feature-library/_app-dialing-enabled.md b/docs/docs/feature-library/_app-dialing-enabled.md
new file mode 100644
index 000000000..9f3c417f5
--- /dev/null
+++ b/docs/docs/feature-library/_app-dialing-enabled.md
@@ -0,0 +1,3 @@
+:::tip Application dialing enabled
+You may also use this feature to dial [TwiML apps](https://console.twilio.com/?frameUrl=/console/voice/twiml/apps) in addition to standard phone numbers. This allows you to dial webhooks, Studio flows, and other Twilio accounts without incurring PSTN fees, while also passing context in the form of parameters. To perform an application dial, use the format `app:APxxxxx`, where `APxxxxx` is your application SID. You may pass parameters as well, using the format `app:APxxxxx?param1=value1¶m2=value2`.
+:::
\ No newline at end of file
diff --git a/docs/docs/feature-library/conference.md b/docs/docs/feature-library/conference.md
index 778122bc5..d1f5f4db8 100644
--- a/docs/docs/feature-library/conference.md
+++ b/docs/docs/feature-library/conference.md
@@ -2,6 +2,7 @@
sidebar_label: conference
title: conference
---
+import AppDialingEnabled from "./_app-dialing-enabled.md";
import PluginLibraryFeature from "./_plugin-library-feature.md";
@@ -12,6 +13,8 @@ If the beta native external warm transfer functionality is enabled, this feature
This feature is based on [the dialpad addon plugin](https://github.com/twilio-professional-services/flex-dialpad-addon-plugin).
+
+
## flex-user-experience

diff --git a/docs/docs/feature-library/contacts.md b/docs/docs/feature-library/contacts.md
index 436829728..e0d09e4ce 100644
--- a/docs/docs/feature-library/contacts.md
+++ b/docs/docs/feature-library/contacts.md
@@ -2,6 +2,7 @@
sidebar_label: contacts
title: Contacts
---
+import AppDialingEnabled from "./_app-dialing-enabled.md";
## Overview
@@ -13,6 +14,8 @@ This feature adds a contacts directory to Flex. The contacts directory consists
Contacts can be viewed, managed, and dialed using the contacts view added to Flex. In addition, a "Call Contact" section is added to the outbound dialer panel, allowing easy dialing of contacts from any view. Also, if the `custom-transfer-directory` feature is enabled, contacts are available in the transfer panel for cold or warm transfer, and shared contacts can be configured to disable cold and/or warm transfer capability.
+
+
## User experience
Recent contacts:
diff --git a/docs/docs/feature-library/custom-transfer-directory.md b/docs/docs/feature-library/custom-transfer-directory.md
index 474628b0b..3c84d1d53 100644
--- a/docs/docs/feature-library/custom-transfer-directory.md
+++ b/docs/docs/feature-library/custom-transfer-directory.md
@@ -2,6 +2,7 @@
sidebar_label: custom-transfer-directory
title: custom-transfer-directory
---
+import AppDialingEnabled from "./_app-dialing-enabled.md";
import PluginLibraryFeature from "./_plugin-library-feature.md";
@@ -23,6 +24,8 @@ It also enables the addition of an external directory, enabling the following be
- present a list of external transfer numbers from the `contacts` feature if enabled
- validation checks performed on transfer numbers with notifications of any validation failures
+
+
## flex-user-experience
Example queue transfer
diff --git a/plugin-flex-ts-template-v2/src/feature-library/custom-transfer-directory/flex-hooks/custom-actions/startExternalColdTransfer.ts b/plugin-flex-ts-template-v2/src/feature-library/custom-transfer-directory/flex-hooks/custom-actions/startExternalColdTransfer.ts
index 7028da796..b8342493d 100644
--- a/plugin-flex-ts-template-v2/src/feature-library/custom-transfer-directory/flex-hooks/custom-actions/startExternalColdTransfer.ts
+++ b/plugin-flex-ts-template-v2/src/feature-library/custom-transfer-directory/flex-hooks/custom-actions/startExternalColdTransfer.ts
@@ -24,7 +24,9 @@ export const registerStartExternalColdTransfer = async () => {
return;
}
- if (!shouldSkipPhoneNumberValidation()) {
+ // Validate phone numbers if not disabled. We cannot validate application SIDs as they
+ // may be from another account.
+ if (!shouldSkipPhoneNumberValidation() && !phoneNumber.startsWith('app:')) {
try {
const validationCheck = await PhoneNumberService.validatePhoneNumber(phoneNumber);
diff --git a/serverless-functions/src/functions/common/twilio-wrappers/programmable-voice.private.js b/serverless-functions/src/functions/common/twilio-wrappers/programmable-voice.private.js
index d54909f43..389582991 100644
--- a/serverless-functions/src/functions/common/twilio-wrappers/programmable-voice.private.js
+++ b/serverless-functions/src/functions/common/twilio-wrappers/programmable-voice.private.js
@@ -1,4 +1,4 @@
-const { isString, isObject } = require('lodash');
+const { isString, isObject, round } = require('lodash');
const axios = require('axios');
const { executeWithRetry, twilioExecute, getRegionUrl } = require(Runtime.getFunctions()[
@@ -33,6 +33,34 @@ exports.coldTransfer = async function coldTransfer(parameters) {
twiml: `${to}`,
});
}
+ if (to.startsWith('app:')) {
+ const toParts = to.split('?');
+ let paramsStr = '';
+ if (toParts.length > 1) {
+ // We have params, split them out
+ const paramParts = toParts[1].split('&');
+ const params = {};
+ for (const param of paramParts) {
+ const valueParts = param.split('=');
+ if (!valueParts.length) {
+ continue;
+ }
+ params[valueParts[0]] = valueParts[1];
+ }
+ for (const paramName in params) {
+ if (!Object.hasOwn(params, paramName)) {
+ continue;
+ }
+ paramsStr += ``;
+ }
+ }
+ return client.calls(callSid).update({
+ twiml: `${toParts[0].replace(
+ 'app:',
+ '',
+ )}${paramsStr}`,
+ });
+ }
return client.calls(callSid).update({
twiml: `${to}`,
});