Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
d7ac2ae
chore: add recordingCompleteCallback scafold
gpaoloni Jul 1, 2026
7ddc453
chore: expose route
gpaoloni Jul 1, 2026
dfc15e0
chore: create voicemail task
gpaoloni Jul 2, 2026
d7af718
chore: use voicemail task channel
gpaoloni Jul 2, 2026
ce94f7d
fix: remove explicit task queue
gpaoloni Jul 2, 2026
c398609
fix: stringify task attributes
gpaoloni Jul 2, 2026
ce4107c
chore: attach conversation media on contact creation
gpaoloni Jul 8, 2026
5943e68
chore: debug
gpaoloni Jul 8, 2026
fe11565
fix: add customChannelType task attr
gpaoloni Jul 8, 2026
132bb1e
fix: add channelType task attr
gpaoloni Jul 8, 2026
44330aa
chore: add conversation media using dedicated endpoint
gpaoloni Jul 9, 2026
212c57e
debug
gpaoloni Jul 9, 2026
70b3c44
fix: path
gpaoloni Jul 9, 2026
e77ff0c
chore: add voicemail PoC UI
gpaoloni Jul 10, 2026
20a6b76
fix: add attribute needed for task assignment
gpaoloni Jul 13, 2026
5701a7b
fix: add attribute needed for task assignment
gpaoloni Jul 14, 2026
cf8cbf1
fix: canTransferConference only check for voice tasks
gpaoloni Jul 14, 2026
43c9fc5
chore: routing attr
gpaoloni Jul 14, 2026
887f814
Merge branch 'master' into CHI-3708
stephenhand Aug 3, 2026
2da8627
channel types enum
stephenhand Aug 3, 2026
fbb5375
Add extra info to voicemail callback
stephenhand Aug 4, 2026
72d687b
Add parameter checks
stephenhand Aug 4, 2026
5c8a450
Potential fix for pull request finding
stephenhand Aug 4, 2026
d6ec85e
Potential fix for pull request finding
stephenhand Aug 4, 2026
29eb71f
Potential fix for pull request finding
stephenhand Aug 4, 2026
2219b30
Merge remote-tracking branch 'origin/CHI-3946-productionise_backend' …
stephenhand Aug 4, 2026
0d6430f
Move create contact code around to be more resilient
stephenhand Aug 4, 2026
d73232b
Fix failing tests and linter issues
Copilot Aug 4, 2026
663d076
Fix test
stephenhand Aug 4, 2026
ed645bd
Tidy up
stephenhand Aug 5, 2026
30225b0
Linter
stephenhand Aug 5, 2026
34dd581
Fix failing createHrmContactTaskRouterListener test by mocking patchT…
Copilot Aug 5, 2026
796bf88
Merge branch 'master' into CHI-3946-productionise_backend
stephenhand Aug 5, 2026
c16b2ec
Add comprehensive unit tests for new voicemail lambda code
Copilot Aug 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { Twilio } from 'twilio';
import { ROUTE_PREFIX } from '../router';
import { AccountSID } from '@tech-matters/twilio-types';
import { savePostSurvey } from '../hrm/savePostSurvey';
import { ChannelType } from '@tech-matters/twilio-types';

const triggerTypes = ['withUserMessage', 'withNextMessage'] as const;
export type TriggerTypes = (typeof triggerTypes)[number];
Expand All @@ -43,7 +44,7 @@ export type CapturedChannelAttributes = {
releaseFlag?: string;
chatbotCallbackWebhookSid: string;
isConversation: boolean;
channelType: string;
channelType: ChannelType;
};

export const isChatCaptureControlTask = (taskAttributes: {
Expand Down Expand Up @@ -154,7 +155,7 @@ type CaptureChannelOptions = {
memoryAttribute?: string; // where in the task attributes we want to save the bot's memory (allows compatibility for multiple bots)
releaseFlag?: string; // the flag we want to set true when the channel is released
isConversation: boolean;
channelType: string;
channelType: ChannelType;
webhookBaseUrl: string;
};

Expand Down Expand Up @@ -357,7 +358,7 @@ export type HandleChannelCaptureParams = (
releaseFlag?: string; // The flag we want to set true in the channel attributes when the channel is released
additionControlTaskAttributes?: string; // Optional attributes to include in the control task, in the string representation of a JSON
controlTaskTTL?: number;
channelType: string;
channelType: ChannelType;
twilioWorkspaceSid: string;
chatServiceSid: string;
surveyWorkflowSid: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
registerTaskRouterEventHandler,
TaskRouterEventHandler,
} from '../taskrouter/taskrouterEventHandler';
import { AccountSID } from '@tech-matters/twilio-types';
import { AccountSID, ChannelType } from '@tech-matters/twilio-types';
import { Twilio } from 'twilio';
import { EventType, TASK_WRAPUP } from '../taskrouter/eventTypes';
import { EventFields } from '../taskrouter';
Expand Down Expand Up @@ -96,7 +96,7 @@ const postSurveyInitHandler = async ({
client: Twilio;
taskSid: string;
taskLanguage: string;
channelType: string;
channelType: ChannelType;
environment: string;
webhookBaseUrl: string;
chatServiceSid: string;
Expand Down
10 changes: 7 additions & 3 deletions lambdas/account-scoped/src/conversation/createConversation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,18 @@
*/

import { Twilio } from 'twilio';
import { ConversationSID } from '@tech-matters/twilio-types';
import { AseloCustomChannel } from '../customChannels/aseloCustomChannels';
import {
AseloCustomChannelType,
ChannelType,
channelTypes,
ConversationSID,
} from '@tech-matters/twilio-types';
import { newErr, newOk, Result } from '../Result';

const CONVERSATION_CLOSE_TIMEOUT = 'P3D'; // ISO 8601 duration format https://en.wikipedia.org/wiki/ISO_8601
export type CreateFlexConversationParams = {
studioFlowSid: string;
channelType: AseloCustomChannel | 'web'; // The chat channel being used
channelType: ChannelType & (AseloCustomChannelType | typeof channelTypes.WEB); // The chat channel being used
uniqueUserName: string; // Unique identifier for this user
senderScreenName: string; // Friendly info to show in the Flex UI (like Telegram handle)
onMessageAddedWebhookUrl?: string; // The url that must be used as the onMessageSent event webhook.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,13 @@ import { newErr, newOk } from '../Result';
import { getTwilioClient, getDocsBucketName } from '@tech-matters/twilio-configuration';
import { newMissingParameterResult } from '../httpErrors';

export const getExternalRecordingS3LocationHandler: FlexValidatedHandler = async (
{ body: event },
accountSid: AccountSID,
) => {
const { callSid } = event as { callSid?: string };

if (!callSid) {
return newMissingParameterResult('callSid');
}

export const getExternalRecordingS3Location = async ({
callSid,
accountSid,
}: {
callSid: string;
accountSid: AccountSID;
}) => {
try {
const client = await getTwilioClient(accountSid);
const bucket = await getDocsBucketName(accountSid);
Expand All @@ -56,3 +53,16 @@ export const getExternalRecordingS3LocationHandler: FlexValidatedHandler = async
return newErr({ message: err.message, error: { statusCode: 500, cause: err } });
}
};

export const getExternalRecordingS3LocationHandler: FlexValidatedHandler = async (
{ body: event },
accountSid: AccountSID,
) => {
const { callSid } = event as { callSid?: string };

if (!callSid) {
return newMissingParameterResult('callSid');
}

return getExternalRecordingS3Location({ accountSid, callSid });
};
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ import { retrieveFeatureFlags } from '../configuration/aseloConfiguration';
import { chatChannelJanitor } from './chatChannelJanitor';
import { hasTaskControl } from '../transfer/hasTaskControl';
import { isChatCaptureControlTask } from '../channelCapture/channelCaptureHandlers';
import { isAseloCustomChannel } from '../customChannels/aseloCustomChannels';
import { getWorkspaceSid } from '@tech-matters/twilio-configuration';
import { ChatChannelSID, ConversationSID } from '@tech-matters/twilio-types';
import { getCurrentDefinitionVersion } from '../hrm/formDefinitionsCache';
import { ChannelType, isAseloCustomChannelType } from '@tech-matters/twilio-types';

const isCleanupBotCapture = (
eventType: EventType,
Expand Down Expand Up @@ -71,8 +71,8 @@ const isCleanupCustomChannel = async (
workspaceSid: string,
taskSid: string,
taskAttributes: {
channelType?: string;
customChannelType?: string;
channelType?: ChannelType;
customChannelType?: ChannelType;
isChatCaptureControl?: boolean;
},
) => {
Expand All @@ -84,7 +84,7 @@ const isCleanupCustomChannel = async (
return false;
}

return isAseloCustomChannel(
return isAseloCustomChannelType(
taskAttributes.customChannelType || taskAttributes.channelType,
);
};
Expand Down
9 changes: 6 additions & 3 deletions lambdas/account-scoped/src/customChannels/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/

import { AccountSID } from '@tech-matters/twilio-types';
import {
AccountSID,
AseloCustomChannelType,
ChannelType,
} from '@tech-matters/twilio-types';
import { getSsmParameter, SsmParameterNotFound } from '@tech-matters/ssm-cache';
import { AseloCustomChannel } from './customChannelToFlex';
import { retrieveServiceConfigurationAttributes } from '../configuration/aseloConfiguration';
import { getTwilioClient } from '@tech-matters/twilio-configuration';

Expand Down Expand Up @@ -89,7 +92,7 @@ export const getFacebookAppSecret = (): Promise<string> =>

export const getChannelStudioFlowSid = (
accountSid: AccountSID,
channelName: AseloCustomChannel | 'chat',
channelName: ChannelType & (AseloCustomChannelType | 'chat'),
): Promise<string> =>
getSsmParameter(
`/${process.env.NODE_ENV}/twilio/${accountSid}/${channelName}_studio_flow_sid`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,6 @@ export const removeConversation = async (
},
) => client.conversations.v1.conversations.get(conversationSid).remove();

export { AseloCustomChannel, isAseloCustomChannel } from './aseloCustomChannels';

type SendConversationMessageToFlexParams = Omit<
CreateFlexConversationParams,
'twilioNumber'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@
import '@twilio-labs/serverless-runtime-types';
import crypto from 'crypto';
import {
AseloCustomChannel,
findExistingConversation,
sendConversationMessageToFlex,
} from '../customChannelToFlex';
import { AccountScopedHandler, HttpRequest } from '../../httpTypes';
import {
AccountSID,
aseloCustomChannelTypes,
ConversationSID,
InstagramMessageEvent,
InstagramMessageObject,
Expand Down Expand Up @@ -117,15 +117,15 @@ export const instagramToFlexHandler: AccountScopedHandler = async (
const senderExternalId = sender.id;
const messageExternalId = message.mid;
const subscribedExternalId = event.entry[0].id;
const channelType = AseloCustomChannel.Instagram;
const channelType = aseloCustomChannelTypes.INSTAGRAM;
const chatFriendlyName = `${channelType}:${senderExternalId}`;
const uniqueUserName = `${channelType}:${senderExternalId}`;
const senderScreenName = senderExternalId; // TODO: see if we can use ig handle somehow
const messageAttributes = JSON.stringify({ messageExternalId });
const onMessageSentWebhookUrl = `${process.env.WEBHOOK_BASE_URL}/lambda/twilio/account-scoped/${accountSid}/customChannels/instagram/flexToInstagram?recipientId=${senderExternalId}`;
const studioFlowSid = await getChannelStudioFlowSid(
accountSid,
AseloCustomChannel.Instagram,
aseloCustomChannelTypes.INSTAGRAM,
);
let result;
if (isInstagramStoryReply(message)) {
Expand Down
11 changes: 4 additions & 7 deletions lambdas/account-scoped/src/customChannels/line/lineToFlex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,8 @@
*/

import crypto from 'crypto';
import { AccountSID } from '@tech-matters/twilio-types';
import {
AseloCustomChannel,
sendConversationMessageToFlex,
} from '../customChannelToFlex';
import { AccountSID, aseloCustomChannelTypes } from '@tech-matters/twilio-types';
import { sendConversationMessageToFlex } from '../customChannelToFlex';
import { AccountScopedHandler, HttpRequest } from '../../httpTypes';
import { newErr, newOk } from '../../Result';
import { getChannelStudioFlowSid, getLineChannelSecret } from '../configuration';
Expand Down Expand Up @@ -125,13 +122,13 @@ export const lineToFlexHandler: AccountScopedHandler = async (

const studioFlowSid = await getChannelStudioFlowSid(
accountSid,
AseloCustomChannel.Line,
aseloCustomChannelTypes.LINE,
);
const responses: any[] = [];

for (const messageEvent of messageEvents) {
const messageText = messageEvent.message.text;
const channelType = AseloCustomChannel.Line;
const channelType = aseloCustomChannelTypes.LINE;
const subscribedExternalId = destination; // AseloChat ID on Line
const senderExternalId = messageEvent.source.userId; // The child ID on Line
const chatFriendlyName = `${channelType}:${senderExternalId}`;
Expand Down
11 changes: 4 additions & 7 deletions lambdas/account-scoped/src/customChannels/modica/modicaToFlex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,8 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/

import { AccountSID } from '@tech-matters/twilio-types';
import {
AseloCustomChannel,
sendConversationMessageToFlex,
} from '../customChannelToFlex';
import { AccountSID, aseloCustomChannelTypes } from '@tech-matters/twilio-types';
import { sendConversationMessageToFlex } from '../customChannelToFlex';
import { AccountScopedHandler, HttpRequest } from '../../httpTypes';
import { newErr, newOk } from '../../Result';
import { getChannelStudioFlowSid } from '../configuration';
Expand All @@ -41,7 +38,7 @@ export const modicaToFlexHandler: AccountScopedHandler = async (
const { source, destination, content } = event;

const messageText = content;
const channelType = AseloCustomChannel.Modica;
const channelType = aseloCustomChannelTypes.MODICA;
const subscribedExternalId = destination; // The helpline short code
const senderExternalId = source; // The child phone number
const chatFriendlyName = senderExternalId;
Expand All @@ -50,7 +47,7 @@ export const modicaToFlexHandler: AccountScopedHandler = async (
const onMessageSentWebhookUrl = `${process.env.WEBHOOK_BASE_URL}/lambda/twilio/account-scoped/${accountSid}/customChannels/modica/flexToModica?recipientId=${senderExternalId}`;
const studioFlowSid = await getChannelStudioFlowSid(
accountSid,
AseloCustomChannel.Modica,
aseloCustomChannelTypes.MODICA,
);
console.debug(
'ModicaToFlex: sending message from',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,8 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/

import { AccountSID } from '@tech-matters/twilio-types';
import {
AseloCustomChannel,
sendConversationMessageToFlex,
} from '../customChannelToFlex';
import { AccountSID, aseloCustomChannelTypes } from '@tech-matters/twilio-types';
import { sendConversationMessageToFlex } from '../customChannelToFlex';
import { AccountScopedHandler, HttpRequest } from '../../httpTypes';
import { newErr, newOk } from '../../Result';
import { getChannelStudioFlowSid, getTelegramBotApiSecretToken } from '../configuration';
Expand Down Expand Up @@ -61,14 +58,14 @@ export const telegramToFlexHandler: AccountScopedHandler = async (
chat: { id: senderExternalId, username, first_name: firstName },
} = event.message;

const channelType = AseloCustomChannel.Telegram;
const channelType = aseloCustomChannelTypes.TELEGRAM;
const chatFriendlyName = username || `${channelType}:${senderExternalId}`;
const uniqueUserName = `${channelType}:${senderExternalId}`;
const senderScreenName = firstName || username || 'child';
const onMessageAddedWebhookUrl = `${process.env.WEBHOOK_BASE_URL}/lambda/twilio/account-scoped/${accountSid}/customChannels/telegram/flexToTelegram?recipientId=${senderExternalId}`;
const studioFlowSid = await getChannelStudioFlowSid(
accountSid,
AseloCustomChannel.Telegram,
aseloCustomChannelTypes.TELEGRAM,
);
console.debug(
'TelegramToFlex: sending message from',
Expand Down
Loading
Loading